一个小时学会搭建和使用 kubernetes

本文是一个面向 kubernetes 初学者的教程,是我作为一名开发者在开始使用和理解 kubernetes 一点经验总结,它包含两部分,第一部分介绍使用 kubeadm 搭建一个 kubernetes 集群,第二部分使用 kubernetes 部署一个包含多个组件的应用程序,通过这两部分初学者基本能够理解 kubernetes 的组成以及应用。

作为一个开发者,为什么要了解 kubernetes?因为 kubernetes 做到了让应用的开发、部署、运维整个流程无缝连接,应用的整个生命周期管理从未像现在这样如此贴近一个开发者,对此我只能说很爽很强大。为了简述方便,下文把 kubernetes 统称为 k8s。

第一部分,使用 kubeadm 搭建 k8s

k8s 的安装很复杂,搭建高可用的 k8s 集群尤其复杂,本文介绍的 k8s 安装方式是基于官方的工具 kubeadm 完成的,虽然 kubeadm 现在还在开发当中,相信不久的将来 kubeadm 会变成一个真正构建 k8s 集群的标准工具。

1. 机器准备

准备相互连通的三台 linux ,虽然 kubeadm 支持在单节点部署一个 k8s 集群,但是为了能做到真正演示 k8s 集群工作原理,建议使用多个节点。这里我使用的三台云服务机器,基于 Ubuntu 16.04,这也是基于 Ubuntu linux 搭建 k8s 的最低版本。这里三台机器分别是 ubuntu@node1、ubuntu@node2、ubuntu@node3,并且关闭三个节点的 swap。

2. 准备 docker

k8s 本质上一个容器编排工具,所以容器是 k8s 运作的前提,k8s 支持各种容器运行时,这里我们使用 docker。在 Ubuntu 上安装 docker 参见官方文档 https://docs.docker.com/install/linux/docker-ce/ubuntu/

由于 docker 官方镜像在国内访问特别的慢,docker 环境准备完成之后请配置国内的加速镜像:

1
2
3
4
5
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://registry.docker-cn.co"]
}
EOF

如果提示权限不足,请使用 sudo 命令或 root 用户。

3. 安装 k8s 的基础组件

安装 k8s 的最低要求见 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#before-you-begin :

  • 要求 2CPU 和 2 GB
  • 不能启用 swap
  • 外网可通

任选一台机器作 k8s master 节点,后面所有的安装如果没有特殊标明都在 master 上执行,使用 sudo 命令或 root 用户依次执行下面的命令完成以下各个组件的安装,这里我们使用阿里云的 k8s 源进行安装:

1
2
3
4
5
6
7
8
9
10
11
apt-get update && apt-get install -y apt-transport-https
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl


systemctl daemon-reload
systemctl restart kubelet

这里简单介绍一下这三个组件的作用:

  • kubelet 是 work node 节点负责 Pod 生命周期状态管理以及和 master 节点交互的组件
  • kubectl k8s 的命令行工具,负责和 master 节点交互
  • kubeadm 搭建 k8s 的官方工具

4. 安装 k8s 依赖的各种镜像

使用 root 执行下面命令

1
kubeadm init --image-repository=gcr.akscn.io/google_containers --pod-network-cidr=192.168.0.0/16

这里我们通过 --image-repository 指定了一个国内的镜像,因为 google 官方镜像被国内无法访问,并且指定了 --pod-network-cidr 网络情况,这是因为后面我们的组件使用的是 Calico这个网络组件,所有这些都不用去在乎它们是什么,只要先按部就班的安装。

安装完成之后会有如下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
....
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.10.76.89:6443 --token t51nk2.4rtw6twkmca53nbu \
--discovery-token-ca-cert-hash sha256:8356397a4ccd20b2d6506130e8bb4ad13ccc3fe33302296d3c48651601be5bfd

按照提示你应该再继续执行以下操作:

  • 让 k8s 的命令可以在非 root 用户下执行
  • 安装 k8s 的网络组件

所以你要记住上面输出的提示操作,找个地方把他们存下来。

5. 让非 root 用户可以使用 k8s

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

为了能够在 worker 节点也可以使用 kubectl 命令,建议再 worker node 加入集群之后,把 $HOME/.kube/config 文件也复制一份到 worker node 节点。

6. 安装 Calico 网络组件

k8s 的网络组件非常多,为了演示方便我们使用 Calico:

1
2
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml

安装完成之后检查 corndns :

1
kubectl get pods --all-namespaces

确保所有的 STATUS 都是 running 说明安装成功

7. 加入 worker node

在上面 kubeadm init 命令的输出中有加入节点的命令:

1
2
3

kubeadm join 10.10.76.89:6443 --token t51nk2.4rtw6twkmca53nbu \
--discovery-token-ca-cert-hash sha256:8356397a4ccd20b2d6506130e8bb4ad13ccc3fe33302296d3c48651601be5bfd

这个 token 是必须的,而且 token 的有效期是 24 小时,分别在其它被选为 work node 的节点执行这个加入命令,token 的获取和使用参考 https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-token/

执行完成之后,在 master 节点执行 kubectl get nodes,会看到如下输出,说明其他节点加入成功:

1
2
3
4
NAME           STATUS   ROLES    AGE    VERSION
10-10-158-97 Ready <none> 2d2h v1.14.1
10-10-23-227 Ready <none> 2d2h v1.14.1
10-10-76-89 Ready master 2d2h v1.14.1

查看组成 k8s 的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl get pods --namespace=kube-system 
NAME READY STATUS RESTARTS AGE
calico-node-9vrt4 2/2 Running 0 2d22h
calico-node-rklrf 2/2 Running 0 2d22h
calico-node-wb7ln 2/2 Running 0 2d22h
coredns-9846cf96-dblrm 1/1 Running 2 2d22h
coredns-9846cf96-x7z7l 1/1 Running 2 2d22h
etcd-10-10-76-89 1/1 Running 2 2d22h
kube-apiserver-10-10-76-89 1/1 Running 0 2d22h
kube-controller-manager-10-10-76-89 1/1 Running 0 2d22h
kube-proxy-2nbm6 1/1 Running 0 2d22h
kube-proxy-vvdpk 1/1 Running 0 2d22h
kube-proxy-whjqt 1/1 Running 0 2d22h
kube-scheduler-10-10-76-89 1/1 Running 0 2d22h

可以看到 k8s 主要有以下核心组组件组成:

  • etcd 保存了整个集群的状态;
  • apiserver 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
  • controller manager 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
  • scheduler 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
  • kubelet 负责维护容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理;
  • Container runtime 负责镜像管理以及 Pod 和容器的真正运行(CRI);
  • kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡

第二部分,使用 k8s 部署一个完整应用

我们将使用刚才搭建的 k8s 集群部署一个完整的应用,这个应用包含三个组件:一个用 ember 构建的前端 APP,一个用 Python 构建的后端 APP,一个 redis 用作存储节点,组成如下:

为了演示 k8s 强大的能力,在这个应用中,三个 APP 彼此是不知道对方的存在的,也就是我们需要让三个 APP 通过网络能够相互发现对方并取得连接,这样即使他们的 IP 发生变动也不会影响他们之间的交互。

1. k8s 的基础概念解释

k8s 是通过一个个声明式的资源对象来完成对应用管理的,为了能够覆盖完整的应用生命周期管理,k8s 提供了很多资源对象:

  • Autoscaling (HPA)
  • ConfigMap
  • CronJob
  • CustomResourceDefinition
  • DaemonSet
  • Deployment
  • Ingress
  • Job
  • LocalVolume
  • Namespace
  • NetworkPolicy
  • Node
  • PersistentVolume
  • Pod
  • PodPreset
  • ReplicaSet
  • Resource Quota
  • Secret
  • SecurityContext
  • Service
  • ServiceAccount
  • StatefulSet
  • Volume

在这个演示中我们要用到以下几种资源:

Pod

Pod 是一组紧密关联的容器集合,它们共享 IPC、Network 和 UTS namespace,是 Kubernetes 调度的基本单位。Pod 的设计理念是支持多个容器在一个 Pod 中共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。可以把 Pod 理解为共享资源的一个或一组进程

Deployment

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,借用 Deployment 可以完成:

  • 创建 Pod 和 ReplicaSet
  • 扩容和缩容
  • 暂停和继续 Deployment

Namespace

Namespace 是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组,默认的 Namespace 是 default。

Service

Service 是对一组提供相同功能的 Pods 的抽象,并为它们提供一个统一的入口。借助 Service,应用可以方便的实现服务发现与负载均衡,并实现应用的零宕机升级。Service 通过标签来选取服务后端,一般配合 Deployment 来保证后端容器的正常运行。

2. 创建一个 front-end Pod 尝鲜

为了演示方便,我已经在仓库 https://github.com/zhyq0826/kubernetes-tutorial-app 中创建好了 Pod,cd 进入 kubernetes-tutorial-app/step-one,获取我们要创建的 Pod。 k8s 采用声明式的定义,这样方便维护而且容易追溯。

启动一个 Pod 非常简单:

1
kubectl apply -f ember-app-pod.yaml

然后观察 Pod 的启动情况:

1
kubectl get pods

如果能看到容器的状态是 running 说明正确启动了。

创建 Pod 如此简单,我们看一下如何定义 Pod:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod # 创建的资源类型 pod
metadata:
name: ember-app # 资源名称
labels:
app: ember-app # pod 标签 用在 service 中进行筛选
spec:
containers:
- image: zhyq0826/nginx:k8s-ember-app # 镜像名称 来自 docker hub 公开镜像
name: ember-app # pod container 的名称
ports:
- containerPort: 80 # 容器监控 port

这个文件定义了一个 Pod, 这个 Pod 中包含了一个容器,是一个已经做好的 nginx 镜像,这里打包了我们要演示的 front-app,需要注意的是这里我们为 Pod 打上了一个 label: app=ember-app ,这个 label 将在下一步我们创建 service 的时候派上用场。

这个容器并不能访问,容器只在它内部通过 nginx 监听了 80 端口,如果想让容器可以外部访问,我们需要把容器的端口映射到外部,比如可以像这样:

1
kubectl port-forward ember-app 88:80

这个命令的意思把 Pod ember-app 的 80 端口和本机的 88 端口进行映射,然后再通过下面的命令访问容器内的应用:

1
curl localhost:88

3. 通过 Service 访问 Pod

在 2 中我们创建了一个 Pod,为了演示 Service 作为负载均衡的特性,我们再创建一个 Pod:

1
kubectl apply -f ember-app-pod2.yaml

这样我们就有两个 Pod 了,这两个 Pod 除了名字不同以外,其他都是一样的。

现在我们创建一个 Service 用来访问刚才我们创建的两个 Pod:

1
kubectl create -f ember-app-service.yaml

这两个文件可以在 kubernetes-tutorial-app/step-one/ 中找到。

创建 Service 之后我们查看一下 Service:

1
2
kubectl get service ember-app-service
ember-app-service LoadBalancer 10.101.206.188 <pending> 80:30537/TCP 21h

Service 成功启动,然后用 curl 检查我们的服务 curl 10.101.206.188 能够访问说明我们的服务启动成功了。

Service 的作用就是生成一个 proxy 可以动态根据 Pod 的 label 路由的对应的 Pod,查看 Service 的文件定义可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service # 资源类型
metadata:
name: ember-app-service
spec:
type: LoadBalancer # service 类型 有 4 种类型
ports:
- port: 80 # service 监听端口 service 类型 是 loadBalance 会在 node 节点暴露一个 port 和 此处的 port 不同 随机的
protocol: TCP # 协议
targetPort: 80 # 目标端口
selector:
app: ember-app # label 定义的 service 根据 app: ember-app 这个 label 寻找 pod

我们指定了 Service 的 selector 和 Pod 的 label 一样,这是 Service 能够正确访问两个 Pod 的关键,Service 根据 selector 提供的 label 来过滤具有相同 label 的 Pod。

4. 使用 Deployment 部署 Pod

虽然可以直接使用 Pod 部署应用,但并不是推荐的方式,因为 Pod 自己无法扩容,无法在故障时自动转移,为此 k8s 提供了 Deployment 来实现 Pod 的高可用部署。

在 Deployment 中可以根据自己的需要配置 Pod 的数量,Pod 的升级方式,Pod 故障转移方式等等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: extensions/v1beta1
kind: Deployment # 资源类型
metadata:
name: ember-app-deployment
spec:
replicas: 2 # pod 副本数量
minReadySeconds: 15
strategy:
type: RollingUpdate # 部署策略 滚动升级
rollingUpdate:
maxUnavailable: 1 # 在升级过程中最多允许几个 pod 不可用
maxSurge: 1 # 升级过程中允许最多添加几个 pod, 如果 副本是 2 升级过程中要保证可用一直是 2 必须要再加一个 pod
template: # 定义 deploy 使用 pod
metadata:
labels:
app: ember-app # 模板定的 pod 的 label 根据这个 label 生成 service
spec: # spec 相当于 step-one 中的 pod
containers:
- image: zhyq0826/nginx:k8s-ember-app
imagePullPolicy: Always # 镜像策略
name: ember-app
ports:
- containerPort: 80

进入 kubernetes-tutorial-app/step-two 目录,然后创建 Deployment:

1
kubectl apply -f ember-app-deployment.yaml

查看 Pod 可以看到这个 Deployment 启动了两个 Pod:

1
2
3
4
kubectl get pods
NAME READY STATUS RESTARTS AGE
ember-app-deployment-f4cb9594b-9xpzz 1/1 Running 0 1m
ember-app-deployment-f4cb9594b-s8bvm 1/1 Running 0 1m

5. 构建完整的应用

到目前为止,你已经知道了如何使用 Deployment 创建高可用的应用,并且知道用 Service 来为 Pod 提供供内部集群访问的能力,接下来我们就把我们要部署的后端应用全部部署。

进入 kubernetes-tutorial-app/step-three 可以看到:

1
2
3
4
5
6
7
.
├── ember-app-deployment.yaml
├── ember-app-service.yaml
├── python-app-deployment.yaml
├── python-app-service.yaml
├── redis-deployment.yaml
└── redis-service.yaml

可以看到这里我准备了三个 Deployment 和 三个 Service,分别对应着即将要部署的三个应用,并且在 python-app-deployment.yaml 中通过环境变量为 python-app 提供了 redis 的地址:

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
26
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: python-app-deployment
spec:
replicas: 2
minReadySeconds: 15
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: python-app
spec:
containers:
- image: zhyq0826/python:k8s-python-app
imagePullPolicy: Always
name: python-app
ports:
- containerPort: 5000
env:
- name: REDIS_HOST
value: redis-service.default.svc.cluster.local # 利用 k8s 提供的默认 dns 解析,发现 redis 服务

Redis 这个 redis-service.default.svc.cluster.local 服务发现功能是通过 k8s 提供的默认的 DNS 就系完成的,组成方式是 $service_name.$namespace.svc.cluster.local ,由于在没有指定 namespace 的时候,默认是 default。DNS 解析可以在 python-app Pod 中查看:

1
2
3
4
$ kubectl exec -it python-app-deployment-7b64859cd9-stjpt cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

可以看到对应的 DNS server 是 10.96.0.10 :

1
2
3
4
$ kubectl get service  --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
calico-typha ClusterIP 10.111.6.44 <none> 5473/TCP 2d13h
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 2d13h

正好是 kube-dns 的地址。

把目录中所有的 Deployment 和 Service 执行完毕,依次执行 kubectl apply -f *.yaml 即可,执行完毕之后,一个完整的应用就组成了。由于我们希望 front-app 可以被用户所访问,所以 ember-app-service 的网络类型是 NodePort,也就是在每个节点会启动一个监听端口把所有外部访问的请求转发到 Service 的监听端口,也就完成了 Service 可以被外部网络所访问。

而 ember-app 由于要访问 python-app ,作为一个前端应用他需要一个可以访问 python-app 的外部地址,为了可以让 ember-app 总是能访问到 python-app 我们在 python-app-service.yaml 中指定主机要绑定的端口 NodePort 32500,这里为了演示方便直接让 python-app 暴露出来,生成环境会把 python-app 挂在 nginx 这种反向代理后面:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service # 资源类型
metadata:
name: python-app-service
spec:
type: NodePort # service 类型 有 4 种类型
ports:
- port: 5000 # service 监听端口 集群内部访问
protocol: TCP # 协议
targetPort: 5000 # 目标端口
nodePort: 32500 # 暴露给外部访问 The range of valid ports is 30000-32767
selector:
app: python-app # label 定义的 service 根据 app: python-app 这个 label 寻找 pod%

这样 ember-app 中就可以通过这个固定的端口访问到 python-app 了:

1
2
3
$.post(`${window.location.protocol}//${window.location.hostname}:${config.backendport}`+'/login', {"username": this.username}, (data)=>{
this.set('msg', `${data.name} ${data.msg} ${data.count}`)
})

最后让我们看一眼我们的应用:

这个应用的功能很简单,每次把用户的登陆次数加一存储在 redis 中,并且显示到前端。

6. 简单回顾

k8s 让应用的整个部署和维护变得非常简单,而且是第一次让一个开发可以如此便捷的部署自己的应用程序,k8s 的安装如果没有 kubeadm 这样的工具将非常复杂,生成环境使用一定要搭建高可用的 k8s 集群,也就是由多个 etcd 节点和多个 master 节点组成,详细可参考官网 https://kubernetes.io/docs/setup/independent/high-availability/

k8s 使用声明式的 API 来管理和创建各种资源对象,相比命令式的更容易维护和变更。

参考文章:

文中涉及的应用完整代码见 https://github.com/zhyq0826/kubernetes-tutorial-app 你可以在你的 k8s 集群中使用他们。

文中 k8s 集群基于 v1.14.1 版本。

三月沙 wechat
扫描关注 wecatch 的公众号