Kubernets

安装 Kubernetes

介绍完 Docker,终于可以开始试试 Kubernetes 了。我们另外安装两样东西:Kubernetes 的命令行客户端 kubctl、一个可以在本地跑起来的 Kubernetes 环境 Minikube。

Install Kubernetes Client (kubectl):

Install and Set Up kubectl on Linux

minikube install:

Install Minikube,需要注意的是minikube的启动限制使用root账户,你需要切换到具有docker权限的非root账户启动

Add new User

1
2
3
4
adduser developer
# password@7
usermod -aG sudo developer
su - developer

Login to the newly created User

1
2
su - developer
# password@7

Add User to the Docker Group

1
2
3
sudo groupadd docker
sudo usermod -aG docker $USER
- Re-Login or Restart the Server

Install Minicube

1
2
3
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
chmod +x minikube
mv ./minikube /usr/local/bin/minikube

Start minikube with Docker Driver

1
minikube start --driver=docker

Verify minikube Installation

1
docker ps

MacOS安装:

1
2
3
4
5
$ brew install --cask docker 
$ open /Applications/Docker.app
$ brew install kubectl
$ brew install minikube
$ minikube start --driver=docker --image-mirror-country cn --registry-mirror="https://a90tkz28.mirror.aliyuncs.com" --image-repository="registry.cn-hangzhou.aliyuncs.com/google_containers"

Open the Kubernetes dashboard in a browser:

1
minikube dashboard

Kubernetes 架构简介

典型的 Kubernetes 集群包含一个 master 和多个 node。

Master :控制集群的中心,Master 上运行着很多进程,包括面向用户的 API 服务、负责维护集群状态的 Controller Manager、负责调度任务的 Scheduler 等

**节点(Node):**这些设备负责执行由控制平面分配的请求任务。node 是提供 CPU、内存和存储资源的节点。每个 node 上运行着维护 node 状态并和 master 通信的 kubelet,以及实现集群网络服务的 kube-proxy。

作为一个开发和测试的环境,Minikube 会建立一个有一个 node 的集群,用下面的命令可以看到:

1
2
3
$ kubectl get nodes
NAME STATUS AGE VERSION
minikube Ready 1h v1.10.0

容器集(Pod):
部署到单个节点上且包含一个或多个容器的容器组。容器集是最小、最简单的 Kubernetes 对象。

1
2
3
4
5
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-minikube-6ddfcc9757-cqxc6 0/1 ImagePullBackOff 0 5m38s
$ kubectl delete deployment jenkins2 -n jenkins #删除
$ kubectl delete deployment --all #删除所有deployment

服务(Service):
一种将一组容器集上运行的应用开放为网络服务的方法。它会把工作定义与容器集分离。

**Deployment **:

检查 Pod 的健康状况,并在 Pod 中的容器终止的情况下重新启动新的容器。 Deployment 是管理 Pod 创建和扩展的推荐方法。

1
2
3
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
hello-minikube 0/1 1 0 43m

kubectl cheatsheet

  1. 命令格式
    kubectl get resource_name
常见resource_name 描述
all
certificatesigningrequests (缩写 csr)
clusterrolebindings
clusterrol
componentstatuses (缩写 cs)
configmaps (缩写 cm)
controllerrevisions
cronjobs
customresourcedefinition (缩写 crd)
daemonsets (缩写 ds)
deployments (缩写 deploy)
endpoints (缩写 ep)
events (缩写 ev)
horizontalpodautoscalers (缩写 hpa)
ingresses (缩写 ing)
jobs
limitranges (缩写 limits)
namespaces (缩写 ns)
networkpolicies (缩写 netpol)
nodes (缩写 no)
persistentvolumeclaims (缩写 pvc)
persistentvolumes (缩写 pv)
poddisruptionbudgets (缩写 pdb)
podpreset
pods (缩写 po)
podsecuritypolicies (缩写 psp)
podtemplates
replicasets (缩写 rs)
replicationcontrollers (缩写 rc)
resourcequotas (缩写 quota)
rolebindings
roles
secrets
serviceaccounts (缩写 sa)
services (缩写 svc)
statefulsets (缩写 sts)
storageclasses (缩写 sc)
  1. 其它可选参数
    用不同的格式查看:-o wide/yaml/json
    看指定标签的pods,支持=, ==, and !=操作符 -l key=value

  2. 举几个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看Master状态
$ kubectl get componentstatuses
# 查看所有命名空间
$ kubectl get namespace
# 列出所有的pods
$ kubectl get pods
# 显示更多的pods列表信息(例如 pod的ip和所处的node)
$ kubectl get pods -o wide
# 列出名字为web的rc
$ kubectl get replicationcontroller web
# 获取名字为web-pod-13je7的pod的信息,并以json格式输出
$ kubectl get -o json pod web-pod-13je7
# 根据pod文件查找pod,并以json格式输出
$ kubectl get -f pod.yaml -o json
# 获取pod容器的状态
$ kubectl get -o template pod/kube-dns-795f5f6f9c-ldxxs --template {{.status.phase}}
# 同时获取所有的rc和service
$ kubectl get rc,services
# 获取符合条件的所有rc,svc,pod
$ kubectl get rc/web service/frontend pods/web-pod-13je7
# 获取所有resource
$ kubectl get all

部署一个单实例服务

我们先尝试像文章开始介绍 Docker 时一样,部署一个简单的服务。Kubernetes 中部署的最小单位是 pod,而不是 Docker 容器。实时上 Kubernetes 是不依赖于 Docker 的,完全可以使用其他的容器引擎在 Kubernetes 管理的集群中替代 Docker。在与 Docker 结合使用时,一个 pod 中可以包含一个或多个 Docker 容器。但除了有紧密耦合的情况下,通常一个 pod 中只有一个容器,这样方便不同的服务各自独立地扩展。

Minikube 自带了 Docker 引擎,所以我们需要重新配置客户端,让 docker 命令行与 Minikube 中的 Docker 进程通讯:

1
$ eval $(minikube -p minikube docker-env)

在运行上面的命令后,再运行 docker image ls 时只能看到一些 Minikube 自带的镜像,就看不到我们刚才构建的 docker-demo:0.1 镜像了。所以在继续之前,要重新构建一遍我们的镜像,这里顺便改一下名字,叫它 k8s-demo:0.1。

1
$ docker build -t k8s-demo:0.1 .

然后创建一个叫 pod.yml 的定义文件:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: k8s-demo
spec:
containers:
- name: k8s-demo
image: k8s-demo:0.1
ports:
- containerPort: 80

这里定义了一个叫 k8s-demo 的 Pod,使用我们刚才构建的 k8s-demo:0.1 镜像。这个文件也告诉 Kubernetes 容器内的进程会监听 80 端口。然后把它跑起来:

1
2
$ kubectl create -f pod.yml
pod "k8s-demo" created

kubectl 把这个文件提交给 Kubernetes API 服务,然后 Kubernetes Master 会按照要求把 Pod 分配到 node 上。用下面的命令可以看到这个新建的 Pod:

1
2
3
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
k8s-demo 1/1 Running 0 5s

因为我们的镜像在本地,并且这个服务也很简单,所以运行 kubectl get pods 的时候 STATUS 已经是 running。要是使用远程镜像(比如 Docker Hub 上的镜像),你看到的状态可能不是 Running,就需要再等待一下。

虽然这个 pod 在运行,但是我们是无法像之前测试 Docker 时一样用浏览器访问它运行的服务的。可以理解为 pod 都运行在一个内网,我们无法从外部直接访问。要把服务暴露出来,我们需要创建一个 Service。Service 的作用有点像建立了一个反向代理和负载均衡器,负责把请求分发给后面的 pod。

创建一个 Service 的定义文件 svc.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: k8s-demo-svc
labels:
app: k8s-demo
spec:
type: NodePort
ports:
- port: 80
nodePort: 30050
selector:
app: k8s-demo

这个 service 会把容器的 80 端口从 node 的 30050 端口暴露出来。注意文件最后两行的 selector 部分,这里决定了请求会被发送给集群里的哪些 pod。这里的定义是所有包含「app: k8s-demo」这个标签的 pod。然而我们之前部署的 pod 并没有设置标签:

1
2
$ kubectl describe pods | grep Labels
Labels: <none>

所以要先更新一下 pod.yml,把标签加上(注意在 metadata: 下增加了 labels 部分):

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: k8s-demo
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo
image: k8s-demo:0.1
ports:
- containerPort: 80

然后更新 pod 并确认成功新增了标签:

1
2
3
4
$ kubectl apply -f pod.yml
pod "k8s-demo" configured
$ kubectl describe pods | grep Labels
Labels: app=k8s-demo

然后就可以创建这个 service 了:

1
2
$ kubectl create -f svc.yml
service "k8s-demo-svc" created

用下面的命令可以得到暴露出来的 URL,在浏览器里访问,就能看到我们之前创建的网页了。

1
2
$ minikube service k8s-demo-svc --url
http://192.168.64.4:30050

横向扩展、滚动更新、版本回滚

在这一节,我们来实验一下在一个高可用服务的生产环境会常用到的一些操作。在继续之前,先把刚才部署的 pod 删除(但是保留 service,下面还会用到):

1
2
$ kubectl delete pod k8s-demo
pod "k8s-demo" deleted

在正式环境中我们需要让一个服务不受单个节点故障的影响,并且还要根据负载变化动态调整节点数量,所以不可能像上面一样逐个管理 pod。Kubernetes 的用户通常是用 Deployment 来管理服务的。一个 deployment 可以创建指定数量的 pod 部署到各个 node 上,并可完成更新、回滚等操作。

首先我们创建一个定义文件 deployment.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-demo-deployment
spec:
replicas: 10
selector:
matchLabels:
app: k8s-demo
template:
metadata:
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo-pod
image: k8s-demo:0.1
ports:
- containerPort: 80

replicas: 10 指定了这个 deployment 要有 10 个 pod,后面的部分和之前的 pod 定义类似。提交这个文件,创建一个 deployment:

1
2
$ kubectl create -f deployment.yml
deployment "k8s-demo-deployment" created

用下面的命令可以看到这个 deployment 的副本集(replica set),有 10 个 pod 在运行。

1
2
3
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
k8s-demo-deployment-774878f86f 10 10 10 19s

假设我们对项目做了一些改动,要发布一个新版本。这里作为示例,我们只把 HTML 文件的内容改一下, 然后构建一个新版镜像 k8s-demo:0.2:

1
2
$ echo '<h1>Hello Kubernetes!</h1>' > html/index.html
$ docker build -t k8s-demo:0.2 .

然后更新 deployment.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-demo-deployment
spec:
replicas: 10
selector:
matchLabels:
app: k8s-demo
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo-pod
image: k8s-demo:0.2
ports:
- containerPort: 80

这里有两个改动,第一个是更新了镜像版本号 image: k8s-demo:0.2,第二是增加了 minReadySeconds: 10 和 strategy 部分。新增的部分定义了更新策略:minReadySeconds: 10 指在更新了一个 pod 后,需要在它进入正常状态后 10 秒再更新下一个 pod;maxUnavailable: 1 指同时处于不可用状态的 pod 不能超过一个;maxSurge: 1 指多余的 pod 不能超过一个。这样 Kubernetes 就会逐个替换 service 后面的 pod。运行下面的命令开始更新:

1
2
$ kubectl apply -f deployment.yml --record=true
deployment "k8s-demo-deployment" configured

这里的 --record=true 让 Kubernetes 把这行命令记到发布历史中备查。这时可以马上运行下面的命令查看各个 pod 的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl get pods
NAME READY STATUS ... AGE
k8s-demo-deployment-774878f86f-5wnf4 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-6kgjp 0/1 Terminating ... 7m
k8s-demo-deployment-774878f86f-8wpd8 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-hpmc5 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-rd5xw 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-wsztw 1/1 Running ... 7m
k8s-demo-deployment-86dbd79ff6-7xcxg 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-bmvd7 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-hsjx5 1/1 Running ... 26s
k8s-demo-deployment-86dbd79ff6-mkn27 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-pkmlt 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-thh66 1/1 Running ... 26s

从 AGE 列就能看到有一部分 pod 是刚刚新建的,有的 pod 则还是老的。下面的命令可以显示发布的实时状态:

1
2
3
4
$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out

由于我输入得比较晚,发布已经快要结束,所以只有三行输出。下面的命令可以查看发布历史,因为第二次发布使用了 --record=true 所以可以看到用于发布的命令。

1
2
3
4
5
$ kubectl rollout history deployment k8s-demo-deployment
deployments "k8s-demo-deployment"
REVISION CHANGE-CAUSE
1 <none>
2 kubectl apply --filename=deploy.yml --record=true

这时如果刷新浏览器,就可以看到更新的内容「Hello Kubernetes!」。假设新版发布后,我们发现有严重的 bug,需要马上回滚到上个版本,可以用这个很简单的操作:

1
2
$ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1
deployment "k8s-demo-deployment" rolled back

Kubernetes 会按照既定的策略替换各个 pod,与发布新版本类似,只是这次是用老版本替换新版本:

1
2
3
4
5
6
$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 4 out of 10 new replicas have been updated...
Waiting for rollout to finish: 6 out of 10 new replicas have been updated...
Waiting for rollout to finish: 8 out of 10 new replicas have been updated...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out

在回滚结束之后,刷新浏览器就可以确认网页内容又改回了「Hello Docker!」。

结语

我们从不同层面实践了一遍镜像的构建和容器的部署,并且部署了一个有 10 个容器的 deployment, 实验了滚动更新和回滚的流程。Kubernetes 提供了非常多的功能,本文只是以走马观花的方式做了一个快节奏的 walkthrough,略过了很多细节。虽然你还不能在简历上加上「精通 Kubernetes」,但是应该可以在本地的 Kubernetes 环境测试自己的前后端项目,遇到具体的问题时求助于 Google 和官方文档即可。在此基础上进一步熟悉应该就可以在别人提供的 Kubernetes 生产环境发布自己的服务。

References:

https://1byte.io/developer-guide-to-docker-and-kubernetes/

https://www.qikqiak.com/k8strain/examples/wordpress/

https://kubernetes.io/docs/tutorials/hello-minikube/