容器化入门(四)--k8s入门

文摘   科技   2024-05-05 16:05   陕西  

容器化入门(四)--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 applykubectl createkubectl delete,再加上参数 -f使用对应的YAML创建或者删除对象。

kubectl apply -f ngx-pod.yml
kubectl delete -f ngx-pod.yml

kubectl createkubectl 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 的失败重试次数。
completionsJob 完成需要运行多少个 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内部等


编程废柴
努力Coding