将你的应用部署在 K8S 上
Kubernetes(简称 K8S)自发布以来,已经成为容器化编排的最佳工具,本文通过一个简单的案例聊一聊如何将一个容器部署到 k8s 集群上,文中所需的代码和容器均在 GITHUB 上。如需要搭建一个 k8s 集群,可以通过 minikube 来搭建,也可以使用 docker for desktop 自带的 k8s 集群
1 创建一个最简单的 Deployment
先简单介绍几个概念
- Pod: k8s 最核心的一个概念,是一个或多个 container 的集合。一个 pod 中的 container 共享存储和网络,k8s 支持多种 contianer,但是目前 docker container 是最常见的一种。我们的应用最终都是各个 pod 的形式存在的,pod 也是 k8s 进行调度的最小单位。
- ReplicaSet: 也是 k8s 中的一种资源,管理一个 pod 的多个副本。一般我们部署 pod 为了保证高可用,都会部署多个副本,避免单点故障。如果 replicaSet 管理的某个 pod 挂掉了,它会请求一个新的 pod 出来以满足设定的副本数量。k8s 还有一个 ReplicaionController 也是控制 pod 副本的,但是相比 ReplicaionController,ReplicaSet 对 pod 标签的支持会更好,现在一般就用 ReplicaSet
- Deployment: 是一个更 high-level 的资源,创建一个 Deployment 后会创建一个 ReplicaSet,然后 ReplicaSet 会创建预期数量的 pod。deployment 可以对 ReplicaSet 的版本进行管理,方便回滚。在生产上一般都是创建一个 Deployment
我们先准备一个简单的 web 应用 simple-web,并将其打包成 docker 镜像 simple-web:1.0。web 应用后,访问 /index
,将会输出 web 应用的名字,访问 /health
,会返回 “OK”
1 | curl http://ip:8080/index |
定义一个 deployment.yml
如下
使用 kubectl create -f deployment.yml
,然后 get 资源可以发现我们创建了一个 deployment,一个 replicaset,一个 pod
1 | ➜ ~ k get deploy |
我们可以进入到 pod 中执行 curl 命令查看输出,也可以把 curl 命令传递给 kubectl exec
1 | ➜ ~ k exec simple-web-6bdf7b4d84-p7jkp -- curl -s http://localhost:8080/index |
2 使用 service 访问 pod
2.1 port-forward
仅仅是创建一个 Deployment,我们还只能在 pod 内访问自己的 web 服务,而无法在外部或者 pod 之间访问 web 服务。kubectl port-forward <podname> <hostPort:containerPort>
可以将宿主机的一个 port 代理到容器的一个 pod 上,使我们在宿主机上访问 web 服务
1 | ➜ ~ k port-forward simple-web-6bdf7b4d84-p7jkp 8889:8080 |
2.2 service
但是当我们有多个副本的 pod 时,我们不可能一个一个 pod 的代理指定端口,同时 port-forward 也没解决 pod 之间的服务发现。这个时候我们需要 Service
来提供路由
我们同样使用 ``kubectl create创建一个
Service`
1 | apiVersion: v1 |
这里 selector 我们需要填写在 deployment 中定义的 pod 的标签
1 | ➜ ~ k get svc |
我们创建了一个叫 simple-web
的 service,可以看到 k8s 分配了一个 clusterIP,并将这个 ip 的 80 端口映射到 pod 的 8080 端口
1 | ➜ ~ curl 10.96.156.194/index |
除了用 ip 访问,我们也可以用 serivce 来访问
1 | ➜ ~ k exec simple-web-6bdf7b4d84-2kzxx -- curl http://simple-web/index |
2.3 nodeport
使用 ClusterIP 我们只能在 k8s 集群内访问 pod,如果要从外部访问 pod,则可以定义 NodePort type 的 Service
1 | apiVersion: v1 |
这样我们就可以通过宿主机 ip:nodeport 访问 pod 中的 web 服务了
Service 还提供了 LoadBalancer 的 type,方便提供一个 external 的 ip 供访问,一般为云厂商使用,此处不表
3 Volume
Volume 提供了一种在 pod 的 container 之间数据交互,container 与宿主机或者其它的存储交互的手段。k8s 的 Volume 有很多类型,下面以 EmptyDir 为例看看 Volume 的使用。
我们将 web 应用的 log 挂载到 EmptyDir,配置如下
1 | apiVersion: apps/v1 |
我们可以在宿主机上找到 emptyDir 的路径,默认是 /var/lib/kubelet/pods/PODUID/volumes/kubernetes.io~empty-dir/VOLUMENAME
1 | ➜ ~ k get pods simple-web-6bdf7b4d84-2kzxx -o yaml | grep uid |
be609483-ed6d-427f-b277-81931cc9eb60
就是 pod uid
以这个 log 为例,我们宿主机上的 log 可以通过 filebeat 收集起来
4 configmap
当我们把应用部署到 pod 后,如果希望通过更改配置更改应用的行为,肯定不希望是需要重新打包一个镜像,所以我们需要把配置文件分离出来,configmap 就可以帮助我们完成这个事
1 | apiVersion: v1 |
然后修改 deployment 如下
1 | apiVersion: apps/v1 |
我们把 configmap 里的配置挂载到了 /app/conf/application.properties
,我们应用的启动脚本会读取这个配置文件 java -jar simple-web.jar --spring.config.location=/app/conf/application.properties
,通过这种方式,我们就将配置文件与应用分离。修改配置文件后,application.properties
就已经被更新了,但是由于 java 应用不会重新加载配置文件,所以需要重启 pod
5 liveness, readiness 与 pod resources
5.1 liveness, readiness
k8s 是如何知道一个 pod 无法提供服务了呢?我们需要定义一个健康检查的 liveness ,k8s 会定期请求这个 liveness,判断服务是否健康。
readiness 类似,通过 readiness 的定义判读 pod 是否已经可以开始提供服务
5.2 resources
k8s 提供了限制 container 所能使用的资源量的方法。resources.requests
定义了需要多少资源,用于 k8s 将 pod 调度到资源充足的节点,resources.limits
则限制了 container 可以使用的资源总量。可参考Kubernetes Container Resource Requirements
1 | apiVersion: apps/v1 |