02 kubernetes 的调度单元 pod

创建第一个 pod

创建 nginx pod

编写 yaml 文件

cd ~
vim nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
  labels:
    name: my-nginx
spec:
  containers:
  - name: my-nginx
    image: nginx
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    ports:
      - containerPort: 80

创建 pod

kubectl apply -f nginx.yaml

查看 pod

kubectl get po

可以看到

NAME       READY   STATUS    RESTARTS   AGE
my-nginx   1/1     Running   0          57s

查看详细信息

kubectl describe pod my-nginx

使用 job 创建 pod

cd ~
vim hellojob.yaml

编写 job ,hello 这个 job 会跑 5 次 pod

apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  completions: 5
  template:
    # 以下是 Pod 
    spec:
      containers:
      - name: hello
        image: nginx
        command: ['sh', '-c', 'echo "Hello, Kubernetes! " && sleep 2']
      restartPolicy: OnFailure

创建 job

kubectl create -f hellojob.yaml
kubectl get job
kubectl get po
kubectl get po -w
kubectl get po -w
kubectl delete job hello

为容器的生命周期事件设置处理函数

创建一个包含一个容器的 Pod ,该容器为 postStart 和 preStop 事件提供对应的处理函数

在从节点 node1 :

cd ~
vim lifecycle.yaml
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: nginx
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:
        exec:
          command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]

kubectl create -f lifecycle.yaml
kubectl get po
kubectl exec -it lifecycle-demo sh
ls
cat /usr/share/message

可以看到

Hello from the postStart handler

创建包含 Init 容器的 Pod

Init 容器

每个 pod 中可以包含多个容器,应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器

Init 容器与普通的容器非常像,除了如下两点:

  1. 它们总是运行到完成

  2. 每个都必须在下一个启动之前成功完成

    如果 pod 的 Init 容器失败,Kubernetes 会不断重启该 pod ,知道 Init 容器成功为止。如果 pod 对应的 restartPolicy 值为 Never ,Kubernetes 不会重新启动 pod

与普通容器的不同之处

Init 容器支持应用容器的全部属性和特性,包括资源限制、数据卷和安全设置。

同时 Init 容器不支持 lifecycle, livenessProbe, readinessProbe 和 startupProbe, 因为它们必须在 pod 就绪之前运行完成。

定义一个具有两个 Init 容器的 pod 。第一个等待 myservice 启动,第二个等待 mydb 启动。一旦这两个 Init 容器都启动完成,pod 将启动 spec 节中的应用容器。

cd ~
vim initPod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'date && sleep 3600']
  initContainers:
  - name: init-container
    image: busybox:1.28
    command: ['sh', '-c', "date && sleep 10"]

启动 pod

kubectl apply -f initPod.yaml
kubectl get po

查看 pod 日志

kubectl logs myapp-pod
Thu Mar 18 08:52:08 UTC 2021

查看 pod 内 init 容器的日志

kubectl logs myapp-pod -c init-container
Thu Mar 18 08:51:57 UTC 2021

用探针检查 pod 的健康性

探针是 kubelet 对容器执行的定期诊断,监测 pod 里面的容器是不是正常启动了。kubelet 调用由容器实现的 Handler 进行诊断,Handler 的类型有三种:

  • ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功
  • TCPSocketAction:对容器的 ip 地址上的指定端口进行 TCP 检查,如果端口打开,则认为诊断成功。
  • HTTPGetAction:对容器的 ip 地址上的指定端口的路径执行 HTTP Get 请求。如果响应的状态码大于等于 200 并且小于 400,则认为诊断是成功的。

每次诊断都将获得以下三种结果之一:

  • Success(成功):容器通过了诊断
  • Failure(失败):容器未通过诊断
  • Unknown(未知):诊断失败,因此不会采取任何行动

什么时候使用探针

对于所包含的容器需要比较长的时间才能启动就绪的 pod ,启动探针是非常有用的。

HTTP 请求探针

cd ~
vim liveness.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: mirrorgooglecontainers/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

上面官方提供的 liveness 容器,10秒之内,服务会给 /healthz 请求返回 200,10秒之后,会返回 500:

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
})

initialDelaySeconds: 3 字段告诉 kubelet 在执行第一次探测之前应该等待 3 秒

periodSeconds: 3 字段指定了 kubelet 每隔 3 秒执行一次存活探测

启动 pod

kubectl apply -f liveness.yaml

观察 pod

kubectl get po -w

kubelet 在容器启动 3 秒后,开始健康检测,启动超过 10 秒之后,健康检查会失败,kubelet 会重启容器。

为容器设置启动时要执行的命令和参数

创建 pod 时,可以为其下的容器设置启动时要执行的命令及参数。通过 command 字段设置命令,通过 args 字段设置命令的参数。

cd ~
vim args.yaml
apiVersion: v1
kind: Pod
metadata:
  name: command-demo
  labels:
    purpose: demonstrate-command
spec:
  containers:
  - name: command-demo-container
    image: debian
    command: ["printenv"]
    args: ["HOSTNAME", "KUBERNETES_PORT"]
  restartPolicy: OnFailure

以上,设置了一个命令,两个参数。命令会打印两个参数。

restartPolicy: OnFailure 字段的意思是:失败了才会重启

创建 pod

kubectl apply -f args.yaml
kubectl get po
kubectl logs command-demo

可以看到,打印了两个参数:

command-demo
tcp://10.1.0.1:443

使用环境变量设置参数

kubectl delete -f args.yaml
vim args.yaml
apiVersion: v1
kind: Pod
metadata:
  name: command-demo
  labels:
    purpose: demonstrate-command
spec:
  containers:
  - name: command-demo-container
    image: debian
    env:
    - name: MESSAGE
      value: "hello world"
    command: ["/bin/echo"]
    args: ["$(MESSAGE)"]
  restartPolicy: OnFailure
kubectl apply -f args.yaml
kubectl get po -w
kubectl logs command-demo

可以看到:

hello world

为容器定义相互依赖的环境变量

可以为运行在 pod 中的容器设置相互依赖的环境变量

vim dependency-var.yaml
apiVersion: v1
kind: Pod
metadata:
  name: dependent-envars-demo
spec:
  containers:
    - name: dependent-envars-demo
      args:
        - printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=$SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=$ESCAPED_REFERENCE'\n'; 
      command:
        - sh
        - -c
      image: busybox
      env:
        - name: SERVICE_PORT
          value: "80"
        - name: SERVICE_IP
          value: "192.168.190.131"
        - name: UNCHANGED_REFERENCE
          value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
        - name: PROTOCOL
          value: "https"
        - name: SERVICE_ADDRESS
          value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
        - name: ESCAPED_REFERENCE
          value: "$$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
kubectl apply -f dependency-var.yaml
kubectl get po
kubectl logs dependent-envars-demo

可以看到:

UNCHANGED_REFERENCE=$(PROTOCOL)://192.168.190.131:80
SERVICE_ADDRESS=https://192.168.190.131:80
ESCAPED_REFERENCE=$(PROTOCOL)://192.168.190.131:80

在变量 PROTOCOL 定义之前打印 $(PROTOCOL),会打印:$(PROTOCOL)

打印 $$(PROTOCOL) 会打印:$(PROTOCOL)

为容器和 Pods 分配 CPU 资源

创建一个命名空间,以便将本次练习中创建的资源与集群的其余部分资源隔离

kubectl create namespace cpu-example

为容器指定 CPU 请求,使用字段:

resources: requests

指定 CPU 限制,使用字段:

resources: limits

创建一个 pod , pod 内有一个容器,容器请求 0.5 个 CPU ,并且限制最多使用 1 个 CPU

vim cpu-request-limit.yaml
apiVersion: v1
kind: Pod
metadata:
  name: cpu-demo
  namespace: cpu-example
spec:
  containers:
  - name: cpu-demo-ctr
    image: nginx
    resources:
      limits:
        cpu: "1"
      requests:
        cpu: "0.5"
    args:
    - -cpus
    - "2"

arg 提供了容器启动时的参数,优先级高于默认的参数,这里 -cpu "2" 告诉容器尝试使用 2 个 CPU ,但是结果并不会使用 2 个 CPU ,因为 resources: limits 限制了 CPU 个数是 1 。

安装 work2

  1. 克隆 work1 虚拟机,链接克隆或者完整克隆都可以,命名为 work2 ,克隆完成后,启动 work2 虚拟机

  2. 修改 ip :vi /etc/sysconfig/network-scripts/ifcfg-ens33

    • IPADDR=192.168.190.133
  3. 重启网络:systemctl restart network

  4. 修改 hostname hostnamectl set-hostname node2

    • 查看 hostname :hostname
  5. 重置 kubeadm

    • kubeadm reset
    • 输入:y
  6. 配置端口转发:

    • echo 1 > /proc/sys/net/ipv4/ip_forward
      echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
      
  7. 将 master 节点的 admin.conf 复制到 work2 节点,在 master 节点执行:

    • scp /etc/kubernetes/admin.conf root@node2:/etc/kubernetes/
  8. 清理环境

    • systemctl stop kubelet && systemctl stop docker && rm -rf /var/lib/cni/ && rm -rf /var/lib/kubelet/* && rm -rf /etc/cni/ && ifconfig flannel.1 down && ifconfig docker0 down && ip link delete flannel.1
      
  9. 启动 docker 和 kubernetes

    • systemctl start docker && systemctl start kubelet
  10. 在 master 节点生成 token 给 work2 节点,在 master 节点执行:

    • kubeadm token create --print-join-command
  11. 生成的 token

    • kubeadm join 192.168.190.131:6443 --token hjyu47.u4ezlgt0yuw8jwrf     --discovery-token-ca-cert-hash sha256:1271d25165cffe9623cc85980a5ac950eba7ff066149b590a509d3e09594a09d
      
  12. 复制到 work2 节点

  13. 等待半分钟至1分钟,执行:kubectl get nodes

  14. 可以看到:

    • NAME     STATUS   ROLES    AGE   VERSION
      master   Ready    master   30h   v1.19.3
      node1    Ready       27h   v1.19.3
      node2    Ready       78s   v1.19.3
      

用节点亲和性把 Pods 分配到节点

列出集群中的节点和节点的标签

kubectl get nodes --show-labels

给节点添加标签,这里区分一下:work1 是虚拟机的名字;node1 是节点的名字

kubectl label nodes  disktype=ssd

看一下这个 pod ,通过 affinity 指定了 disktypessd 的节点

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd            
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

可以通过 kubectl get pods --output=wide 查看 pod 运行在哪个节点

将 ConfigMap 中的兼职对配置为容器环境变量

vim config.yaml

下面的内容,包含 configmap 和 pod

# configmap
apiVersion: v1
kind: ConfigMap
metadata:
# configmap 的名字
  name: my-db-config
# configmap 的数据
data:
  db-url: localhost
---
# pod
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    name: myapp
spec:
  containers:
  - name: myapp
    image: busybox
    # 命令:写入环境变量
    command: ["sh","-c","env"]
    # 环境变量来自于:
    envFrom:
    # 指定 configmap
      - configMapRef:
          name: my-db-config
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"

一次创建 configmap 和 pod 两个对象

kubectl create -f configmap.yaml

可以看到:

configmap/my-db-config created
pod/myapp created

查看日志

kubectl logs myapp

可以看到 db-url=localhost 已经写入环境变量

容器 Root 用户 VS privileged

大多数容器默认以 root 身份运行,不过为了安全,默认的 root 用户实际上是映射的 root 用户,并不具备 root 用户的全部功能,如果需要更大的权限,需要使用特权用户,在运行容器的时候,加上 --privileged 参数。

[root@node1 ~]# docker run -it busybox sh
/ # whoami
root
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # hostname
af29e1748ec7
/ # sysctl kernel.hostname=Attacker
sysctl: error setting key 'kernel.hostname': Read-only file system
/ # exit
[root@node1 ~]#

上面的例子,以默认的 root 用户运行容器,在修改 hostname 的时候,提示没有权限。

下面的例子,在运行容器的时候,加上 --privileged 参数,以特权用户运行

[root@node1 ~]# docker run -it --privileged busybox sh
/ # whoami
root
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # hostname
c21cf0211dc1
/ # sysctl kernel.hostname=Attacker
kernel.hostname = Attacker
/ # hostname
Attacker
/ # exit
[root@node1 ~]#

可以看到,hostname 修改成功了。

Kubernetes 通过 Security Context 提供了相同的功能


apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true

为 Pod 创建非 Root 用户运行

创建一个设置 Security Context 的 pod

vim security.yaml
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    # 容器内所有进程都以 1000 这个用户id来运行
    runAsUser: 1000
    # 进程都以主组id 3000 来运行,所有创建的文件,也划归用户1000和主组3000;如果忽略此字段,则容器的主组id将是root(0)
    runAsGroup: 3000
    # 容器中所有进程也会是附组id2000的一部分。卷/data/demo及在该卷中创建的任何文件的属主都会是组id 2000
    fsGroup: 2000
  # 在宿主机上挂载一个空目录,存放这个 pod 里面的内容
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    # 引用挂载的目录
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    # 是否允许越出权限 
    securityContext:
      allowPrivilegeEscalation: false
kubectl create -f security.yaml
[root@node1 ~]# kubectl exec -it security-context-demo sh
/ $ id
uid=1000 gid=3000 groups=2000
/ $ ps
PID   USER     TIME  COMMAND
    1 1000      0:00 sleep 1h
    6 1000      0:00 sh
   12 1000      0:00 ps
/ $ ls
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ $ cd data/demo/
/data/demo $ ls -l
total 0
drwxrwsrwx    2 root     2000             6 Mar 19 03:35 .
/data/demo $ exit
[root@node1 ~]#

你可能感兴趣的:(02 kubernetes 的调度单元 pod)