容器、Docker与Kubernetes——Kubernetes的配置入门


【编者的话】这是介绍Kubernetes的第三篇,主要集中讲述如何配置Kubernetes集群以及作者在配置过程中遇到的问题。

【3 天烧脑式容器存储网络训练营 | 深圳站】本次培训以容器存储和网络为主题,包括:Docker Plugin、Docker storage driver、Docker Volume Pulgin、Kubernetes Storage机制、容器网络实现原理和模型、Docker网络实现、网络插件、Calico、Contiv Netplugin、开源企业级镜像仓库Harbor原理及实现等。

在本系列文章的第一部分中,我们探讨了容器、Docker以及这些技术如何重新定义行业中的基础设施及其运营方式。第二部分文章则继续讨论,着眼点放在Kubernetes身上——包括Kubernetes是什么以及拥有哪些能力。在今天的第三部分文章中,我将进一步阐述如何上手Kubernetes,同时提供与结构设计相关的一点参考意见。

命令(Command)

Kubernetes的集群管理是通过kubectl命令进行的,如果你使用Google Cloud,则会在SDK中自动安装kubectl命令行工具。虽然你可以完全使用这个命令行工具来配置、控制Kubernetes集群,但是我还是十分推荐你使用单独的配置文件来配置集群,因为你可以使用版本控制工具来追踪每次对集群配置的修改而且保证配置的统一,后面会着重介绍如何去做。

kubectl命令集合包含了非常多的子命令来帮助你控制Kubernetes集群的方方面面,如窥探当前集群的工作状态等等。下面是我觉得非常常用的几个命令,并将其分类说明。

管理类(Management)

kubectl apply -f service-file.yml

apply命令会收集配置文件(service-file.yml)中所有的配置项信息,并自动将配置文件中的配置项跟当前集群的运行配置做对比,然后自动将必要的更新应用到当前集群中。
kubectl rollout

所有通过apply命令触发的指令都会生成一个新的Rollout对象。通过使用kubectl rollout status命令可以查看最近的更新状态、终止一个rollout或者回滚到上一个资源对象(Deployment,Pod,Service等)的版本。

窥探类(Introspection)

kubectl describe [pod,service,...] [resource]

这条命令可以查看某个资源的详细信息,当错误发生的时候你必须首选考虑使用describe命令来获取错误描述信息。
kubectl logs [resource]

Kubernetes内的容器会倾向于把所有的日志定向到STDOUT上,kubectl logs -f命令能让你获取一个资源(Pod/Container)最新的日志。
kubectl get [pods,deployments,services,...]

这条命令会将Kubernetes默认命名空间中运行的资源信息打印出来,如果要看特定命名空间的资源信息,则必须加上--namespace=[my namespace]参数,如果查看所有命名空间的资源信息,则使用--all-namespace参数。
kubectl exec

这条命令是对docker exec命令的包装,可以让你执行容器内部的命令,如果pod中只有一个容器在运行则此命令可以作用在pod上如:kubectl exec -it [pod] /bin/bash。我们可以使用kubectl exec -it [pod name] --/bin/bash来进入一个运行中的pod,-i用来启动标准输入流STDIN-t将输入流定向到TTY(伪终端)中,这样就能模拟终端的bash命令操作了。当然如果一个Pod中启动了多个容器,你可以使用-C[container-name]参数来进入特定的容器中。

部署新镜像

当前还没有命令能够让一个Deployment自动将配置的Pod下的容器更新到最新的版本;但是为Kubernetes集群更新容器版本又是一个非常常见的操作,这时你就必须想清楚更新容器的步骤了,我列出以下几种方法:
  • 将新的容器打上:latest的Tag,然后手动删除运行中的Pod,然后Deployment会自动用最新的容器重新启动Pod;
  • 更新服务的配置文件(Deployment yaml),让容器指向指向最新的container label,比如:redis-cache:9c713a,然后重新apply这个配置文件到Kubernetes的集群中,这种方式需要对配置文件进行版本控制;
  • 手动更新Deployment下Pod的镜像版本:kubectl set image deployment/[service name] *=[new image]


我需要的方案是即能够在Kubernetes中留下更改历史,也不想因为老是去提交配置文件的更改导致陷入版本混乱的泥沼。所以,我个人选择第三种方案,我是这么操作的:
  • 所有Pod template的image都指向容器的:latest tag;
  • 新的容器被Push到注册中心时除了打上:latest tag以外还要打上与代码git仓库HEAD指针指向的Commit号相同的tag(如:9c713a);
  • 运行kubectl set image deployment/[service name] *=[new image] 时填入git commit hash号标识的镜像。


根据以上几点,我可以获得一些好处:
  1. set image 命令来更新Pods的镜像非常的优雅,因为可以使用kubectl rollout status来跟踪所有Deployment的Pods的更新状态;
  2. 当Pod因为某些原因被杀掉或者新的Pod上线,都会确保运行拥有最新代码的镜像;
  3. 我可以在minikube中使用跟Kubernetes集群中相同的docker registry而不用担心会影响到生产环境的Pods;我一直到上线前都不会使用:latest tag的镜像(注:在测试环境或者非生产环境使用git commit hash号来启动容器,但是在生产时使用:latest,因为作者在push镜像时同时打了两个tag)。


搭建本地开发环境

Kubernetes开发人员不仅提供了相当全面的文档可以参考,而且提供了minikube这个可以让开发者在本地环境运行Kubernetes集群的工具。minikube可以运行在多种不同的虚拟化环境下,它可以很容易的启动一个全功能的,包含一个单一节点的Kubernetes集群。当集群启动后,minikube提供了多种命令来访问与窥探集群运行情况,同时kubectl工具也是自动为minikube配置好的。最常用的命令莫过于:
minikube service [service name] --url

这条命令能够打印出本地集群中配好的Service的访问url地址。
minikube dashboard

这会跳转到一个web-based的集群看板页面,帮助你通过可视化的方式了解集群运行的状况。

同时我建议将运行minikube的默认的VM配置加高一点,比如配置:4CPUs与8GB内存,使用VMWare Fusion 作为VM容器,例如可以使用如下命令:
minikube config set cpus 4
minikube config set memory 8192
minikube start --vm-driver vmwarefusion

如果要让kubectl退出对minikube的访问,必须重新配置kubectl让它连接到新的集群;如下命令:
kubectl config get-contexts
kubectl config use-context [cluster context name from above] 

当然,如果要重新连接到本地的minikube,只需要使用这条命令:
kubectl config use-context minikube

Service的配置

Service的配置文件支持JSONYAML,但是我推荐使用YAML,因为它比较容易读写,而且支持注释,这对那些复杂的结构非常管用。

对于集群的配置,Service往往是最先配置的,这就是所谓的Service-first配置法。具体的做法是,为每个要创建的服务分配一个单独的文件夹,这个文件夹中包含了所有启动这个服务的配置文件。为了搜索方面,我建议为每个Kubernetes的Service资源创建一个yaml配置文件,可以这样命名:[service name]-k8s.yml,这个配置文件中写入所有这个Service在Kubernetes环境启动的配置项。

如下是我配置一个Redis缓存服务的目录结构:
services/
redis-cache/
        Dockerfile
        redis.conf
        redis-cache-k8s.yml

以下是服务配置文件redis-cache-k8s.yml:
apiVersion: v1
kind: Service
metadata:
name: redis-cache
labels:
role: cache
spec:
type: NodePort
ports:
- port: 6379
targetPort: 6379
selector:
role: cache

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis-cache
spec:
# https://github.com/kubernetes/kubernetes/issues/23597
revisionHistoryLimit: 3
replicas: 1
template:
metadata:
  labels:
    role: cache
spec:
  containers:
  - name: redis
    image: redis:3
    resources:
      requests:
        cpu: 100m
        memory: 1Gi
    ports:
      - containerPort: 6379

然后通过运行apply -f service/redis-cache/redis-cache-k8s.yml 命令就能让redis-cache服务在整个Deployment中跑起来。

容器与注册中心

如何为容器打Tag

我现在为每个容器都打两个tag分别是::latest与对应代码git库中的HEAD指针Commit的hash值如::9c713a。很多人强烈推荐不要使用:latest tag,原因在这里(注:内容大概讲docker registry并不会主动判断一个镜像是否是最新的,而是可以人为的随意为一个老的镜像打上latest tag的,所以很多人认为latest标签的镜像其实并不一定是最新的),但是我觉得这些都是讲给那些只用latest标签的人听的。我为什么这么做的原因在上面“部署新镜像”一节已经阐述。

确保Minikube能访问GKE的Registry

我早期使用minikube遇到的问题是如何让我的Pod有权限从我的Google私有镜像仓库中拉取docker镜像。当然,当我在GKE中使用Kubernetes集群拉取镜像是没有问题的,因为当使用GKE时,所有的服务器都自动被赋予了访问Google私有镜像仓库的的权限,但是作为本地运行的minikube就没有权限了。

解决方法是在Pod的spec节中使用imagePullSecrets值。首先,登录Google Cloud,然后前往IAM并创建一个新的Service Account 并赋予Storage -> Storage Object Viewer权限,确保勾选“Furnish a new private key”选项,完事后会给你一个JSON文件,这个文件你需要本地保存,是用于授权的;所有这些准备就绪后运行这段脚本生成一个新的Secret资源:
#!/usr/bin/env sh

SPATH="$(cd $(dirname "$0") && pwd -P)"
SECRET_NAME=${1:-docker-registry-secret}
CONFIG_PATH=${2:-$SPATH/localkube.json}
if [[ ! -f $CONFIG_PATH ]]; then
echo "Unable to locate service account config JSON: $CONFIG_PATH";
exit 1;
fi

kubectl create secret docker-registry $SECRET_NAME  \
--docker-server "https://gcr.io" \
--docker-username _json_key \
--docker-email [service account email address] \
--docker-password="`cat $CONFIG_PATH`" ${@:3} 

这段脚本会生成一个名为docker-registry-secret的Secret资源,这个资源稍后要在Service的配置文件中的imagePullSecrets 值中被引用,如下所示:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis-cache
spec:
...
template:
spec:
  imagePullSecrets:
  - name: docker-registry-secret
  ...
  containers:
  - name: redis
    image: gcr.io/[google account id]/redis-cache:latest
...

配置完成后应该就能从Google私有镜像仓库中拉取镜像了。

Secrets资源

敏感的数据比如:密码,认证码与各种key,这些都是要做特殊处理并很容易出错的数据。首先,如果将这些数据不加密的保存在代码管理仓库中,不管这个仓库多么机密,并不是一个保险的做法;但同时,你又必须把这些数据加密并保存在某个安全的地方,并使用一个跟踪系统对其做版本记录与权限控制。我使用了很多不同的加密、保存方式,发现StackExchange的BlackBox非常好用。

BlackBox使用PGP/GPG(非对称加密)加密方式对文件进行加密,确保只有特定的用户才能访问加密的文件。对一个用户授权访问某个资源只需这个用户提供自己的GPG公钥,而移除一个用户的授权只需要在一些配置文件中将其名字去掉即可;然后你要做的事就是告诉BlackBox哪些文件需要加密,而其余的工作交给BlackBox即可,被加密的配置文件可以放心的放入git仓库了。

将这些加密的信息提供给Kubernetes需要一些额外的本地脚本,因为Kubernetes将配置信息以明文的方式存储在etcd中。比如:我会对两类文件进行加密:1、YAML配置文件,内部包含很多key-value形式保存的敏感信息(如:数据库密码等);2、一些公钥文件如:SSL Certificate或者其他秘钥。然后,我使用rake来解密这些文件,然后将解密的文件应用到Kubernetes集群中,使用命令:apply -f -(注意:'-'表示STDIN)。

比如,我有个加密的YAML文件,如下:
---
rails:
secret_key_base: "..."
service_api_key: "..."
database:
username: "..."
password: "..."

然后导入Kubernetes的Secret:
raw_secrets = `blackbox_cat secrets/my-secrets.yml.gpg`

secrets = YAML.load(raw_secrets)

secrets.each do |name, values|
k8s_secret = {
"apiVersion" => "v1",
"kind" => "Secret",
"type" => "Opaque",
"metadata" => { "name" => name },
"data" => {},
}

values.each do |key, value|
k8s_secret["data"][key] = Base64.strict_encode64(value)
end

stdout, status = Open3.capture2("kubectl apply -f -", stdin_data: k8s_secret.to_yaml)
end 

然后在Service的配置文件中来引用这些Secret(使用Secret名称,我通常使用环境变量来指定):
...
env:
- name: RAILS_SECRET_KEY_BASE
valueFrom:
  secretKeyRef:
    name: rails
    key: secret_key_base
- name: RAILS_SERVICE_API_KEY
valueFrom:
  secretKeyRef:
    name: rails
    key: service_api_key
- name: DATABASE_USERNAME
valueFrom:
  secretKeyRef:
    name: database
    key: username
- name: DATABASE_PASSWORD
valueFrom:
  secretKeyRef:
    name: database
    key: password

我认为这是我使用、配置Kubernetes过程中遇到的主要问题,希望对您有帮助。

原文链接:CONTAINERS, DOCKER, AND KUBERNETES PART 3(翻译:肖劲)

0 个评论

要回复文章请先登录注册