上一回,我们搭建了kubernetes集群,现在我们使用kubernetes集群发布第一个容器化应用。在这之前,我们要制作容器的镜像。
使用kubernetes的必备技能:编写配置文件(YAML)。
Kubernetes跟docker项目不同,它不建议我们使用命令行的方式运行容器,而是编写yaml文件,把容器的定义、参数、配置,统统记录在一个yaml文件中,然后用这样一句指令把它运行起来:
kubectl create -f 配置文件
配置文件例子:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
kubernetes负责把这些对象所定义的容器或其他资源创建出来。
其中,deployment是一个定义多副本(上面时两个副本)应用的对象,负责在pod定义发生变化时,对每个副本进行更新。上面定义了一个pod模版,镜像时nginx:1.7.9,监听端口时80。
pod就是kubernetes世界里的“应用”;而一个应用,可以由多个容器组成。
每个API对象都有一个叫作Metadata的字段,这个字段就是API对象的标识,即元数据,它也是我们从kubernetes里找到这个对象的主要依据。这其中最主要使用到的字段是Labels。而像deployment这样的控制器对象,就可以通过这个labels字段从kubernetes中过滤出它所关心的被控制对象。
比如,在这个YAML中,deployment会把所有正在运行的、携带"app:nginx"标签的pod识别为被管理的对象,并确保这些pod的总数严格等于两个。
spec.selector.matchlabels字段,我们一般称为:Label Selector。
一个 Kubernetes 的 API 对象的定义,大多可以分为metadata和spec两个部分。前者存放的是这个对象的元数据,对所有API对象来说,这一部分的字段和格式基本上是一样的;而后者存放的,则是属于这个对象独有的定义,用来描述它所要表达的功能。
可以使用kubectl create指令完成这个操作:
kubectl create -f nginx-deployment.yaml
然后,通过kubectl get 命令检查这个YAML运行起来的状态是不是与我们预期的一致:
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deployment-67594d6bf6-9gdvr 1/1 Running 0 10m
nginx-deployment-67594d6bf6-v6j7w 1/1 Running 0 10m
在命令行中,所有key-value格式,都使用"="表示。kubectl describe查看API对象的详情
$ kubectl describe pod nginx-deployment-67594d6bf6-9gdvr
Name: nginx-deployment-67594d6bf6-9gdvr
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: node-1/10.168.0.3
Start Time: Thu, 16 Aug 2018 08:48:42 +0000
Labels: app=nginx
pod-template-hash=2315082692
Annotations: <none>
Status: Running
IP: 10.32.0.23
Controlled By: ReplicaSet/nginx-deployment-67594d6bf6
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned default/nginx-deployment-67594d6bf6-9gdvr to node-1
Normal Pulling 25s kubelet, node-1 pulling image "nginx:1.7.9"
Normal Pulled 17s kubelet, node-1 Successfully pulled image "nginx:1.7.9"
Normal Created 17s kubelet, node-1 Created container
Normal Started 17s kubelet, node-1 Started container
在kubernetes执行过程中,对API对象的所有重要操作,都会被记录在这个对象的events里,并且显示在kubectl describe指令返回的结果里。这部分是我们将来进行debug的重要依据。如果有异常发生,你一定要第一时间查看这些events,往往可以看到非常详细的错误信息。
如果要对这个nginx服务进行升级,把它的镜像从1.7.9升级为1.8,要怎么做?
只需要修改YAML文件即可。
...
spec:
containers:
- name: nginx
image: nginx:1.8 # 这里被从 1.7.9 修改为 1.8
ports:
- containerPort: 80
可是,这个修改目前只发生在本地,如何让这个更新在kubernetes里也生效呢?
$ kubectl replace -f nginx-deployment.yaml
不过,推荐使用kubectl apply命令,来统一进行kubernetes对象的创建和更新操作。
$ kubectl apply -f nginx-deployment.yaml
# 修改 nginx-deployment.yaml 的内容
$ kubectl apply -f nginx-deployment.yaml
作为用户,我们不必关心当前的操作是创建还是更新,你执行的命令都是kubectl apply,kubernetes会根据配置文件里的内容自动进行具体的处理。
所以说,如果通过容器镜像,我们能够保证应用本身在开发与部署环境里的一致性的话,那么现在,kubernetes项目通过这些YAML文件,就保证了应用的“部署参数”在开发与部署环境中的一致性。
而当应用本身发生变化时,开发人员和运维人员可以依靠容器镜像来进行同步;当应用部署参数发生变化时,这些YAML文件就是他们相互沟通和信任的媒介。
接下来,我们再在这个deployment中尝试声明一个volume。
在kubernetes中,volume是属于pod对象的一部分。所以,我们就需要修改这个YAML文件里的template.spec字段
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: nginx-vol
volumes:
- name: nginx-vol
emptyDir: {}
可以看到,我们在deployment的pod模版部分添加了volumes字段,定义了这个pod声明的所有volume。它的名字叫作nginx-vol,类型时emptyDir。
emptyDir:
它其实就等同于我们之前讲过的docker的隐式volume参数,不显式声明宿主机目录的volume。所以,kubernetes也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的volume目录上。
备注:kubernetes的emptyDir类型,只是把kubernetes创建的临时目录作为volume的宿主机目录,交给了docker。这么做的原因,是kubernetes不想依赖docker自己创建的那个_data目录。
而pod中的容器,使用的是volumeMounts字段来声明自己要挂载那个Volume,并通过mountPath字段来定义容器内的Volume目录,比如:/usr/share/nginx/html。
当然,kubernetes也提供了显示的volume定义,它叫作hostPath。比如下面这个YAML文件:
...
volumes:
- name: nginx-vol
hostPath:
path: /var/data
这样,容器Volume挂载的宿主机目录,就变成了/var/data。
在上述修改完成后,我们还是使用kubectl apply指令,更新这个Deployment:。
$ kubectl apply -f nginx-deployment.yaml
接下来,可以通过kubectl get 指令,查看两个pod被逐一更新的过程:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5c678cfb6d-v5dlh 0/1 ContainerCreating 0 4s
nginx-deployment-67594d6bf6-9gdvr 1/1 Running 0 10m
nginx-deployment-67594d6bf6-v6j7w 1/1 Running 0 10m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5c678cfb6d-lg9lw 1/1 Running 0 8s
nginx-deployment-5c678cfb6d-v5dlh 1/1 Running 0 19s
从结果看,新旧两个pod,被交替创建、删除,最后剩下的就是最新版本的pod。使用kubectl describe查看最新的pod,就会发现volume信息已经出现在了container里:
...
Containers:
nginx:
Container ID: docker://07b4f89248791c2aa47787e3da3cc94b48576cd173018356a6ec8db2b6041343
Image: nginx:1.8
...
Environment: <none>
Mounts:
/usr/share/nginx/html from nginx-vol (rw)
...
Volumes:
nginx-vol:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
最后,可以使用kubectl exec 进入到这个pod中(即容器的namespace中)查看这个volume目录:
$ kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash
# ls /usr/share/nginx/html
此外,你想要从kubernetes集群中删除这个nginx deployment的话,执行:
$ kubectl delete -f nginx-deployment.yaml
总结
kubernetes里“最小”的API对象是pod。pod可以等价为一个应用,所以,pod可以由多个紧密协作的容器组成。
在kubernetes中,我们经常会看到它通过一种API对象来管理另一种API对象,这种组合方式,正是kubernetes进行容器编排的重要模式。
每个kubernetes API对象,往往由Metadata和Spec两部分组成,其中Metadata里的Labels是kubernetes过滤对象的重要手段。
容器要想使用数据卷,volume,正是pod的spec字段的一部分。而pod里的每个容器,则需要显式的声明自己要挂载那个volume。
如果你想要快速熟悉kubernetes,流程:
1)首先,在本顶通过docker测试代码,制作镜像;
2)然后,选择合适的API对象,编写对应YAML文件;
3)最后,在kubernetes上部署这个YAML文件。
注意:以后通过YAML文件来实现所有操作,尽量不要使用docker的命令运行了。