kubectl run
命令是最简单的部署引用的方式,它自动创建必要组件,这样,我们就先不必深入了解每个组件的结构;
了解kubernetes,首先要了解其重要的三个概念:
pod
是k8s的容器,用来封装Docker或其它
容器的对象,它具有自己的虚拟环境(端口, 环境变量等),一个Pod
可以封装多个Docker或其它
容器.,在pod容器中会启动运行docker容器;其主要作用是完成容器化技术的解耦,通过这个媒介可以随时使用docker
或Containerd
等多种其他容器;目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了数量超过 2,650,000 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。
我们此次使用 作者 Lukas 所提供的一些测试使用的镜像:
Pod
是用来封装Docker
容器的对象,它具有自己的虚拟环境(端口, 环境变量等),一个Pod
可以封装多个Docker
容器.
RC
是用来自动控制Pod
部署的工具,它可以自动启停Pod
,对Pod
进行自动伸缩.
RC可以自动化维护多个pod,只需指定pod副本的数量,就可以轻松实现自动扩容缩容的操作;当一个pod宕机,RC可以自动关闭pod,并启动一个新的pod替代它来保证集群的高可用;
我们下载 作者Lukas
所提供Kubia镜像,通过Kubia镜像帮助我们启动3个容器,所启动容器是3个Pod容器,Pod容器中封装着docker容器;
下面是一个RC的部署文件,通过部署启动文件启动三个kubia容器:
cat
命令向kubia-rc.yml
文件中输出后续的内容,以
作为开始标志,以EOF
作为结束标志;
cat <<EOF > kubia-rc.yml
apiVersion: v1
kind: ReplicationController # 创建的控制器类型
metadata:
name: kubia # 为RC控制器命名
spec:
replicas: 3 # 启动的pod副本的数量
selector: # 选择器,用来选择RC管理的pod
app: kubia # 选择标签'app=kubia'的pod,由当前RC进行管理
template: # pod模板,用来创建新的pod
metadata:
labels:
app: kubia # 指定pod的标签
spec:
containers: # 容器配置
- name: kubia # 容器名
image: luksa/kubia # 镜像
imagePullPolicy: Never
ports:
- containerPort: 8080 # 容器暴露的端口
EOF
template
:通过template
设置容器模板,控制器启动容器时,将使用这个模板启动容器;--image=luksa/kubia
:使用的镜像名称,表明从luksa/kubia运行启动容器;replicas:3
:表明启动3个RC控制器自动创建的Pod容器(Pod容器中封装着docker容器)--port=8080
: RC控制器
对外暴露的端口--generator=run/v1 kubia
:创建一个ReplicationController
RC控制器创建后,会根据配置文件中指定的pod数量3,自动创建3个pod:
-f
命令为指定文件名;使用create
命令创建控制器后,控制器会帮我们自动创建容器;
k create -f kubia-rc.yml
# 查看控制器
k get rc
# 查看容器
k get po -o wide
控制器的部署文件中,除了我们 2.1
中介绍的参数外,还包括标签相关的参数;RC控制器也是通过指定的标签app=kubia
对匹配的pod进行管理的,包括:
app=kubia
,自定义标签,可以根据我们的部署情况自己定义,部署文件中我们会从 luksa/kubia
镜像中启动3个容器,启动容器时,会在容器中贴上设置的这个标签;app=kubia
,通过此标签选择容器,用来控制有此标签的容器;允许在pod上添加任何其他标签,而不会影响pod与RC的关联关系
k label pod kubia-vh46q type=special
k get po --show-labels
但是,如果改变pod的app标签的值,就会使这个pod脱离RC的管理,这样RC会认为这里少了一个pod,那么它会立即创建一个新的pod,来满足我们设置的3个pod的要求
k label pod kubia-qrx8r app=foo --overwrite
k get pods -L app
某些特殊时间节点会需要更大运算能力,此时就需要我们创建更多的容器,例如十一活动,各个电商平台均需要租赁服务器,临时进行容器的扩容;
k8s对应用部署节点的扩容缩容能力非常强,只需要指定需要运行多少个pod,k8s就可以完成pod的自动伸缩;
我们使用scale
命令进行容器的扩容缩容,通过指定具体的控制器名字并给定容器数量参数及值即可进行相应的扩容缩容:
# 将pod数量缩容为1个
k scale rc kubia --replicas=1
# 将pod数量扩容为3个
k scale rc kubia --replicas=3
我们使用命令k label po kubia-qcmps type=special
来给通过控制器自动创建的三个Pod容器中的其中一个容器添加一个标签:
此时我们的控制器与容器之间的关联关系没有任何影响,然后我们再次选择一个Pod容器修改其标签app
值为foo
:
此时我们可以看到控制器自动创建了一个新的标签,被修改标签的容器因为没有标签可以和控制器进行匹配,被认为是没有控制器管理的一个独立的容器,而控制器被创建时指定了自动创建的容器数量为3,而此时只剩了2台容器,所以其会自动新建一个新的容器;
我们通过命令修改控制器的部署文件,更改其默认创建容器数量为4,并添加一个新的标签foo: far
,保存退出后查看我们的容器变化:
k edit rc kubia
要注意的是,修改控制器部署文件时,不允许使用tab
键,tab
键在这里是制表符,会导致文件保存失败,只能使用空格键进行命令行缩进;并且标签的冒号后续,也要使用空格键空一格;
这时我们可以观察到,修改文件之前创建的容器并没有任何变化,而因为我们修改了默认创建的容器数量,所以此时控制器自动又新增了一个容器,使数量符合要求,并且新增的容器的标签已经按照我们修改后的部署文件内容进行配置了;
最后,我们这里使用命令删除控制器,但是不关联控制器创建的容器,使容器依然保留并处于脱管状态:
k delete rc kubia --cascade=orphan
为了后续操作的方便性,建议可以简单查看 10.Service
的相关内容,并且执行 10.5 NodePort方式对外暴露端口
的相关操作;
在第2节的内容中,我们通过创建控制器,从而使用控制器自动帮助我们构建了3个容器,这里我们将不适用控制器,而是通过手动创建的方式创建容器,首先,我们创建kubia-manual.yml
部署文件:
cat <<EOF > kubia-manual.yml
apiVersion: v1 # k8s api版本
kind: Pod # 该部署文件用来创建pod资源
metadata:
name: kubia-manual # pod名称前缀,后面会追加随机字符串
spec:
containers: # 对pod中容器的配置
- image: luksa/kubia # 镜像名
imagePullPolicy: Never
name: kubia # 容器名
ports:
- containerPort: 8080 # 容器暴露的端口
protocol: TCP
EOF
image
是这里的核心参数,指定从哪个镜像中构建我们的Pod容器;ports
端口可以不配置,做自动端口映射时才需要使用,当使用手动映射时则不需要此配置;imagePullPolicy
是指镜像的拉取策略,默认是镜像不存在时自动拉取,我们这里设置为Never,从不拉取;name
是给Pod中的docker容器的配置取名,Pod容器中可以包含多个docker容器,为了区分我们可以对其进行取名;k create -f kubia-manual.yml
k get po
查看pod的部署文件的详细信息
k get po kubia-manual -o yaml
查看pod日志
k logs kubia-manual
查看容器部署节点服务器信息
k get po -o wide
部署容器时,我们可以为对应容器生成不同的标签,控制器在控制容器时,可以通过对标签的选择从而控制对应的容器,这很好的解决了我们有大量容器时的分组管理难题;
我们也可以为 pod
指定自定义标签,通过标签可以对 pod
进行分组管理;
ReplicationController
,ReplicationSet
,Service
中,都可以通过 Label
来分组管理 pod
通过kubia-manual-with-labels.yml
部署文件部署pod时,我们可以在部署文件中设置标签的键值对内容;
例如下方的部署文件中为pod设置了两个自定义标签:creation_method
和env
,另外还有- image:
的值比较重要,表明其从哪个镜像启动docker容器;
cat <<EOF > kubia-manual-with-labels.yml
apiVersion: v1 # api版本
kind: Pod # 部署的资源类型
metadata:
name: kubia-manual-v2 # pod名
labels: # 标签设置,键值对形式
creation_method: manual
env: prod
spec:
containers: # 容器设置
- image: luksa/kubia # 镜像
name: kubia # 容器命名
imagePullPolicy: Never
ports: # 容器暴露的端口
- containerPort: 8080
protocol: TCP
EOF
创建部署文件后,通过手动创建的方式创建容器:
k create -f kubia-manual-with-labels.yml
创建容器后,我们通过下方的命令列出所有的pod,并显示pod的标签
k get po --show-labels
以列的形式列出pod的标签
k get po -L creation_method,env
接下来我们做一些对已存在标签增删改查的一些操作;
pod kubia-manual-v2
的env标签值是prod
, 我们把这个标签的值修改为 debug
修改一个标签的值时,必须指定 --overwrite
参数,目的是防止误修改,需要明确的表明就是要修改相关内容,避免因新增标签时不知道已存在此标签导致的误修改;
k label po kubia-manual-v2 env=debug --overwrite
为pod kubia-manual
设置标签
k label po kubia-manual creation_method=manual env=debug
为pod kubia-5rz9h
设置标签```bash
k label po kubia-5rz9h env=debug
k label po kubia-2cfd9 env=debug
k label po kubia-94vtb env=prob
查看标签设置的结果
```bash
k get po -L creation_method,env
查询 creation_method=manual
的pod,使用-l
来进行条件过滤,使用逗号可以分割多个条件,同时使用多个条件查询,需要注意的是!
表示排查,这种特殊字符需要使用引号将其引起来;
其他查询举例:
creation_method!=manual
:!
表示不等,表明查询不包含manual 的其它标签env in (prod,debug)
:in表示匹配多个值env notin (prod,debug)
:notin表示匹配不包含的多个值# -l 查询creation_method=manual的Pdod
k get po -l creation_method=manual -L creation_method,env
查询有 env 标签的 pod:
# -l 查询
k get po -l env -L creation_method,env
查询 creation_method=manual 并且 env=debug 的 pod:
# -l 查询
k get po -l creation_method=manual,env=debug -L creation_method,env
查询不存在 creation_method 标签的 pod
# -l 查询
k get po -l '!creation_method' -L creation_method,env
删除标签时在标签名后边跟上一个-
来表示删除一个标签:
k label po kubia-94vtb env-
我们不能通过直接指定服务器的地址来约束pod部署的节点,例如我们有2个工作节点,在部署时,Pod会随机选择一个节点进行部署,这是不可控的;
但是在实际业务中,我们需要的运算单元是需要指定部署到固定的单元,此时就需要通过为node设置标签,在部署pod时,使用节点选择器,来选择把pod部署到匹配的节点服务器;
部署文件,其中节点选择器nodeSelector
设置了通过标签gpu=true
来选择节点
cat <<EOF > kubia-gpu.yml
apiVersion: v1
kind: Pod
metadata:
name: kubia-gpu # pod名
spec:
nodeSelector: # 节点选择器,把pod部署到匹配的节点
gpu: "true" # 通过标签 gpu=true 来选择匹配的节点
containers: # 容器配置
- image: luksa/kubia # 镜像
name: kubia # 容器名
imagePullPolicy: Never
EOF
创建pod kubia-gpu
,并查看pod的部署节点
k create -f kubia-gpu.yml
k get po -o wide
我们可以看到此时REDAY的值为0/1,因为Pod中要包含docker容器,但是因为此时我们docker容器还没有部署,所以其状态为Pending;
下面为名称为192.168.64.193
的节点服务器,添加标签gpu=true
k label node 192.168.64.193 gpu=true
k get node -l gpu=true -L gpu
创建部署文件以及完成节点服务器的标签设置后,Pod会马上启动容器,并自动进行节点服务器注册;
此时我们再次查看并查看pod的部署节点
k get po -o wide
查看pod kubia-gpu
的描述
k describe po kubia-gpu
可以为资源添加注解,注解不能被选择器使用
# 注解
k annotate pod kubia-manual tedu.cn/shuoming="foo bar"
k describe po kubia-manual
使用 kubectl port-forward
命令设置端口转发,对外暴露pod.
使用服务器的 8888 端口,映射到 pod 的 8080 端口
k port-forward kubia-manual --address localhost,192.168.64.191 8888:8080
# 或在所有网卡上暴露8888端口
k port-forward kubia-manual --address 0.0.0.0 8888:8080
在浏览器中访问 http://192.168.64.191:8888/
可以使用命名空间对资源进行组织管理,我们可以创建多个命名空间,在命名空间下可以创建多个容器,多个命名空间内的容器互不影响;
但不同命名空间的资源并不完全隔离,它们之间可以通过网络互相访问
# namespace 写全拼或简写均可
k get ns
k get po --namespace kube-system
k get po -n kube-system
新建部署文件custom-namespace.yml
,创建命名空间,命名为custom-namespace
cat <<EOF > custom-namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: custom-namespace
EOF
# 创建命名空间
k create -f custom-namespace.yml
创建pod,并将其部署到命名空间custom-namespace
# 创建 Pod 时指定命名空间
k create -f kubia-manual.yml -n custom-namespace
# 默认访问default命名空间,默认命名空间中存在我们此前创建的kubia-manual
k get po kubia-manual
# 访问custom-namespace命名空间中的pod
k get po kubia-manual -n custom-namespace
按名称删除
, 可以指定多个名称,例如: k delete po po1 po2 po3;在删除时会首先停止关闭容器,然后再进行删除操作,所以会比较慢k delete po kubia-gpu
按标签删除
,使用-l
进行过滤删除k delete po -l creation_method=manual
删除命名空间和其中所有的pod
k delete ns custom-namespace
删除当前命名空间中所有pod
k delete po --all
# 由于有ReplicationController,所以会自动创建新的pod
[root@master1 ~]# k get po
NAME READY STATUS RESTARTS AGE
kubia-m6k4d 1/1 Running 0 2m20s
kubia-rkm58 1/1 Running 0 2m15s
kubia-v4cmh 1/1 Running 0 2m15s
删除工作空间中所有类型中的所有资源
,这个操作会删除一个系统Service kubernetes,它被删除后会立即被自动重建k delete all --all
删除节点服务器中的标签
k label no 192.168.64.193 gpu-
存活探针可以自动检测容器是否正常,会每隔一段时间进行容器状态的检测,如果连续3此检测到容器异常,会自动重启容器使其恢复,并且在重启时,会自动记录重启次数;
有三种存活探针:
我们这里将介绍HTTP GET
存活探针;
luksa/kubia-unhealthy 镜像是一个封装好的不健康镜像,在kubia-unhealthy
镜像中,前5次请求均为正常: 从第6次请求开始会返回500异常报错;
在部署文件中,我们使用kubia-unhealthy
镜像来创建容器,并且我们添加探针,来探测容器的健康状态.
探针默认每10秒探测一次,连续三次探测失败后重启容器,在启动容器时,就会自动进行第一次探测;
cat <<EOF > kubia-liveness-probe.yml
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveness # pod名称
spec:
containers:
- image: luksa/kubia-unhealthy # 镜像
name: kubia # 容器名
imagePullPolicy: Never
livenessProbe: # 存活探针配置
httpGet: # HTTP GET 类型的存活探针
path: / # 探测路径
port: 8080 # 探测端口
EOF
k create -f kubia-liveness-probe.yml
查看创建的 kubia-liveness 容器
k get po kubia-liveness
pod的RESTARTS属性,每重启一次就会加1;
查看pod的日志,每次探测请求时,都会打印一个日志,前5次探测是正确状态,后面3次探测是失败的,则该pod会被删除,然后进行重启,重启后日志将又会从第一次探测日志开始打印,而此时RESTARTS属性就会加1;
k logs kubia-liveness --previous
-----------------------------------------
Kubia server starting...
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
Received request from ::ffff:172.20.3.1
k describe po kubia-liveness
---------------------------------
......
Restart Count: 6
Liveness: http-get http://:8080/ delay=0s timeout=1s period=10s #success=1 #failure=3
......
delay
0表示容器启动后立即开始探测timeout
1表示必须在1秒内响应,否则视为探测失败period
10s表示每10秒探测一次failure
3表示连续3次失败后重启容器我们上边介绍了,在容器启动时,存活探针就会自动进行第一次探测,而某些复杂的运行启动时间可能会比较长,启动过程中可能会导致探测失败,多次失败的情况下,会导致运用再次重启;
通过设置 delay 延迟时间,可以避免在容器内应用没有完全启动的情况下就开始探测, delay 延迟时间的设定可以在部署文件中通过initialDelaySeconds
参数进行设定;
cat <<EOF > kubia-liveness-probe-initial-delay.yml
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveness
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
imagePullPolicy: Never
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15 # 第一次探测的延迟时间
EOF
重新设置部署文件后,可以直接使用新的部署文件重新创建容器,创建后自行测试是否符合延迟探测的预期即可;
Kubernetes 提供了一个控制台管理工具,可以在浏览器界面上做一些简单的管理操作,功能比较有限,其使用 Dashboard
容器进行运行管理控制台,这个容器提供了一个service 并在服务器上打开了一个端口映射,我们通过访问这个service的端口映射从而访问 Dashboard
容器;
我们可以使用 k get pod -n kube-system
命令查看 kube-system 所提供的一些容器,里边有一个 dashboard
容器,或者也可以直接使用 k get pod -n kube-system | grep dashboard
进行查找:
我们可以使用 k get svc -n kube-system
命令查看 dashboard
容器所提供的 service 暴露的端口信息,或者也可以直接使用 k get svc -n kube-system | grep dashboard
进行查找:
我们可以看到 https 默认的端口443映射到了一个随机的30456端口;
# 查看集群信息
k cluster-info | grep dashboard
根据上面信息可以看到 dashboard 的访问地址:
https://192.168.64.191:30456/
使用集群CA 生成客户端证书,该证书拥有所有权限:
cd /etc/kubernetes/ssl
# 导出证书文件
openssl pkcs12 -export -in admin.pem -inkey admin-key.pem -out kube-admin.p12
下载 /etc/kubernetes/ssl/kube-admin.p12
证书文件, 在浏览器中导入:
访问 dashboard 会提示登录, 这里我们用令牌的方式访问 :https://192.168.64.191:30456/
我们这里使用更简便的令牌方式进行权限获取:
# 获取 Bearer Token,复制输出中 ‘token:’ 开头那一行
k -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
我们上一章介绍了Kubernetes 的控制台,现在我们使用控制台来创建控制器,我们复制创建控制台的部署文件,然后去掉 cat命令相关内容,只保留yml配置内容:
RC控制器是一个旧版本的控制器,RS是一个新版本的控制器,RS控制器是用来替代RC控制器的,俩者唯一的却别就是RS提供了增强选择器,比RC控制器只能使用标签选择器选择容器要更方便,还可以使用表达式来选择容器;
在部署文件中,我们选择创建3个pod副本,如果现在标签匹配的,实际存在的副本数量要大于3的话,那么它会随机杀掉超过数量的Pod容器;
cat <<EOF > kubia-replicaset.yml
apiVersion: apps/v1 # RS 是 apps/v1中提供的资源类型
kind: ReplicaSet # 资源类型
metadata:
name: kubia # RS 命名为 kubia
spec:
replicas: 3 # pod 副本数量
selector:
matchLabels: # 使用 label 选择器
app: kubia # 选取标签是 "app=kubia" 的pod
template:
metadata:
labels:
app: kubia # 为创建的pod添加标签 "app=kubia"
spec:
containers:
- name: kubia # 容器名
image: luksa/kubia # 镜像
imagePullPolicy: Never
EOF
创建 ReplicaSet
k create -f kubia-replicaset.yml
# 之前脱离管理的pod被RS管理
# 设置的pod数量是3,多出的pod会被关闭
k get rs
----------------------------------------
NAME DESIRED CURRENT READY AGE
kubia 3 3 3 4s
# 多出的3个pod会被关闭
k get pods --show-labels
----------------------------------------------------------------------
NAME READY STATUS RESTARTS AGE LABELS
kubia-8d9jj 1/1 Pending 0 2m23s app=kubia,foo=bar
kubia-lc5qv 1/1 Terminating 0 3d5h app=kubia
kubia-lhj4q 1/1 Terminating 0 2d22h app=kubia
kubia-pjs9n 1/1 Running 0 3d5h app=kubia
kubia-wb8sv 1/1 Pending 0 2m17s app=kubia,foo=bar
kubia-xp4jv 1/1 Terminating 0 2m17s app=kubia,foo=bar
# 查看RS描述, 与RC几乎相同
k describe rs kubia
我们可以在控制台看到目前的容器列表,其中有一个容器处于Terminating
状态:
我们先删除现有的RS控制器,并保留容器:
k delete rs kubia --cascade=orphan
然后重新使用新的部署文件内容,使用表达式的标签选择器,并修改启动容器数量为4:
cat <<EOF > kubia-replicaset.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 4
selector:
matchExpressions: # 表达式匹配选择器
- key: app # label 名是 app
operator: In # in 运算符
values: # label 值列表
- kubia
- foo
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
imagePullPolicy: Never
EOF
然后创建新的RS控制器:
k create -f kubia-replicaset.yml
# 查看rs
k get rs
# 查看pod
k get po --show-labels
可使用的运算符:
In
: label与其中一个值匹配NotIn
: label与任何一个值都不匹配Exists
: 包含指定label名称(值任意)DoesNotExists
: 不包含指定的label测试完成后我们清理一下环境,方便后续测试的继续进行:
k delete all --all
k get rs
k get po
除了RC控制器,RS控制器这种每个场景都可以使用的控制器外,还有3中特殊场景使用的控制器,我们这里做一些简单的介绍;
DS控制器最适合的场景就是监控场景的部署,它可以在每个节点上运行一个 pod,例如资源监控,kube-proxy等;
DaemonSet不需要指定pod数量,它会在每个节点上部署一个pod,十分适合部署监控场景的使用;下面我们来模拟部署一个监控工具,进行某一环境的监控;
cat <<EOF > ssd-monitor-daemonset.yml
apiVersion: apps/v1
kind: DaemonSet # 资源类型
metadata:
name: ssd-monitor # DS资源命名
spec:
selector:
matchLabels: # 标签匹配器
app: ssd-monitor # 匹配的标签
template:
metadata:
labels:
app: ssd-monitor # 创建pod时,添加标签
spec:
containers: # 容器配置
- name: main # 容器命名
image: luksa/ssd-monitor # 镜像
imagePullPolicy: Never
EOF
创建 DS
k create -f ssd-monitor-daemonset.yml
DS 创建后,会在所有节点上创建pod,包括master,我们这里一共有3台服务器,所以其部署了3个容器:
k get po -o wide
并且我们可以通过节点的label来选择节点,在所有选定的节点上部署pod:
cat <<EOF > ssd-monitor-daemonset.yml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector: # 节点选择器
disk: ssd # 选择具有标签'disk=ssd'的节点
containers:
- name: main
image: luksa/ssd-monitor
imagePullPolicy: Never
EOF
部署文件创建后需要先清理环境,再创建控制器:
# 先清理
k delete ds ssd-monitor
# 再重新创建
k create -f ssd-monitor-daemonset.yml
查看 DS控制器 和 pod容器, 看到并没有创建pod,这是因为不存在具有disk=ssd标签的节点
k get ds
k get po
为节点’192.168.64.192’设置标签 disk=ssd
,这样 DS 会在该节点上立即创建 pod
k label node 192.168.64.192 disk=ssd
k get ds
k get po -o wide
同样,进一步测试,为节点’192.168.64.193’设置标签 disk=ssd
k label node 192.168.64.193 disk=ssd
k get ds
k get po -o wide
删除’192.168.64.193’节点上的disk
标签,那么该节点中部署的pod会被立即销毁
# 注意删除格式: disk-
k label node 192.168.64.193 disk-
k get ds
k get po -o wide
测试完成后,我们清理环境
k delete all --all
Job控制器是用来运行单个任务的,任务结束后pod控制器不再重启容器,使容器关闭退出;这种单次任务容器的运行使用控制器主要作用是为了避免容器在运行过程中的出错和意外关闭;
cat <<EOF > exporter.yml
apiVersion: batch/v1 # Job资源在batch/v1版本中提供
kind: Job # 资源类型
metadata:
name: batch-job # 资源命名
spec:
template:
metadata:
labels:
app: batch-job # pod容器标签
spec:
restartPolicy: OnFailure # 任务失败时重启
containers:
- name: main # 容器名
image: luksa/batch-job # 镜像
imagePullPolicy: Never
EOF
创建 job
镜像 batch-job 中的进程,运行120秒后会自动退出
k create -f exporter.yml
k get job
-----------------------------------------
NAME COMPLETIONS DURATION AGE
batch-job 0/1 7s
k get po
-------------------------------------------------------------
NAME READY STATUS RESTARTS AGE
batch-job-q97zf 0/1 ContainerCreating 0 7s
等待两分钟后,pod中执行的任务退出,再查看job和pod
k get job
-----------------------------------------
NAME COMPLETIONS DURATION AGE
batch-job 1/1 2m5s 2m16s
k get po
-----------------------------------------------------
NAME READY STATUS RESTARTS AGE
batch-job-q97zf 0/1 Completed 0 2m20s
我们也可以使用Job让pod连续运行5次,因为其默认每次只会启动一个容器进行运行,所以我们设置启动5个容器的话,会先创建第一个pod,等第一个完成后后,再创建第二个pod,以此类推,共顺序完成5个pod;
cat <<EOF > multi-completion-batch-job.yml
apiVersion: batch/v1
kind: Job
metadata:
name: multi-completion-batch-job
spec:
completions: 5 # 指定完整的数量
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
imagePullPolicy: Never
EOF
k create -f multi-completion-batch-job.yml
并且还可以设置并发的运行,每次运行2个容器,共完成5个容器的运行:
cat <<EOF > multi-completion-parallel-batch-job.yml
apiVersion: batch/v1
kind: Job
metadata:
name: multi-completion-parallel-batch-job
spec:
completions: 5 # 共完成5个
parallelism: 2 # 可以有两个pod同时执行
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
imagePullPolicy: Never
EOF
k create -f multi-completion-parallel-batch-job.yml
Cronjob控制器用来执行定时和重复的计划任务,例如备份,临时数据的清理等;cron时间表格式为:"几分 几点 几号 几月 周几"
;
其含义分别为:
cat <<EOF > cronjob.yml
apiVersion: batch/v1beta1 # api版本
kind: CronJob # 资源类型
metadata:
name: batch-job-every-fifteen-minutes
spec:
# 0,15,30,45 - 分钟
# 第一个* - 每个小时
# 第二个* - 每月的每一天
# 第三个* - 每月
# 第四个* - 每一周中的每一天
schedule: "0,15,30,45 * * * *"
jobTemplate:
spec:
template:
metadata:
labels:
app: periodic-batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
imagePullPolicy: Never
EOF
在设置时间格式中,每个字段之间使用空格分隔,单个字段多个值的,使用逗号进行分隔;上面部署文件中,设置了 每天每个小时的0,15,30,45 分执行任务,只为分钟设置了具体的数值值,其它值均设置为 * 号;
创建cronjob
k create -f cronjob.yml
# 立即查看 cronjob,此时还没有执行任务创建pod
k get cj
----------------------------------------------------------------------------------------------
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
batch-job-every-fifteen-minutes 0,15,30,45 * * * * False 1 27s 2m17s
# 到0,15,30,45分钟时,会创建一个pod
k get po
--------------------------------------------------------------------------------------
NAME READY STATUS RESTARTS AGE
batch-job-every-fifteen-minutes-1567649700-vlmdw 1/1 Running 0 36s
Service类似于一个注册中心,k8s中使用的注册中心是etcd
,通过Service资源,在注册中心发现注册的所有可以访问的容器,客户端调用时调用Service,通过Service转发调用指令,找到对应注册的容器,从而避免因容器的创建销毁导致的注册地址变化无法找到容器:
在Service 帮助客户端进行调用转发时,首先Service自己要打开一个端口,以供注册中心、客户端等进行连接;
上面我们创建了对外暴露的端口的信息,其可以使外部设备直接访问Service,但在实际项目中,我们的容器是不断变化的,经常性的会出现大批量的扩容缩容,因此我们使用Service主要还是控制器通过其统一管理容器;
在service帮我们调用容器时,会首先从注册中心得到地址,并且service自己也要打开一个端口对外暴露,方便控制器访问Service,然后再通过标签选择相应容器进行统一的调用管理;
接下来我们创建内部使用的Service 服务:
cat <<EOF > kubia-svc.yml
apiVersion: v1
kind: Service # 资源类型
metadata:
name: kubia # 资源命名
spec:
ports:
- port: 80 # Service向外暴露的端口
targetPort: 8080 # 容器的端口
selector:
app: kubia # 通过标签,选择名为kubia的所有pod
EOF
核心参数就是对外暴露的端口以及选择具有哪些标签的容器和容器的端口;
k create -f kubia-svc.yml
#查看service服务,也可以使用 k get service 命令
k get svc
如果没有pod具有app:kubia标签,可以创建前面的ReplicaSet资源,并让RS自动创建pod:
k create -f kubia-replicaset.yml
Kubernetes 会创建一个虚拟网络,这个虚拟网络是可以跨服务器的,我们创建的容器、Service都可以连接虚拟网络,并可以通过虚拟网络进行跨服务器的互相连接;
但是因为我们现在创建的service服务并没有向外暴露端口,暂时还不能在服务器外部访问service服务,但是我们可以使用服务器集群进行访问,通过执行curl http://10.68.123.147
来从内部的虚拟网络访问Service,多次执行时我们可以看到,Service会在多个pod中轮训发送请求
当 Service 选中容器后会自动创建 EndPoint 断点对象,其本质就是一个地址列表(数组/集合),EndPoint 和 Service 会绑定到一起,存放Service获取的容器地址列表,俩者之间通过名称进行关联,也就是说它俩是同名的,或者说只要同名的俩个EndPoint 和 Service 服务就会被绑定在一起;我们可以通过 k get ep
命令查看 EndPoint 相关信息:
或者也可以通过查看 Service详情查看关联的 EndPoint 对象信息:
进行后续操作时,需要保证 9.1 使用部署文件创建ReplicaSet控制器
中的相关操作均已执行,已正确创建RS控制器集群,我们后续的操作将依托RS控制器集群进行操作;
我们通过Service访容器时,可能会产生一些数据,但是后续如果再次调用时,可能调用的是其它容器,此时就无法访问此前长生的数据了,为了使Service调用的总是同一个容器,我们要使用 会话亲和性
的概念,一般使用客户端的IP地址进行关联,如果使用这个IP进行访问,只要IP不变,就访问同一个容器;
也就是说来自同一个客户端的请求,总是发给同一个pod,通过在Service的部署文件中添加 sessionAffinity
参数来进行设置:
cat <<EOF > kubia-svc-clientip.yml
apiVersion: v1
kind: Service
metadata:
name: kubia-clientip
spec:
sessionAffinity: ClientIP # 回话亲和性使用ClientIP
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
EOF
k create -f kubia-svc-clientip.yml
k get svc
此时我们再次进入kubia-5zm2q容器,向Service发送请求,执行多次会看到,每次请求的都是同一个pod
前边在创建Service服务时,已经简单了解了endpoint,endpoint是在Service和pod之间的一种资源,用来存储容器的地址;一个endpoint资源,包含一组pod的地址列表;
并且endpoint也可以手动创建,手动创建endpoint一般都是为了调用外部系统,比如集成地图导航时,我们调用百度地图的服务时,需要访问其外部的服务器;
k describe svc kubia
查看所有endpoint:
k get ep
查看名为kubia的endpoint:
k get ep kubia
并且,endpoint虽然会自动创建,但是其必须在包含pod选择器的服务被创建时才会自动被创建,但是我们在调用外部服务时,并不会设置选择器,所以不含pod选择器的服务,不会创建 endpoint,此时就需要我们手动创建endpoint来调用外部服务了;
cat <<EOF > external-service.yml
apiVersion: v1
kind: Service
metadata:
name: external-service # Service命名
spec:
ports:
- port: 80
EOF
k create -f external-service.yml
# 查看Service
k get svc
# 通过内部网络ip访问Service,没有Endpoint地址列表,会拒绝连接
curl http://10.68.27.124
创建endpoint关联到Service,它的名字必须与Service同名
cat <<EOF > external-service-endpoints.yml
apiVersion: v1
kind: Endpoints # 资源类型
metadata:
name: external-service # 名称要与Service名相匹配
subsets:
- addresses: # 包含的地址列表
- ip: 153.3.238.110 # 百度的ip地址
- ip: 58.20.196.140 # 搜狐的ip地址
ports:
- port: 80 # 目标服务的的端口
EOF
也可以设置你自己喜欢的地址,复制官网网页连接,然后使用工具ping即可获取地址信息:
# 创建Endpoint
k create -f external-service-endpoints.yml
# 访问 external-service
# 多次访问,会在endpoints地址列表中轮训请求
curl http://10.68.27.124
完全限定域名
访问外部服务这种方式不需要使用 NodePort 对象添加地址链表,通过指定一个域名来直接访问,这种方式只能设置一个固定的域名;
cat <<EOF > external-service-externalname.yml
apiVersion: v1
kind: Service
metadata:
name: external-service-externalname
spec:
type: ExternalName
externalName: www.baidu.com # 域名
ports:
- port: 80
EOF
通过部署文件创建服务:
k create -f external-service-externalname.yml
# 访问 external-service-externalname
curl www.chinaunicom.com.cn
上述操作都是通过集群的IP去进行调用操作的,那么没有集群的情况怎么办呢,其实我们也可以在容器内部直接使用这些 Service 的名称去进行调用;
# 进入Pod容器
k exec -it kubia-8dpqn bash
# 通过Service名称直接访问服务
curl http://external-service-externalname
将Service 端口直接暴露到服务器上,映射到服务器上,这样的话我们就可以通过服务器直接访问Service端口,然后通过Service进行访问请求的转发;
前面创建的Service只能在集群内部网络中访问,那么怎么让客户端来访问Service呢? 共有三种方式进行Service的访问:
今天我们只介绍 NodePort 节点端口方式对外暴露端口;
Service在暴露端口时,可以使用NodePort方式在每个服务器中都暴露相同的一个端口,每个节点(包括master),都开放这个相同的端口,可以通过任意节点的端口来访问Service;
cat <<EOF > kubia-svc-nodeport.yml
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort # 在每个节点上开放访问端口
ports:
- port: 80 # 集群内部访问该服务的端口
targetPort: 8080 # 容器的端口
nodePort: 30123 # 外部访问端口
selector:
app: kubia
EOF
创建并查看 Service:
k create -f kubia-svc-nodeport.yml
k get svc kubia-nodeport
可以通过任意节点的30123
端口来访问 Service
我们通过访问可以看到,当访问不同节点时可能会由相同的容器返回相应信息,但是当连续访问同一个节点时,将会在三个容器中切换返回容器信息;
但是浏览器不会一直去切换访问,这是因为现在浏览器与服务器建立了连接缓存,只有断开后,重新建立连接,才会访问其它服务,不会浏览器连接只会存在几十秒,每隔几十秒刷新一次,还是能观察到服务切换的状态;
或者我们也可以使用命令直接 使用 service 对外暴露 pod
k expose \
rc kubia \
--type=NodePort \
--name kubia-http
k get svc
这里创建了一个 service 组件,用来对外暴露pod访问,在所有节点服务器上,暴露了20916端口,通过此端口,可以访问指定pod的8080端口
访问以下节点服务器的30123端口,都可以访问该应用
注意: 要把端口修改成你生成的随机端口
如果容器被删除,那么容器中的数据也将丢失,为了数据永久保存,需要进行容器挂载操作,那么k8s是怎么进行容器挂载的呢?
k8s中数据卷的类型分为:
接下来我们开始尝试各种方式的数据挂载;
创建包含两个docker容器的pod, 它们共享同一个卷,其中一个docker运行一个应用,每10秒生成1个 index.html 文件,每隔10秒产生新的随机内容,覆盖之前的内容;
另一个docker部署Nginx服务,使用Nginx作为一个web应用服务器,去访问 index.html 文件;这样我们就可以观察到,每隔10秒生成的文件内容都会改变;
最后将其挂载到 emptyDir 中,用来验证其存储文件的策略;
cat <<EOF > fortune-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: fortune
labels:
app: fortune
spec:
containers:
- image: luksa/fortune # 镜像名
name: html-genrator # 容器名
imagePullPolicy: Never
volumeMounts:
- name: html # 卷名为 html
mountPath: /var/htdocs # 容器中的挂载路径
- image: nginx:alpine # 第二个镜像名
name: web-server # 第二个容器名
imagePullPolicy: Never
volumeMounts:
- name: html # 相同的卷 html
mountPath: /usr/share/nginx/html # 在第二个容器中的挂载路径
readOnly: true # 设置为只读
ports:
- containerPort: 80
protocol: TCP
volumes: # 卷
- name: html # 为卷命名
emptyDir: {} # emptyDir类型的卷
EOF
部署文件中的 containers
中设置了俩个docker容器,并设置了数据卷的挂载位置,volumes
中设置了数据卷为 emptyDir;
# 清理此前创建的所有内容
k delete all --all
# 创建Pod容器
k create -f fortune-pod.yml
# 查看创建的Pod容器
k get po
此时我们已经可以使用命令查看生成的 html文件内容了,并且内容是每10秒重新生成的:
并且可以使用 docker inspect c035
命令查看挂载详情,我们切换到挂载的目录可以查看我们生成的文件:
此时我们保持在html目录下,直接执行删除命令 kubectl delete po fortune
删除我们刚刚创建的容器,然后切换目录我们就可以看到,实际的目录已经被同步删除了:
创建Service, 通过这个Service访问pod的80端口
cat <<EOF > fortune-svc.yml
apiVersion: v1
kind: Service
metadata:
name: fortune
spec:
type: NodePort
ports:
- port: 8088
targetPort: 80
nodePort: 32088
selector:
app: fortune
EOF
# 创建Service
k create -f fortune-svc.yml
# 查看创建的Service
k get svc
用浏览器访问 http://172.20.2.22:32088/
emptyDir方式依赖于自身本身的服务器,当服务器被删除后,挂载的文件夹目录也会被删除,还是会导致数据丢失;
那么,我们是否可以单独创建一个服务器,只用作数据卷的挂载呢?这样当其它服务被删除时,并不会影响到我们用于挂载数据卷的服务器,那么这样的话,数据卷中数据的存储就需要使用网络在各个服务器之间进行存取;
centos虚拟机使用NFS系统需要先安装其系统,下面我们在191,192,193这三台服务器上安装 nfs:
# centos8使用此命令
dnf install nfs-utils
# centos7使用此命令
yum install nfs-utils
安装完成之后我们就可以共享文件夹了,首先我们在 master 节点 192.168.64.191 上创建 nfs 目录 /etc/nfs_data
用作共享文件夹,并允许 1921.68.64
网段的主机共享访问这个目录
# 创建文件夹
mkdir /etc/nfs_data
在exports文件夹中写入配置dnf install nfs-utils -bash,并设置no_root_squash:表示服务器端使用root权限访问时不进行身份降级,rw
表示读写权限, asyn
表示异步操作;并且在虚拟机中,有很多的虚拟网络,我们这里设置共享到 192.168.64.0
这个虚拟网段;
cat <<EOF > /etc/exports
/etc/nfs_data 192.168.64.0/24(rw,async,no_root_squash)
EOF
最后启动相关服务:
systemctl enable nfs-server
systemctl enable rpcbind
systemctl start nfs-server
systemctl start rpcbind
尝试在客户端主机上,例如在192.168.64.192
服务器上进行下列操作挂载远程的nfs
目录:
# 新建挂载目录
mkdir /etc/web_dir/
# 在客户端, 挂载服务器的 nfs 目录
mount -t nfs 192.168.64.191:/etc/nfs_data /etc/web_dir/
上一小节我们创建了共享文件夹来进行数据卷的挂载,通过网络将数据存储到另一台服务器上从而避免因服务器的变动而导致数据丢失的情况,但这样又会引起另一个问题,如果服务器的存储位置发生变化,那么俩台服务器之间的配置、部署都需要重新设置,这将是十分麻烦的事情;
所以这里引入了 PersistentVolume 对象,用于设置存储位置,存储条件和访问模式,通过PersistentVolume对象来实现服务器之间的解耦;
创建 PersistentVolume - 持久卷资源部署文件:
cat <<EOF > mongodb-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity:
storage: 1Gi # 定义持久卷大小
accessModes:
- ReadWriteOnce # 只允许被一个客户端挂载为读写模式
- ReadOnlyMany # 可以被多个客户端挂载为只读模式
persistentVolumeReclaimPolicy: Retain # 当声明被释放,持久卷将被保留
nfs: # nfs远程目录定义
path: /etc/nfs_data
server: 192.168.64.191
EOF
# 创建持久卷
k create -f mongodb-pv.yml
# 查看持久卷
k get pv
虽然使用PersistentVolume进行了服务器之间的解耦,但通常被认为这样的解耦是不彻底的,所以又引入了 Persistent Volume Claim 对象,用于服务器与 PersistentVolume 对象之间的解耦,Persistent Volume Claim对象只设置了数据卷的存储条件和访问模式,并没有设置具体的存储路径,这样就使得如果在多台Persistent Volume对象存在的情况下,可以不必关心存储条件的设置;
可以简单理解为 Persistent Volume Claim 为一个声明,而 PersistentVolume 是一个具体实现,它们俩者之间是通过存储条件和访问模式的相似度进行匹配的,会自动关联相似度较高的俩个对象;
使用持久卷声明,使应用与底层存储技术解耦,这里只设置存储条件:
cat <<EOF > mongodb-pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 1Gi # 申请1GiB存储空间
accessModes:
- ReadWriteOnce # 允许单个客户端读写
storageClassName: "" # 参考动态配置章节
EOF
k create -f mongodb-pvc.yml
k get pvc
然后我们创建一个mongodb服务
cat <<EOF > mongodb-pod-pvc.yml
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
imagePullPolicy: Never
securityContext:
runAsUser: 0
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc # 引用之前创建的"持久卷声明"
EOF
验证 pod 中加挂载了 nfs 远程目录作为持久卷
k create -f mongodb-pod-pvc.yml
k exec -it mongodb -- mongo
use mystore
db.foo.insert({name:'foo'})
db.foo.find()
查看在 nfs 远程目录中的文件
cd /etc/nfs_data
ls
一个复杂的容器是需要设置启动命令和启动参数的,某些在启动时需要设置环境不变量和环境参数,这时就需要我们自己配置启动参数了
在docker 中使用 ENTRYPOINT 来设置启动命令,使用CMD来对启动命令中的参数进行设置;
Dockerfile中定义命令和参数的指令:
ENTRYPOINT
启动容器时,在容器内执行的命令CMD
对启动命令传递的参数其中CMD
可以在docker run
命令中进行覆盖
例如:
......
ENTRYPOINT ["java", "-jar", "/opt/sp05-eureka-0.0.1-SNAPSHOT.jar"]
CMD ["--spring.profiles.active=eureka1"]
启动容器时,可以执行:
docker run <image>
或者启动容器时覆盖CMD
docker run <image> --spring.profiles.active=eureka2
k8s
中覆盖docker的ENTRYPOINT
和CMD
command
可以覆盖ENTRYPOINT
args
可以覆盖CMD
在镜像luksa/fortune:args
中,设置了自动生成内容的间隔时间参数为10秒
可以通过k8s的args
来覆盖docker的CMD
,将自动生成内容的间隔时间参数设置为2秒;
cat <<EOF > fortune-pod-args.yml
apiVersion: v1
kind: Pod
metadata:
name: fortune
labels:
app: fortune
spec:
containers:
- image: luksa/fortune:args
args: ["2"] # docker镜像中配置的CMD是10,这里用args把这个值覆盖成2
name: html-genrator
imagePullPolicy: Never
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
imagePullPolicy: Never
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
EOF
k create -f fortune-pod-args.yml
# 查看pod
k get po -o wide
重复地执行curl命令,访问该pod,会看到数据每2秒刷新一次
注意要修改成你的pod的ip
curl http://172.20.2.55
在镜像luksa/fortune:env
中通过环境变量INTERVAL
来指定内容生成的间隔时间
下面配置中,通过env
配置,在容器中设置了环境变量INTERVAL
的值,使用命令行执行的话就是 docker run -e interval=5
:
cat <<EOF > fortune-pod-env.yml
apiVersion: v1
kind: Pod
metadata:
name: fortune
labels:
app: fortune
spec:
containers:
- image: luksa/fortune:env
env: # 设置环境变量 INTERVAL=5
- name: INTERVAL
value: "5"
name: html-genrator
imagePullPolicy: Never
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
imagePullPolicy: Never
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
EOF
k delete po --all
k create -f fortune-pod-env.yml
# 查看pod
k get po -o wide
# 进入pod
k exec -it fortune bash
# 查看pod的环境变量
env
------------
INTERVAL=5
......
# 从pod推出,回到宿主机
exit
重复地执行curl命令,访问该pod,会看到数据每5秒刷新一次
注意要修改成你的pod的ip
curl http://172.20.2.56
通过ConfigMap资源,可以从pod中把环境变量配置分离出来,使环境变量配置与pod解耦;其类似于配置中心的作用,将这些参数集中存放,从而在修改配置时不需要改变容器的相关配置,只需要动Map即可;
可以从命令行创建ConfigMap资源:
# 直接命令行创建
k create configmap fortune-config --from-literal=sleep-interval=20
或者从部署文件创建ConfigMap:
# 或从文件创建
cat <<EOF > fortune-config.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: fortune-config
data:
sleep-interval: "10"
EOF
并且在这一个文件中可以设置多个值,供多个容器使用,只需要使用键去对应获取值即可;
# 创建ConfigMap
k create -f fortune-config.yml
# 查看ConfigMap的配置
k get cm fortune-config -o yaml
我们可以使用部署文件添加12.4.1
中创建的配置文件,从ConfigMap获取配置数据,设置为pod的环境变量
cat <<EOF > fortune-pod-env-configmap.yml
apiVersion: v1
kind: Pod
metadata:
name: fortune
labels:
app: fortune
spec:
containers:
- image: luksa/fortune:env
imagePullPolicy: Never
env:
- name: INTERVAL # 环境变量名
valueFrom:
configMapKeyRef: # 环境变量的值从ConfigMap获取
name: fortune-config # 使用的ConfigMap名称
key: sleep-interval # 用指定的键从ConfigMap取数据
name: html-genrator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
imagePullPolicy: Never
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
EOF
执行步骤 12.4.1
后,我们也可以选择从控制台添加具体的配置文件,点击进入配置文件内按照键值对匹配的方式进行添加即可;
配置环境变量后,可以在启动参数中使用环境变量
cat <<EOF > fortune-pod-args.yml
apiVersion: v1
kind: Pod
metadata:
name: fortune
labels:
app: fortune
spec:
containers:
- image: luksa/fortune:args
imagePullPolicy: Never
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
args: ["\$(INTERVAL)"] # 启动参数中使用环境变量
name: html-genrator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
imagePullPolicy: Never
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
EOF
先删除之前创建的ComfigMap后,从磁盘文件创建 ConfigMap
d delete cm fortune-config
创建一个文件夹,存放配置文件
cd ~/
mkdir configmap-files
cd configmap
创建nginx的配置文件,启用对文本文件和xml文件的压缩
cat <<EOF > my-nginx-config.conf
server {
listen 80;
server_name www.kubia-example.com;
gzip on;
gzip_types text/plain application/xml;
location / {
root /ur/share/nginx/html;
index index.html index.htm;
}
}
EOF
添加sleep-interval
文件,写入值25
cat <<EOF > sleep-interval
25
EOF
从configmap-files文件夹创建ConfigMap
cd ~/
k create configmap fortune-config \
--from-file=configmap-files
常见的系统升级有俩种,一种是直接杀掉所有旧版本,然后启动新版本,一般称为停机升级,这种升级方式的缺点就是在停机过程中会造成系统不可用,影响用户体验;
另一种是逐步替换,杀掉一个旧版本,启动一个新版本,一直到所有旧版本杀掉,新版本启动,这种升级一般称为滚动升级,缺点就是用户在使用中,有些请求会请求到旧版本,有些请求会请求到新版本,并且对于版本之间的耦合兼容要求较高,新版本和旧版本之间必须要相互兼容;
相对来说,停机升级会比较简单,而滚动升级就会比较麻烦,而Deployment 就是是一种自动实现滚动升级的资源,用于部署
或升级
应用;
创建Deployment时,ReplicaSet资源会随之创建,实际Pod是由ReplicaSet创建和管理,而不是由Deployment直接管理
Deployment可以在应用滚动升级过程中, 引入另一个RepliaSet, 并协调两个ReplicaSet,与RepliaSet的配置设计基本一致;
cat <<EOF > kubia-deployment-v1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
imagePullPolicy: Never
name: nodejs
EOF
k create -f kubia-deployment-v1.yml --record
k get deploy
k get rs
k get po
k rollout status deploy kubia
我们可以看到Deployment对象帮我们创建了3个RS控制器对象,而RS控制器对象帮我们创建了3个容器对象;
其中RS控制对象的名字继承了Deployment对象的名字,并且RS控制对象的名字对镜像名进行了hash运行产生了hash值加到了名字后面;
同样的,容器对象的名字也继承了RS控制对象的名字,并且容器对象的名字对RS控制对象的镜像名也进行了hash运行产生了hash值加到了名字后面;
接下来我们创建一个Service服务的Node方式对外暴露的断开,执行 10.5 NodePort方式对外暴露端口
小节的内容,便于我们使用Service观察访问的情况;
只需要在 pod 模板中修改镜像的 Tag, Deployment 就可以自动完成升级过程
Deployment的升级策略
minReadySeconds
设置为10秒, 减慢滚动升级速度, 便于我们观察升级的过程.
k patch deploy kubia -p '{"spec": {"minReadySeconds": 10}}'
修改 Deployment 中 pod 模板使用的镜像就可以触发滚动升级
为了便于观察, 我们新打开一个标签,在另一个191终端页面中执行循环, 通过 service 来访问pod,循坏调用服务:
while true; do curl http://192.168.64.191:30123; sleep 0.5s; done
可以看到目前访问的都是V1版本的内容,接下来我们修改镜像将其改为V2版本:
k set image deploy kubia nodejs=luksa/kubia:v2
我们看到已经开始启动V2版本了,通过不断的杀掉V1启动V2的方式来将版本更新完成,我们可以通过不同的命令来了解升级的过程和原理
# 查看升级滚动状态
k rollout status deploy kubia
# 查看控制器
k get rs
# 查看容器
k get po --show-labels
# 查看控制器详情,可以查看单独设置的hash值标签
k describe rs kubia-577cffc4f5
在升级过程中,Deployment对象会创建多个RS对象,分别控制新版本和旧版本,因为RS控制器是通过标签来选择创建的容器的,而这时同一个控制器会选择新版本也会控制旧版本,这是怎么做到的呢?
这是因为在创建容器是会给容器设置一个额外的hash值标签,通过此标签来分别进行选择,控制新版本的RS控制器是一个,控制旧版本的RS控制器是另一个;
我们再设置一个V3版本,v3 镜像中的应用模拟一个 bug, 从第5次请求开始, 会出现 500 错误:
k set image deploy kubia nodejs=luksa/kubia:v3
出错后,我们执行回滚命令,使其回退到此前的可用版本V2版本:
k rollout undo deploy kubia
我们观察Service连接的变化情况,由V2替换V3时的报错逐渐增多,再到V3回退到V2的报错逐渐减少:
滚动升级时是先创建新版本pod,再销毁旧版本pod的,我们可以通过参数设置来控制滚动升级中的新建容器数量和销毁容器数量;
maxSurge
- 默认25%
maxUnavailable
- 默认 25%
容器数量可以使用百分比表示,也可以使用整数直接表示数量;
# 查看升级速率参数
k get deploy -o yaml
接下来我们尝试将 image 升级到 v4 版本,在触发更新后立即暂停更新.
这时会有一个新版本的 pod 启动, 可以暂停更新过程, 让少量用户可以访问到新版本, 并观察其运行是否正常.
根据新版本的运行情况, 可以继续完成更新, 或回滚到旧版本.
k set image deploy kubia nodejs=luksa/kubia:v4
# 暂停
k rollout pause deploy kubia
# 继续
k rollout resume deploy kubia
在升级过程中,我们可以设置 minReadySeconds
参数来自动控制出错版本是否继续升级,其会在升级中等待容器就绪后倒计时一段时间(默认为10秒),然后才会下一步继续的升级;
minReadySeconds
:
最后还需要添加一个就绪探针,通过 minReadySeconds
参数设置倒计时参数,在倒计时开始时开始探测容器的就绪状态,探测成功则容器为就绪状态,探测失败则设置容器状态为未就绪状态,在倒计时结束后,如果获取容器还是未就绪状态,则认为升级失败,从而停止升级状态;
cat <<EOF > kubia-deployment-v3-with-readinesscheck.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v3
name: nodejs
imagePullPolicy: Never
readinessProbe:
periodSeconds: 1
httpGet:
path: /
port: 8080
EOF
我们这里设置探测时间为1秒探测1测,并且只要探测成功,立即变为就绪状态,并且设置了滚动升级的策略,升级为V3。同样模拟第5次开始升级状态为V3:
k apply -f kubia-deployment-v3-with-readinesscheck.yml
就绪探针探测间隔设置成了 1 秒, 第5次请求开始每次请求都返回500错, 容器会处于未就绪状态. minReadySeconds
被设置成了10秒, 只有pod就绪10秒后, 升级过程才会继续.所以这是滚动升级过程会被阻塞, 不会继续进行.
我们可以看到,在升级过程中会有报错的提醒,并且新建的容器中有一个容器状态是未就绪,此时有未就绪状态的容器,那么就不会开始倒计时,升级也就不会继续进行下去:
默认升级过程被阻塞10分钟后, 升级过程会被视为失败, Deployment描述中会显示超时(ProgressDeadlineExceeded),这时升级也就不会再继续进行了:
k describe deploy kubia
-----------------------------
......
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded
......
这是只能通过手动执行 rollout undo 命令进行回滚
k rollout undo deploy kubia
kubectl get cs
:查看集群系统服务基础信息(正常为Healthy健康状态)kubectl get no
:查看节点服务器基础信息(正常为Ready就绪状态)kubectl get rc
:查看RC控制器基础信息kubectl get po
:查看创建的容器基础信息(正常为Running状态)kubectl get svc
:svc是service缩写,这里写全写也可以,获取service信息,也可以查询指定service信息,例如k get svc kubia-nodeport
;kubectl get po kubia-manual -o yaml
:查看Pod中部署的容器的详细信息;kubectl get po kubia-manual -o yaml
:查看pod日志kubectl get po -o wide
:查看容器部署节点服务器信息kubectl get po --show-labels
:列出所有的pod,并显示pod的标签kubectl get po -L creation_method,env
:以列的形式所有的pod,并显示pod的标签kubectl create -f xxx.yml
:使用部署配置文件创建Pod,-f
参数后跟yml配置文件kubectl label po kubia-manual-v2 env=debug
:为Pod容器新增标签kubectl label po kubia-manual-v2 env=debug --overwrite:
修改Pod容器的标签kubectl label po kubia-94vtb env-
:删除Pod容器kubectl get po -L creation_method,env
:查看Pod容器的标签kubectl get po -l creation_method=manual -L creation_method,env
:使用标签查询匹配的Pod容器kubectl label node 192.168.64.193 gpu=true
:为指定节点的服务器添加标签kubectl get node -l gpu=true -L gpu
:查看节点服务器的标签信息kubectl describe po kubia-gpu
:查看Pod服务器的详细描述信息kubectl get ns
:查看系统中存在的命名空间kubectl get po -n xxx
: xxx为命名空间名,查看此命名空间内有哪些服务kubectl delete rc kubia --cascade=orphan
:删除控制器,但保留容器kubectl edit rc kubia
:修改部署文件,后续跟文件类型参数(rc/pod),文件名kubectl delete cj -all
:删除所有计划任务,,其中 -all
也可以替换为集体的任务名kubectl get pv
:查看PersistentVolume对象kubectl get pvc
:查看Persistent Volume Claim 对象