容器化入门(四)--k8s入门
本文内容主要来源于极客时间《Kubernetes 入门实战课》,夹杂了自己在学习过程中的思考和相关记录
1.容器编排(Container Orchestration)就是在容器之上的管理和调度工作。容器编排的主角就是Kubernetes(k8s). k8s由Goole开发、并捐献给CNCF云原生基金会,是一个生成级别的容器编排平台和集群管理系统
2.使用minikube搭建小巧完备的k8s实践环境:
参考官网上安装:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
install minikube-linux-amd64 /usr/local/bin/minikube && rm minikube-linux-amd64
查看版本号 minikube version
:
安装k8s客户端工具kubectl
minikube kubectl
使用minikube启动集群,启动过程中会下载k8s版本
minikube start --kubernetes-version=v1.23.3 --force
可以用如下命令查看集群状态
minikube status
minikube node list
也可以用minikube ssh
登录到节点上,它其实是一个虚拟机
配置kubectl:针对minikube,无法直接使用kubectl,必须要带个前缀、参数前要带"--",为了方便使用,可以利用linux的alias。同时,可以配置自动补全能力
alias kubectl="minikube kubectl --"
source <(kubectl completion bash)
然后就可以愉快的使用kubectl了:
# 查版本
kubectl version --short
# 运行一个pod
kubectl run ngx --image=nginx:alpine
# 查询pod
kubectl get pod
3.Kubernetes 的基本架构
典型的控制面/业务面分离架构。控制面负责整体系统的监控和维护,业务面承载业务能力。在k8s中,控制面又称为Master Node,业务面称为Work Node。可以通过 kubectl get node
查看node的状态
Master Node节点内部包含四个组件:apiserver、etcd、scheduler、controller-manager。apiserver是整个k8s系统的唯一入口,对外提供了RESTful API,所有组件都只能通过它通信,它是整体系统的联络员; etcd是分布式key-value数据库,持久化各种系统里的资源对象和状态;scheduler负责容器的编排,把Pod调度到合适的节点运行; controller-manager负责节点和容器的维护,实现故障检测、服务迁移、应用收缩等。四个组件都容器化了,可以通过 kubuctl get pod -n kube-system
查询到对应Pod的状态。
Work Node节点内部包含三个组件:kubelet、kube-proxy、container-runtime。kubelet是代理,负责Node相关的绝大部分操作,它可以和apiserver通信,上传下达的作用。kube-proxy是网络代码,负责容器的网络通信。container-runtime是容器和镜像的实际使用者,当然,容器运行时可不局限于Docker。这三个组件中,kubelet要管理整个节点,所以它不是以容器化的方式提供的;而kube-proxy是以容器化的方式提供的:
除了基础组件,还有很多有用的插件Addons。可以通过 minikube addons list
查询插件的部署状态。可以使用minikube dashboard
使能dashboard插件,这个插件可以用浏览器的方式管理k8s集群, 但是它只能本地访问,针对腾讯云等场景需要远程使用时可以通过在本机上配置ssh 隧道方式使用
ssh -L 45013:127.0.0.1:45013 root@**.**.**.**
配置成功后可以正常使用:
4.k8s中的YAML:
关注”命令式“和”声明式“两种风格的差异:”命令式“将每一条要执行的步骤都写清楚,注重顺序和过程。像是那种非常强势、控制力极强的家长管理小孩那样,方方面面都严格管控,当然有时候这非常累人且起不到好效果; 而”声明式“不关心过程,它只描述定义好结果,让系统自动来想完成任务。在k8s这样复杂的系统上,我们要使用”声明式“的风格,仅告诉它我们想要的目标态、由系统自动地、指能地规划调度,达到我们想要的目的。这里,YAML就是我们”声明“目标态的载体,在k8s的使用中伴随这各种各样的YAML 最基本的语法:
规则 | 描述 |
---|---|
缩进 | 使用空格进行缩进,不要使用制表符。推荐使用两个空格作为缩进。 |
键值对 | 使用冒号 : 分隔键和值,键值对之间使用空格分隔。 键不需要用引号 |
列表 | 使用短横线 - 表示列表项,并缩进一个级别。 |
对象 | 使用键值对表示对象,键和值之间使用冒号 : 分隔,对象之间缩进一个级别。 |
注释 | 使用井号 # 表示注释,注释从井号开始直到行末。 |
字符串引用 | 使用单引号 ' ' 或双引号 " " 包裹字符串,推荐使用双引号。不强制使用引号 |
多行字符串 | 使用管道 ”|“ 符号表示多行字符串块,或使用大于号 > 表示折叠的多行字符串,即合并为1行。 |
多个文档 | 可以在同一个YAML文件中使用三个连续的短横线 --- 分隔多个文档。 |
YAML是JSON的超集,任何合法的JSON语法都是合法的YAML
例子1, 数组/列表
OS:
- linux
- macOS
- windows
例子2, 对象
kubernetes:
matser: 1
worker: 3
一个更复杂的例子
kubernetes:
matser:
- apiserver: running
- etcd: running
node:
- kubelet: running
- kube-proxy: down
- container-runtime: [docker, containerd, cri-o]
5.k8s中的API对象:
什么是API对象:k8s为了管理集群,抽象出来的业务对象,这些对象的管理都是通过apiserver的RESTful接口实现的。可以通过 kubectl api-resources
所有支持的对象类型。很多对象都有简写名称SHORT NAMES,用于操作起来更高效。
当然我们不用直接使用rest接口去管理这些所谓的api对象,命令行工具kubectl已经替我们封装了。我们可以在使用 kubectl 命令的时候,加上参数 --v=9来看到封装的过程。例如:
6.如何使用YAML描述api对象:如何使用YAML描述api对象。 用一个最简单最普通的pod说来举例明,之前我们使用kubectl run ngx --image=nginx:alpine
通过命令式方式创建了一个ngx pod,我们可以通过如下的YAML来描述
apiVersion: v1
kind: Pod
metadata:
name: ngx-pod
labels:
env: demo
owner: chrono
spec:
containers:
- image: nginx:alpine
name: ngx
ports:
- containerPort: 80
整个YAMM大致可以分为2部分:
”head“头:指明对应对象类型,以及对应的api版本,包括一些元信息:(name和labels等) ”body“体:体现具体的规格描述specification,是我们声明的期望状态
可以使用kubectl apply
、kubectl create
、kubectl delete
,再加上参数 -f使用对应的YAML创建或者删除对象。
kubectl apply -f ngx-pod.yml
kubectl delete -f ngx-pod.yml
kubectl create
和kubectl apply
都可以根据YAML创建对象,两者的区别是:create只能创建,如果对象已存在会报错;但是apply会自动判断资源是否已存在,如果存在则更新、不存在则创建
7.如何高效书写YAML:
技巧一:可以通过 kubectl api-resources
获取资源类型、版本等信息技巧二:使用 kubectl explain
获取资源的类型、版本、相关的字段等信息
技巧三:让 kubectl生成一份“文档样板”,免去我们打字和对齐格式的工作。使用两个特殊参数 --dry-run=client
和-o yaml
,前者是空运行,后者是生成 YAML 格式,结合起来使用就会让 kubectl 不会有实际的创建动作,而只生成 YAML 文件。然后可以进一步根据文档样板稍作调整.例如可以使用kubectl run ngx --image=nginx:alpine --dry-run=client -o yaml
生成一份简单的Pod的YAML文件。(进一步的,我们可以将--dry-run=client -o yaml
定义为一个变量$out
,方便后续使用)
技巧四:可以通过 -o yaml
参数从别的对象获取完整的YAML描述文件,但是要注意,这种方式获取的YAML可能会很复杂,里面包含了一些系统创建的字段。例如kubectl get pod ngx-pod -o yaml
8.最核心的资源对象Pod:
为了解决多应用联合运行的问题,同时还要不破坏容器的隔离,需要在容器外面再建立一个“收纳舱”,让多个容器既保持相对独立,又能够小范围共享网络、存储等资源,而且永远是“绑在一起”的状态。 Pod应运而生 Pod的本意是豆荚,顾名思义,内部的豆子就是容器 Pod是k8s中的原子对象,其他的对象都是直接或者间接的依赖Pod 但事实上在 Kubernetes 里通常并不会直接创建 Pod,因为它只是对容器做了简单的包装,比较脆弱,离复杂的业务需求还有些距离,需要 Job、CronJob、Deployment 等其他对象增添更多的功能才能投入生产使用
9.用YAML描述Pod:
”head“头:定义api版本、资源类型和metadata,值得注意的是metadata中labels字段,我们可以添加任意多的键值对,给对应的对象打上归类标签,可以利用标签更加方便的过滤和管理对象
apiVersion: v1
kind: Pod
metadata:
name: busy-pod
labels:
owner: chrono
env: demo
region: north
tier: back
”body“体:描述了具体的规格specification,描述Pod里container的信息。其中containers是一个数组,可以包含多个容器。针对容器的参数中,ports用于列出容器对外暴露的端口;imagePullPolicy用于指定镜像的拉取策略,可以是 Always/Never/IfNotPresent;env:定义 Pod 的环境变量,注意它是一个数组,每个数组元素是一个对象,包括name和value属性,value必须是一个字符串、需要双引号;command定义容器启动时要执行的命令,相当于 Dockerfile 里的 ENTRYPOINT 指令;args:它是 command 运行时的参数,相当于 Dockerfile 里的 CMD 指令。
spec:
containers:
- image: busybox:latest
name: busy
imagePullPolicy: IfNotPresent
env:
- name: os
value: "ubuntu"
- name: debug
value: "on"
command:
- /bin/echo
args:
- "$(os), $(debug)"
9.Pod相关的操作:
创建和删除pod
kubectl apply -f busy-pod.yml
kubectl delete -f busy-pod.yml
也可以根据pod名称删除pod
kubectl delete pod busy-pod
查看pod的标准输出流信息, 一般用于日志打印:
kubectl logs busy-box
查询pod的信息,可以根据label过滤
kubectl get po
kubectl get pod -l owner=bianchengfeichai
pod异常时,根据事件定位异常的原因:
kubectl describe pod busy-box
拷贝文件
kubectl cp a.txt ngx-pod:/tmp
exec执行pod内的指令,与 Docker 有一点小差异,需要在 Pod 后面加上 --,把 kubectl 的命令与 Shell 命令分隔开
kubectl exec ngx-pod -- ls /tmp/a.txt
kubectl exec -it ngx-pod -- sh
10.用面向对象的思想理解k8s:
面向对象编程(OOP),它把一切都视为高内聚的对象,强调对象之间互相通信来完成任务,强调封装和隐藏细节。Kubernetes 使用 YAML 来描述资源,把业务简化成了一个个的对象,内部有属性,外部有联系,也需要互相协作 面向对象有2条优秀准则:“单一职责”和“组合优于继承”,k8s严格遵循这2条准则。例如:Pod只管理容器,提供最基础的容器的管理能力;其他更高级的容器管理能力、应该定义其他的对象,然后在新的对象中包含一个Pod对象,提供新的扩展的能力
10.为什么需要Job/CronJob:在具体的业务场景中,有一类离线业务,即它不是实时在线的,只需要一次性运行或者每个一段时间定时运行一次。而原生的Pod满足不了这类诉求,因此Job/CronJob应运而生,分别对应一次性运行或者每个一段时间定时运行一次的离线场景
11.用YAML描述Job
”head“头:同Pod,无变化,但是它的api版本是batch/v1 ”body“体:简单来说,就是在body体里”组合“了一个原生的Pod。
apiVersion: batch/v1
kind: Job
metadata:
name: echo-job
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- image: busybox
name: echo-job
imagePullPolicy: IfNotPresent
command: ["/bin/echo"]
args: ["hello", "world"]
”body“体中扩展参数:除了在body体里”组合“了一个原生的Pod之外,Job还有自己的一些属性,我们控制Job执行的超时时间、并发等,这些参数定义在第一级的spec下,这些是Job的参数、而不是Pod的参数。这些参数在管理复杂的任务时非常有用。
参数名称 | 含义 |
---|---|
activeDeadlineSeconds | 设置 Job 运行的超时时间。 |
backoffLimit | 设置 Job 的失败重试次数。 |
completions | Job 完成需要运行多少个 Pod,默认是 1 个。 |
parallelism | 允许并发运行的 Pod 数量,避免过多占用资源。 |
11.操作Job
基础的创建、删除、查询pod日志等
kubectl apply -f echo-job.yml
kubectl delete -f echo-job.yml
kubectl get job
kubectl get pod
kubectl logs echo-job-9vqvw
创建“sleep-job”,它随机睡眠一段时间再退出,模拟运行时间较长的作业。Job 的参数设置成 15 秒超时,最多重试 2 次,总共需要运行完 4 个 Pod,但同一时刻最多并发 2 个 Pod
apiVersion: batch/v1
kind: Job
metadata:
name: sleep-job
spec:
activeDeadlineSeconds: 15
backoffLimit: 2
completions: 4
parallelism: 2
template:
spec:
restartPolicy: OnFailure
containers:
- image: busybox
name: echo-job
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- sleep $(($RANDOM % 10 + 1)) && echo done
可以在执行期间,通过kubectl get pod -w
(w代表watch)持续监控pod的变化
12.CronJob
YAML描述:CronJob简称为cj,它是在Job对象的基础上有扩展出的新功能,因为它的YAML无非就是在它的spec中嵌套了一个Job。这样,事实上,cj的yaml中会有3个spec字段。同时,cj有一些自己的参数,例如典型的schedule字段用于指定定时执行的时间,它定义在最外层的spec中,是linux crontab定时的形式:
apiVersion: batch/v1
kind: CronJob
metadata:
name: echo-cj
spec:
schedule: '*/1 * * * *'
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- image: busybox
name: echo-cj
imagePullPolicy: IfNotPresent
command: ["/bin/echo"]
args: ["hello", "world"]
创建、删除等跟job是一样的,我们可以创建这个cj,看看效果
cj定时启动一个Pod,但是默认不会删除老的Pod,这可能会导致系统产生大量的Pod。需要关注Pod的保留策略
13.怎么管理配置:应用在运行时,往往需要各种各样的配置参数,例如数据库地址和密码等。这些参数如何管理、如何在容器内外部传递呢? 很多参数是跟实际运行的环境有关系,所以不能在build镜像时通过Dockerfile写死;当然,我们也可以单独写配置文件,容器运行起来后,通过cp命令拷贝进容器中,但是这种方法不适合自动化运维。 因此ConfigMap/Secret对象应运而生。ConfigMap/Secret对象是一类简单的对象,内部存储了key-value形式的配置信息,k8s的其他对象就可以使用ConfigMap/Secret对象获取相关的配置信息
14.Config对象:
可以使用 kubectl create cm info --from-literal=k=v $out
生成YAMl样板,cm是Config的简称Config对象的Yaml非常简单,head头的部分跟其他对象类型,body体的部分只有一个data字段,里面用key-value的形式定义了各种配置数据
apiVersion: v1
kind: ConfigMap
metadata:
name: info
data:
count: '10'
debug: 'on'
path: '/etc/systemd'
greeting: |
say hello to kubernetes.
使用 kubectl apply
创建cm对象查询cm对象以及具体包含的配置数据
kubectl get cm
kubectl describe cm info
kubectl get cm info -o yaml
14.Secret对象:
cm对象已经很好用了,但是它存储的配置信息都是明文的,对于密码等用户敏感信息直接使用cm存储肯定是不太安全的。因为Secret对象应运而生
Kubernetes里Secret 对象又细分出很多类,目前只需要关注最普通的一类,如果通过命令行创建,需要指定类型generic
Kubernetes里Secret 对象又细分出很多类,目前只需要关注最普通的一类,如果通过命令行创建,需要指定类型generic
kubectl create secret generic my-secret --from-literal=name=bianchengfeichai $out
对应的YAML跟cm差不多,但是里面存储的value都是经过base64转码的结果,不是原始的值。
apiVersion: v1
kind: Secret
metadata:
creationTimestamp: null
name: my-secret
data:
name: YmlhbmNoZW5nZmVpY2hhaQ==
如果要在YAMl手写定义新的配置,需要写入base64转码后的结果。
echo -n "bianchengfeichai" | base64
如下是一个完整的示例
apiVersion: v1
kind: Secret
metadata:
name: user
data:
name: YmlhbmNoZW5nZmVpY2hhaQ== # bianchengfeichai
pwd: MTIzNDU2 # 123456
db: bXlzcWw= # mysql
创建、查询等方式跟cm类似,不过只能看到base转码后的密文 Secret这种经过base64转码的密文实际加密程度略等于无,感觉有些鸡肋
14.使用ConfigMap/Secret对象的两种方式:
通过环境变量的方式使用:使用 pod.spec.containers.env.valueFrom
引用对应的ConfigMap/Secret对象,获取对应key对应的value值作为环境变量的值
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
- env:
- name: COUNT
valueFrom:
configMapKeyRef:
name: info
key: count
- name: GREETING
valueFrom:
configMapKeyRef:
name: info
key: greeting
- name: USERNAME
valueFrom:
secretKeyRef:
name: user
key: name
- name: PASSWORD
valueFrom:
secretKeyRef:
name: user
key: pwd
image: busybox
name: busy
imagePullPolicy: IfNotPresent
command: ["/bin/sleep", "300"]
可以进入对应的pod确认,对应环境变量已经生效:
下图展示了各种对象之间的关系:
以Volume 的方式使用:可以将对应的配置挂载为容器的卷,容器内部通过文件的方式访问配置。使用时,先定义卷再挂载卷,具体讲,就是现在spec下跟 containers:
同级定义volumes:
,这也是一个数组列表,可以定义多个卷,卷定义时引用ConfigMap/Secret对象; 然后在containers:
下通过volumeMounts:
声明要挂载的卷和对应的路径
apiVersion: v1
kind: Pod
metadata:
name: vol-pod
spec:
volumes:
- name: cm-vol
configMap:
name: info
- name: sec-vol
secret:
secretName: user
containers:
- volumeMounts:
- mountPath: /tmp/cm-items
name: cm-vol
- mountPath: /tmp/sec-items
name: sec-vol
image: busybox
name: busy
imagePullPolicy: IfNotPresent
command: ["/bin/sleep", "300"]
进入对应的pod,发现配置都映射为文件了,可以直接读取文件:
下图展示了各种对象之间的关系:
15.实战搭建WordPress应用
基本架构:因为当前还没有学习Service/Ingress等内容,我们先将Wordpress容器和MariaDB容器部署在k8s上,而Nginx还是使用docker的方式运行
部署MariaDB:为了方便的管理MariaDB启动时数据库名称、用户名等信息,我们先创建一个cm管理相关参数
apiVersion: v1
kind: ConfigMap
metadata:
name: maria-cm
data:
DATABASE: 'db'
USER: 'wp'
PASSWORD: '123'
ROOT_PASSWORD: '123'
然后定义MariaDB对应的Pod,我们需要引用maria-cm,不过这里我们需要maria-cm中定义的所有的变量,我们可以直接使用envFrom
导入cm中的所有变量,并添加前缀
apiVersion: v1
kind: Pod
metadata:
name: maria-pod
labels:
app: wordpress
role: database
spec:
containers:
- image: mariadb:10
name: maria
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
envFrom:
- prefix: 'MARIADB_'
configMapRef:
name: maria-cm
创建对应的pod,通过kubectl get po -o wide
确认pod状态正常、并获取ip地址
部署WordPress:同样的,我们先创建一个cm对象,管理启动WordPress需要的相关的参数,注意IP地址要跟maria-pod的地址对应上
apiVersion: v1
kind: ConfigMap
metadata:
name: wp-cm
data:
HOST: '172.17.0.9'
USER: 'wp'
PASSWORD: '123'
NAME: 'db'
定义pod
apiVersion: v1
kind: Pod
metadata:
name: wp-pod
labels:
app: wordpress
role: website
spec:
containers:
- image: wordpress:5
name: wp-pod
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
envFrom:
- prefix: 'WORDPRESS_DB_'
configMapRef:
name: wp-cm
创建对应的pod,并确保状态正常
配置端口转发:当前 wp-pod工作在集群的私有IP上,为了方便使用和测试,可以配置端口转发,将本地的8080端口映射到wp-pod的80端口上,可以使用kubectl port-forward来转发
kubectl port-forward wp-pod 8080:80 &
创建Nginx反向代理,因为WordPress配置了反向代理,必须通过80端口访问。编写对应的配置文件
server {
listen 80;
default_type text/html;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080;
}
}
启一个nginx容器,使用我们设定的配置文件
docker run -d --rm --net=host -v ./ngx.conf:/etc/nginx/conf.d/default.conf nginx:alpine
通过浏览器实测安装OK
同时,也可以查询对应pod的日志等
通过dashboard管理集群,查看各种对象的状态等,同时还可以创建对象、编辑对象、进入Pod内部等