文章目录

Kubernetes

一、集群部署(ansible方式)

部署kubernetes集群流程回顾

  1. 检查唯一性
  • mac
  • product_uuid
  1. 设置/确认 内核模块
  • 网桥
  1. 设置内核参数
  • 关于 iptables 的观察网桥流量
    /etc/sysctl.d/k8s.conf
  1. 关闭 swap 交互分区
    swapoff -a

    注释或者删除 /etc/fstab 中关于 swap 的挂载配置
    kubelet 服务器要求的必须关闭

  2. 安装容器运行时
    CRI-O
    Docker

  3. 安装 kubeadm kubelet
    可以使用阿里云镜像

  4. 拉取 kubernetes 所需组件的容器镜像

  5. 初始化集群的 master 节点

  6. 将集群的其他节点加入到 集群成员中

1-8 是每个节点都必须做的

机器要求,最少3G内存,两个处理器

1、初始化ansible

# 创建ansible运行目录
mkdir /ansible
cd /ansible
cp /etc/ansible/ansible.cfg .
# 更改ansible配置
vim ansible.cfg
inventory      = ./hosts
host_key_checking = False
# 创建主机清单
vim hosts
[k8s_master]
master
[k8s_node]
node1
node2
[k8s]
[k8s:children]
k8s_master
k8s_node

2、添加解析

vim /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
10.9.29.112 master
10.9.29.116 node1
10.9.29.117 node2
# 复制解析文件到当前目录,以供使用
cp /etc/hosts ./etc-hosts

3、使用ansible传输公钥

vim give_key.yml
---
- hosts: all
  gather_facts: no
  remote_user: root
  vars:
    ansible_ssh_pass: 1
  tasks:
  - name: Set authorized key taken from file
    authorized_key:
      user: root
      state: present
      key: "{{ lookup('file', '/root/.ssh/id_rsa.pub') }}"
...
# 先生成公钥
ssh-keygen

# 传输
ansible-playbook give_key.yml

4、发送hosts解析及更改主机名

vim give_host.yml
---
- name: 同步所有节点的 /etc/hosts 文件 并且设置主机名
  hosts: k8s
  gather_facts: no
  tasks:
    - name: 同步 hosts 文件
      copy: src=etc-hosts dest=/etc/hosts
    - name: 设置各自的主机名
      shell:
        cmd: hostnamectl set-hostname "{{ inventory_hostname }}"
      register: sethostname
    - name: 验证是否成功设置了主机名
      debug: var=sethostname.rc
...
ansible-playbook give_host.yml

5、部署前的环境检查

# 基于 Python2.7
vim check-port.py
#!/bin/env python
#coding:utf-8
import re
import subprocess
import socket

hostname = socket.gethostname()

ports_set = set()
if 'master' in hostname:
    check_ports = {"6443", "10250", "10251", "102502", "2379", "2380"}
else:
    check_ports = {str(i) for i in xrange(30000, 32768) }
    check_ports.add("10250")

r = subprocess.Popen("ss -nta", stdout=subprocess.PIPE,shell=True)
result = r.stdout.read()

for line in result.splitlines():
	if re.match('^(ESTAB|LISTEN|SYN-SENT)', line):
	    line = line.split()[3]
	    port = line.split(':')[-1]
	    ports_set.add(port)

used_ports = check_ports & ports_set
used_ports = ' '.join(used_ports)
if used_ports:
	print("这些端口已使用: %s" % used_ports)
else:
	print("端口未占用")

vim before-you-begin.yml
---
- name: 开始部署集群之前的检查和设置
  hosts: k8s
  gather_facts: no
  tasks:
    - name: 配置禁用 SELinux
      shell: |
        setenforce 0;
        sed -ri '/^SELINUX=/ c SELINUX=disabled' /etc/selinux/config
      tags:
        - swap

    - name: 关闭交互分区
      shell:
        cmd: swapoff -a; sed -ri 's/.*swap.*/#&/g' /etc/fstab
        warn: no
      tags:
        - swap

    - name: 创建模块配置文件 /etc/modules-load.d/k8s.conf
      blockinfile:
        path: /etc/modules-load.d/k8s.conf
        create: yes
        block: |
          br_netfilter

    - name: 确保节点上的 iptables 能够正确地查看桥接流量
      blockinfile:
        path: /etc/sysctl.d/k8s.conf
        create: yes
        block: |
          net.bridge.bridge-nf-call-ip6tables = 1
          net.bridge.bridge-nf-call-iptables = 1

    - name: 执行加载模块的命令
      shell: modprobe br_netfilter

    - name: 检查 SELinux and Swap
      shell: |
        echo "主机名是"$HOSTNAME > /tmp/host-info;
        echo "selinux的状态是" `getenforce` >> /tmp/host-info;
        echo "swap分区的状态是:" `free -m |grep 'Swap'` >> /tmp/host-info;
        echo "配置模块:" `lsmod | grep br_netfilter` >> /tmp/host-info;
        sysctl --system | grep 'k8s.conf' -A 2  >> /tmp/host-info;

    - name: 获取 mac 信息并写入信息文件
      shell: |
        host=$(hostname);
        ip link | awk -v host=$host '/link\/ether/ {print $2, host}' >> /tmp/host-info ;
        echo "---------------------------" >> /tmp/host-info

    - name: 获取比对报告
      fetch:
        src: /tmp/host-info
        dest: ./
        
    - name: 执行端口检查脚本
      script: ./check-port.py
      register: ret
    - debug: var=item
      loop: "{{ ret.stdout_lines }}"
...
ansible-playbook before-you-begin.yml

# 查看主机信息,确保每个都正确,product_uuid和mac唯一
cat {master,node1,node2}/*/*

可能还要添加的参数

vim /etc/sysctl.conf
net.ipv4.ip_forward=1

vim /etc/sysctl.d/k8s.conf
vm.swappiness=0

6、部署docker

# 这里是先在某个节点上下载 docker 所需要的所有 rpm 包,之后再把这些包传输到 Ansible 机器上的某个位置。接着把 rpm 包,从 Ansible 机器上分发到每个节点(除了刚才已经下载 rpm 包的节点),最后每个节点使用 yum localinstall 命令从本地安装 docker
# 这样做可以减少流量的消耗,带宽的占用
mkdir -p docker/file
vim docker/deploy-docker.yml
---
- name: deploy docker
  hosts: k8s
  gather_facts: no
  vars:
    pkg_dir: /yum-pkg
    pkgs:
      - device-mapper-persistent-data
      - lvm2
      - containerd.io-1.2.13
      - docker-ce-19.03.11
      - docker-ce-cli-19.03.11


    # 变量 download_host 需要手动设置
    # 且值需要是此 playbook 目标主机中的一个
    # 需要写在 inventory 文件中的名称
    download_host: "master"
    local_pkg_dir: "{{ playbook_dir }}/{{ download_host }}"

  tasks:
    - name: 测试使用 -e 是否覆盖了变量
      debug:
        msg: "{{ local_pkg_dir }} {{ download_host }}"
      tags:
        - deploy
        - test

    - name: "只需要给 {{ download_host }}安装仓库文件"
      when: inventory_hostname == download_host
      get_url:
        url: https://download.docker.com/linux/centos/docker-ce.repo
        dest: /etc/yum.repos.d/docker-ce.repo
      tags:
        - deploy

    - name: 创建存放 rmp 包的目录
      when: inventory_hostname == download_host
      file:
        path: "{{ pkg_dir }}"
        state: directory
      tags:
        - deploy

    - name:  正在下载软件包
      when: inventory_hostname == download_host
      yum:
        name: ["docker-ce", "docker-ce-cli", "containerd.io"]
        download_only: yes
        download_dir: "{{ pkg_dir }}"
      tags:
        - deploy

    - name: 获取下载目录 "{{ pkg_dir }}" 中的文件列表
      when: inventory_hostname == download_host
      shell: ls -1 "{{ pkg_dir }}"
      register: files
      tags:
        - deploy

    - name: 把远程主机下载的软件包传输到 ansible 本地
      when: inventory_hostname == download_host
      fetch:
        src: "{{ pkg_dir }}/{{ item }}"
        dest: ./
      loop: "{{files.stdout_lines}}"
      tags:
        - deploy

    - name: 传输 rpm 包到远程节点
      when: inventory_hostname != download_host
      copy:
        src: "{{ local_pkg_dir }}{{ pkg_dir }}"
        dest: "/"
      tags:
        - deploy

    - name: 正在执行从本地安装软件包
      shell:
        cmd: yum -y localinstall *
        chdir: "{{ pkg_dir }}"
        warn: no
      async: 600
      poll: 0
      register: yum_info
      tags:
        - deploy

    - name: 打印安装结果
      debug: var=yum_info.ansible_job_id
      tags:
        - deploy

    - name: 设置 /etc/docker/daemon.json
      copy: src=file/daemon.json dest=/etc/docker/daemon.json
      notify: restart docker
      tags:
        - start
        - update

    - name: 启动 docker
      systemd:
        name: docker
        enabled: yes
        state: started
      tags:
        - start
  handlers:
    - name: restart docker
      systemd:
        name: docker
        state: restarted
...

该文件传输失败???????????? 后期解决

vim docker/file/daemon.json
{
   "registry-mirrors": ["https://vomr0any.mirror.aliyuncs.com"],
   "exec-opts": ["native.cgroupdriver=systemd"],
   "log-driver": "json-file",
   "log-opts": {
      "max-size": "100m"
   },
   "storage-driver": "overlay2"
}
ansible-playbook docker/deploy-docker.yml  -t deploy

ansible-playbook docker/deploy-docker.yml  -t start

7、部署非高可用kubernetes集群

vim deploy-kubeadm.yml
---
- name: Deploy  kubeadm  kubelet kubectl
  hosts: k8s
  gather_facts: no
  vars:
    pkg_dir: /kubeadm-pkg
    pkg_names: ["kubelet", "kubeadm", "kubectl"]

    # 变量 download_host 需要手动设置
    # 且值需要是此 playbook 目标主机中的一个
    # 需要写在 inventory 文件中的名称
    download_host: "master"
    local_pkg_dir: "{{ playbook_dir }}/{{ download_host }}"

  tasks:
    - name: 测试使用 -e 是否设置并覆盖了变量
      debug:
        msg: "{{ local_pkg_dir }} {{ download_host }}"
      tags:
        - deploy
        - test

    - name: "只需要给 {{ download_host }}安装仓库文件"
      when: inventory_hostname == download_host
      copy:
        src: file/kubernetes.repo
        dest: /etc/yum.repos.d/kubernetes.repo
      tags:
        - deploy

    - name: 创建存放 rmp 包的目录
      when: inventory_hostname == download_host
      file:
        path: "{{ pkg_dir }}"
        state: directory
      tags:
        - deploy

    - name:  下载软件包
      when: inventory_hostname == download_host
      yum:
        name: "{{ pkg_names }}"
        download_only: yes
        download_dir: "{{ pkg_dir }}"
      tags:
        - deploy

    - name: 获取下载目录 "{{ pkg_dir }}" 中的文件列表
      when: inventory_hostname == download_host
      shell: ls -1 "{{ pkg_dir }}"
      register: files
      tags:
        - deploy

    - name: 把远程主机下载的软件包传输到 ansible 本地
      when: inventory_hostname == download_host
      fetch:
        src: "{{ pkg_dir }}/{{ item }}"
        dest: ./
      loop: "{{files.stdout_lines}}"
      tags:
        - deploy

    - name: 传输 rpm 包到远程节点
      when: inventory_hostname != download_host
      copy:
        src: "{{ local_pkg_dir }}{{ pkg_dir }}"
        dest: "/"
      tags:
        - deploy

    - name: 正在执行从本地安装软件包
      shell:
        cmd: yum -y localinstall *
        chdir: "{{ pkg_dir }}"
        warn: no
      async: 600
      poll: 0
      register: yum_info
      tags:
        - deploy

    - name: 打印安装结果
      debug: var=yum_info.ansible_job_id
      tags:
        - deploy
...
mkdir file
vim file/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
ansible-playbook deploy-kubeadm.yml

kubelet的配置文件为 /var/lib/kubelet/config.yaml

8、拉取镜像

因为原镜像地址为国外的地址,国内无法访问,故,你可以

  • 创建一台国外的云,下载对应的镜像,保存至本地(贼几把安全)
  • 在网上胡求找一个(安全指数0颗星)

因为我有一个已经打包好的镜像,所以我就直接用了

# 查看kubernetes依赖的镜像
kubeadm config images list

9、初始化master节点

# 不支持高可用的集群初始化
kubeadm init --kubernetes-version=v1.20.4 --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.9.29.112 --ignore-preflight-errors=Swap
# 支持高可用的集群初始化
kubeadm init --kubernetes-version=v1.20.4 --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=masterIP --control-plane-endpoint=kube-lab  --ignore-preflight-errors=Swap --upload-certs
  • --kubernetes-version=v1.17.2

    版本号,根据自己的情况更改,一般应该和 kubeadm 的版本一致

    通过如下命令获得

    kubeadm version

    输出的 GitVersion:"v1.20.4" 就是版本号了

  • --pod-network-cidr=10.244.0.0/16

​ pod 使用的网络,可以自定义,这个根据自己的情况修改,不修改也可以

​ 好像是固定的

  • --apiserver-advertise-address=192.168.1.200

​ master 节点的有效 IP 或者可以被解析的 DNS 名称,需要是 master 节点的有效网卡地址,比如 ens33, eth0 等。

  • --ignore-preflight-errors=Swap

​ 忽略检查 Swap 时候的报错

  • --control-plane-endpoint

    负载均衡的地址,支持dns解析名或者IP,添加该选项后支持高可用,如果使用dns 记得该dns一定要可以被解析

  • --upload-certs

    配合高可用使用,可以自动上传证书

# 初始化成功后,会有以下信息,复制后直接在node节点使用即可加入集群
kubeadm join 10.9.29.112:6443 --token en6s67.08rnsg20dc5t8z4n \
    --discovery-token-ca-cert-hash sha256:7d034842b9ee7a6b17d9ce7088839f4570da1c61b29922f28e72b855c10003cc 
# 如果是高可用,还会有一条,这个使用后会添加一个master进入集群
kubeadm join kub-lab:6443 --token s2ccws.tzb7v4olicidp032 \
    --discovery-token-ca-cert-hash sha256:29a2b437f79c5e4958c3d73e6c64fe0a4df24f0f3bcabd5ced28392d7a882e10 \
    --control-plane --certificate-key c0a9a1c4a067b20dca95447f809d95c973220244c740a47f71d5302e0a759ea7

注意,token有效期为24小时,certificate-key有效期为2小时,过期无法使用

10、配置kubelet

# 假如你希望用一个普通用户运行集群,执行如下操作(root也推荐使用这个)
rm -rf $HOME/.kube
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 假如你使用  root 用户运行集群,执行如下命令
export KUBECONFIG=/etc/kubernetes/admin.conf
# 查看当前node节点
kubectl get nodes

11、配置网络插件

kubernetes可以使用的网络插件有很多,我选择的是Flannel

cd ~ && mkdir flannel && cd flannel
wget https://github.com/flannel-io/flannel/blob/master/Documentation/kube-flannel.yml
# 修改内容
# 在flanneld启动参数加上`--iface=<iface-name>`
# 例如
  containers:
  - name: kube-flannel
    image: registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64
    command:
    - /opt/bin/flanneld

    args:
    - --ip-masq
    - --kube-subnet-mgr
    - --iface=ens33  # 可以添加多个,但是必须有一个能用的
    - --iface=eth0 
# 如果镜像没办法下载,可以更换下载源
registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64
# 启动
kubectl apply -f ~/flannel/kube-flannel.yml
# 查看配置进度
kubectl get pods --namespace kube-system

12、将node加入集群

# 执行上面得到的这条命令即可
kubeadm join 10.9.29.112:6443 --token en6s67.08rnsg20dc5t8z4n \
    --discovery-token-ca-cert-hash sha256:7d034842b9ee7a6b17d9ce7088839f4570da1c61b29922f28e72b855c10003cc 
# 查看pods
kubectl get pods -n kube-system
》
NAME               						READY  STATUS       RESTARTS  AGE
coredns-6c66ffc55b-l76bq   		1/1   Running       0      16m
coredns-6c66ffc55b-zlsvh    	1/1   Running       0      16m
etcd-node1               			1/1   Running       0      16m
kube-apiserver-node1       		1/1   Running       0      16m
kube-controller-manager-node1 1/1   Running   		0      15m
kube-flannel-ds-sr6tq       	0/1   CrashLoopBackOff  6      7m12s
kube-flannel-ds-ttzhv       	1/1   Running       0      9m24s
kube-proxy-nfbg2          		1/1   Running       0      7m12s
kube-proxy-r4g7b          		1/1   Running       0      16m
kube-scheduler-node1       		1/1   Running       0      16m

# 遇到异常状态0/1的pod长时间启动不了可删除它等待集群创建新的pod资源
kubectl delete pod kube-flannel-ds-sr6tq -n kube-system
》
pod "kube-flannel-ds-sr6tq" deleted
# 查看节点状态
kubectl get nodes -n kube-system

13、清理集群

如果做错了怎么办呢

# 在删除节点之前,请重置 kubeadm 安装的状态
kubeadm reset

# 在主节点运行
kubectl drain <node name> --delete-local-data --force --ignore-daemonsets

kubectl delete node <node name>

14、创建一个简单的实例

# 创建一个pods
mkdir nginx
cd nginx
vim nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.8
        ports:
        - containerPort: 80

# 运行
kubectl apply -f nginx.yml


# 查看pods状态
kubectl get pods -l app=nginx

# 查看某个pods的具体状态
kubectl describe pods pods名

二、kubectl的基本命令

  • 查看各种资源
kubectl get 资源类型
# 这里的资源类型可以是任意的k8s可以创建的,例如pod、deployment、configmap、serviceaccount等等等等

# 查看具体某个资源的具体信息,错误排查就用他,例如查看pod信息,在event中查看他的报错信息来进行下一步的解决步骤
kubectl describe 资源类型 具体的资源名
  • 查看帮助文档
kubectl explain 资源类型

# 例如,查看pod的帮助文档,还可以查看pod 中的任何一个选项的具体信息,继续往下.就完事了
kubectl explain pod.apiVersion

# 一些基本命令的用法查询,可以一层一层查到你懂为止
kubectl 命令 -h
  • 进入容器
kubectl exec pod名 -c 容器名 -it -- bash -il
  • 升级镜像
# 升级deployment的镜像
kubectl set deploy/deployment名 容器名=镜像:tag
  • 查看发布状态
# 查看发布状态
kubectl rollout status deploy/deployment名

# 查看历史版本信息
kubectl rollout history deploy/test-dep

# 查看具体版本的内容
kubectl rollout history deploy/deployment名 --revision 1
  • 为node打标签
kubectl label nodes <node-name> <label-key>=<label-value>

三、yml文件格式详解

kubernetes的yml文件书写有个规律,所有的字段命名方式都是驼峰式命名,多个单词组成的第一个单词首字母小写后面的每个单词首字母都大写,需要注意的是一些特殊的情况,例如kind中的资源类型是首字母大写的

1、apiVersion

不同类型的资源可用的apiversion也不同,具体可以使用explain命令查看具体资源可用的apiversion

2、kind

指定了这个 API 对象的类型(Type),是一个 Pod,根据实际情况,此处资源类型可以是Deployment、Job、Ingress、Service等。

3、metadata

包含Pod的一些meta信息,比如名称、namespace、标签等信息。重新赋值可以覆盖原有的信息
可以使用的字段有
name、namespace、labels、annotations

4、spec

specification of the resource content 指定该资源的内容,包括一些container,storage,volume以及其他Kubernetes需要的参数,以及诸如是否在容器失败时重新启动容器的属性。可在特定Kubernetes API找到完整的Kubernetes Pod的属性。
  • containers
# "Containers"和"Init Containers"这两个字段都属于 Pod 对容器的定义,内容也完全相同,只是 Init Containers 的生命周期,会先于所有的 Containers,并且严格按照定义的顺序执行。
# k8s 对 Container 的定义,和 Docker 相比并没有什么太大区别。Docker中Image(镜像)、Command(启动命令)、workingDir(容器的工作目录)、Ports(容器要开发的端口),以及 volumeMounts(容器要挂载的 Volume)都是构成container 的主要字段。

在spec的containers属性中,可设置的属性有:
name、image、command、args、workingDir、
ports、env、resource、volumeMounts、
livenessProbe、readinessProbe、livecycle、
terminationMessagePath、imagePullPolicy、securityContext、
stdin、stdinOnce、tty
ImagePullPolicy 字段:
# 定义镜像的拉取策略。之所以是一个 Container 级别的属性,是因为容器镜像本来就是 Container 定义中的一部分。
# 默认值: Always  表示每次创建 Pod 都重新拉取一次镜像。
# 当容器的镜像是类似于 nginx 或者 nginx:latest 这样的名字时,imagePullPolicy 也会被认为 Always。
# 可用值还有:Never 永远不会主动拉取这个镜像, 或者 IfNotPresent 只在宿主机上不存在这个镜像时才拉取。
Lifecycle 字段:
# 定义 Container Lifecycle Hooks。作用是在容器状态发生变化时触发一系列"钩子"。
# 例子: 在这个例子中,容器成功启动之后,在 /usr/share/message 里写入了一句"欢迎信息"(即 postStart 定义的操作)。而在这个容器被删除之前,我们则先调用了 nginx 的退出指令(即 preStop 定义的操作),从而实现了容器的"优雅退出"。
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: daocloud.io/library/nginx:latest
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:
        exec:
          command: ["/usr/sbin/nginx","-s","quit"]
          
# postStart:是在容器启动后,立刻执行一个指定的操作。
# 注意:postStart 定义的操作,虽然是在 Docker 容器 ENTRYPOINT 执行之后,但它并不严格保证顺序。也就是说,在 postStart 启动时,ENTRYPOINT 有可能还没有结束。如果 postStart执行超时或者错误,k8s 会在该 Pod 的 Events 中报出该容器启动失败的错误信息,导致 Pod 也处于失败的状态。
# preStop:是容器被杀死之前(比如,收到了 SIGKILL 信号)。
# 注意:preStop 操作的执行,是同步的。所以,它会阻塞当前的容器杀死流程,直到这个 Hook 定义操作完成之后,才允许容器被杀死,这跟 postStart 不一样。

一个pod的生命周期
# Pod 生命周期的变化,主要体现在 Pod API 对象的 Status 部分,这是除了 Metadata 和 Spec 之外的第三个重要字段。其中,pod.status.phase,就是 Pod 的当前状态,有如下几种可能的情况:
Pending
### 此状态表示Pod 的 YAML 文件已经提交给了 Kubernetes,API 对象已经被创建并保存在 Etcd 当中。但这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。
Running
### 此状态表示Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
Succeeded
### 此状态表示 Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
Failed
### 此状态表示 Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状态的出现,意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。
Unknown
### 这是一个异常状态,表示 Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver。这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。

Pod 对象的 Status 字段,还可以再细分出一组 Conditions。这些细分状态的值包括:PodScheduled、Ready、Initialized,以及 Unschedulable,它们主要用于描述造成当前 Status 的具体原因是什么。比如, Pod 当前的 Status 是 Pending,对应的 Condition 是 Unschedulable,这表示它的调度出现了问题。比如, Ready 这个细分状态表示 Pod 不仅已经正常启动(Running 状态),而且已经可以对外提供服务了。这两者之间(Running 和 Ready)是有区别的,仔细思考一下。Pod 的这些状态信息,是判断应用运行情况的重要标准,尤其是 Pod 进入了非"Running"状态后,一定要能迅速做出反应,根据它所代表的异常情况开始跟踪和定位,而不是去手忙脚乱地查阅文档。

5、NodeName

一旦 Pod 的这个字段被赋值,k8s就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。这个字段一般由调度器负责设置,用户也可以设置它来"骗过"调度器,这个做法一般是在测试或者调试的时候才会用到。

6、HostAliases

定义 Pod 的 hosts 文件(比如 /etc/hosts)里的内容
用法:
 apiVersion: v1
    kind: Pod
    ...
    spec:
      hostAliases:
      - ip: "10.1.2.3"
        hostnames:
        - "foo.remote"
        - "bar.remote"
    ...
pod启动后,/etc/hosts 的内容将如下所示:
# cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.2.3 foo.remote
10.1.2.3 bar.remote

# 注意:在 k8s 中,如果要设置 hosts 文件里的内容,一定要通过这种方法。否则,如果直接修改了 hosts 文件,在 Pod 被删除重建之后,kubelet 会自动覆盖掉被修改的内容。

7、跟linux namespace有关的属性

# 凡是跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的,凡是 Pod 中的容器要共享宿主机的 Namespace,也一定是 Pod 级别的定义
# 原因:Pod 的设计,就是要让它里面的容器尽可能多地共享 Linux Namespace,仅保留必要的隔离和限制能力。这样,Pod 模拟出的效果,就跟虚拟机里程序间的关系非常类似了。
# 例子:
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  shareProcessNamespace: true  # 表示这个 Pod 里的容器要共享 PID Namespace
  hostNetwork: true     #   定义了共享宿主机的 Network、IPC 和 PID Namespace,这样,此 Pod 里的所有容器
  hostIPC: true        # 会直接使用宿主机的网络、直接与宿主机进行 IPC 通信、看到宿主机里正在运行的所有进程。
  hostPID: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true  # 等同于设置了 docker run 里的 -it(-i 即 stdin,-t 即 tty)参数

四、kubernetes的其他资源类型

1、secret

secret用来保存小片敏感数据的k8s资源,例如密码,token,或者秘钥。这类数据当然也可以存放在Pod或者镜像中,但是放在Secret中是为了更方便的控制如何使用数据,并减少暴露的风险。

用户可以创建自己的secret,系统也会有自己的secret。Pod需要先引用才能使用某个secret

Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患

注意:像这样创建的 Secret 对象,它里面的内容仅仅是经过了转码,并没有被加密。生产环境中,需要在 Kubernetes 中开启 Secret 的加密插件,增强数据的安全性。

创建secret

內建的Secrets:由ServiceAccount创建的API证书附加的秘钥,k8s自动生成的用来访问apiserver的Secret,所有Pod会默认使用这个Secret与apiserver通信.

自己创建secret有两种方式

  • 使用kubectl create secret命令

  • yaml文件创建Secret

使用命令行的方式创建
# 命令格式,文件路径可以使用key代替原有的名字,会自动将文件的内容进行base64编码
kubectl create secret generic secret的名字 --from-file=文件路径
# generic 是指从本地文件中创建secret,其他可选参数可以自己去查看
# 例子
kubectl create secret generic my-secret --from-file=path/to/bar
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-file=ssh-publickey=path/to/id_rsa.pub
# 查看secret
kubectl get secret

# 想要查看具体的内容
kubectl get secrets secret名 -o json
yaml方式创建
# 使用base64先对数据进行编码                            # base64解码
echo -n 'admin' | base64                             # echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
YWRtaW4=                                             # 1f2d1e2e67df
echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm

# 编写yml文件
vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

# 创建
kubectl create -f ./secret.yaml
使用secret

secret可以作为数据卷挂载或者作为环境变量暴露给Pod中的容器使用,也可以被系统中的其他资源使用。比如可以用secret导入与外部系统交互需要的证书文件等。

pod中使用secret—挂载数据卷
  • 值得注意的一点是,以文件的形式挂载到容器中的secret,他们的值已经是经过base64解码的了,可以直接读出来使用。

  • 被挂载的secret内容自动更新,也就是如果修改一个Secret的内容,那么挂载了该Secret的容器中也将会取到更新后的值,但是这个时间间隔是由kubelet的同步时间决定的。最长的时间将是一个同步周期加上缓存生命周期(period+ttl)

    • 特例:以subPath(https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath)形式挂载到容器中的secret将不会自动更新
# 示例
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"  # pod内部的目录
      readOnly: true
  volumes:
  - name: foo
    secret:   # 将secret作为数据卷挂载
      secretName: mysecret
# 检查,进入pod
kubectl exec mypod -c mypod -it -- bash -il
# 查看。etc目录下是否有foo目录,foo目录下是否有数据
  • 映射secret key到指定的路径
# 例子
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username

# username会被映射到/etc/foo/my-group/my-username而不是/etc/foo/username
  • Secret文件权限

    可以指定secret文件的权限,类似linux系统文件权限,如果不指定默认权限是0644,等同于linux文件的-rw-r–r–权限

# 例子
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 256  # 由于JSON不支持八进制数字,因此用十进制数256表示0400,如果用yaml格式的文件那么就很自然的使用八进制了

# 同理可以单独指定某个key的权限
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 511  # 对应八进制的777

环境变量的形式使用secret
# 例子
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME  # 环境变量的变量名key
        valueFrom:            # 环境变量的来源
          secretKeyRef:      # 来自secret
            name: mysecret   # secret的名字
            key: username    # 使用的key
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never

使用imagePullSecrets

创建一个专门用来访问镜像仓库的secret,当创建Pod的时候由kubelet访问镜像仓库并拉取镜像,具体描述文档在 这里设置自动导入的imagePullSecrets,可以手动创建一个,然后在serviceAccount中引用它。所有经过这个serviceAccount创建的Pod都会默认使用关联的imagePullSecrets来拉取镜像,参考文档自动挂载手动创建的Secret参考文档:https://kubernetes.io/docs/tasks/inject-data-application/podpreset/

使用secret的注意事项
  • 需要被挂载到Pod中的secret需要提前创建,否则会导致Pod创建失败。通过secretKeyRef引用一个不存在你secret key会导致pod创建失败
  • secret是有命名空间属性的,只有在相同namespace的Pod才能引用它
  • 单个Secret容量限制的1Mb,这么做是为了防止创建超大的Secret导致apiserver或kubelet的内存耗尽。但是创建过多的小容量secret同样也会耗尽内存,这个问题在将来可能会有方案解决
  • kubelet只支持由API server创建出来的Pod中引用secret,使用特殊方式创建出来的Pod是不支持引用secret的,比如通过kubelet的–manifest-url参数创建的pod,或者–config参数创建的,或者REST API创建的。
Pod中区分生产和测试证书
# 就是创建两套不同的用户名和密码给两个不同的pod使用
# 创建2种不同的证书,分别用在生产和测试环境
kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11

kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests

# 创建2个不同的Pod
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: daocloud.io/library/nginx
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"



# 以“.”开头的key可以产生隐藏文件
kind: Secret
apiVersion: v1
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo=
---
kind: Pod
apiVersion: v1
metadata:
  name: secret-dotfiles-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: dotfile-secret
  containers:
  - name: dotfile-test-container
    image: k8s.gcr.io/busybox
    command:
    - ls
    - "-l"
    - "/etc/secret-volume"
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"
# 会在挂载目录下产生一个隐藏文件,/etc/secret-volume/.secret-file

应用实例
使用secret指定私有仓库
# 创建secret
kubectl create secret docker-registry regcred --docker-server=10.11.67.119 --docker-username=diange --docker-password=QianFeng@123
# --docker-server 是私有仓库地址
# --docker-username 是私有仓库用户名
# --docker-password 是私有仓库对用用户名的密码

# 创建pod
cat nginx.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: xingdian
  labels:
    app: xingdian
spec:
  containers:
    - name: diandian
      image: 10.11.67.119/xingdian/nginx@sha256:2963fc49cc50883ba9af25f977a9997ff9af06b45c12d968b7985dc1e9254e4b 
      ports:
      - containerPort: 80
  imagePullSecrets:
    - name: regcred
存放数据库的 Credential 信息
cat test-projected-volume.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume 
spec:
  containers:
  - name: test-secret-volume
    image: busybox
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass

# 定义了一个容器,它声明挂载的 Volume是 projected 类型 , 并不是常见的 emptyDir 或者 hostPath 类型,而这个 Volume 的数据来源,则是名为 user 和 pass 的 Secret 对象,分别对应的是数据库的用户名和密码。

2、configmap

  • 用来存储配置文件的kubernetes资源对象,所有的配置内容都存储在etcd中。ConfigMap与 Secret 类似。

  • ConfigMap 保存的是不需要加密的、应用所需的配置信息。

  • ConfigMap 的用法几乎与 Secret 完全相同:可以使用 kubectl create configmap 从文件或者目录创建 ConfigMap,也可以直接编写 ConfigMap 对象的 YAML 文件。

  • 删除configmap后原pod不受影响;然后再删除pod后,重启的pod的events会报找不到cofigmap的volume;

  • pod起来后再通过kubectl edit configmap …修改configmap,过一会pod内部的配置也会刷新。在容器内部修改挂进去的配置文件后,过一会内容会再次被刷新为原始configmap内容

创建configmap
使用命令行指定
# --from-literal 直接指定配置信息
kubectl create configmap test-config1 --from-literal=db.host=10.5.10.116 --from-literal=db.port='3306'

# --from-file 可以指定一个文件,也可以指定一个目录,指定目录时只会识别其中的文件,忽略子目录
kubectl create configmap test-config2 --from-file=./app.properties
使用yml文件创建
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: test4
  namespace: default
data:
  cache_host: memcached-gcxt
  cache_port: "11211"
  chache_prefix: gcxt
  my.cnf: |
    [mysqld]
    log-bin=mysql-bin
    haha=hehe
使用configmap
通过环境变量使用
# 指定一个key为环境变量
cat testpod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: daocloud.io/library/nginx
      env:
        - name: SPECIAL_LEVEL_KEY   //这里是容器里设置的新变量的名字
          valueFrom:
            configMapKeyRef:
              name: special-config    //这里是来源于哪个configMap
              key: special.how           //configMap里的key
        - name: SPECIAL_TYPE_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.type
  restartPolicy: Never


# 将所有的key/value都变为环境变量
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: daocloud.io/library/nginx
      envFrom:
      - configMapRef:
          name: special-config
  restartPolicy: Never

作为volume挂载
# 例子
apiVersion: v1
kind: Pod
metadata:
  name: test-configmap1
spec:
  containers:
  - name: test-container
    image: nginx:1.8
    env:
    - name: DB_host   # 变量的变量名key
      valueFrom:      # 变量来源
        configMapKeyRef: # 来自configmap
          name: test1   # configmap的名字
          key: db.host  # configmap的key
    - name: DB_port
      valueFrom:
        configMapKeyRef:   # 绑定configmap为数据卷
          name: test1
          key: db.port   # 可以指定key,如果不指定会挂载所有的
  restartPolicy: Never


3、downward API

用于在容器中获取 POD 的基本信息,kubernetes原生支持Downward API提供了两种方式用于将 POD 的信息注入到容器内部:

  • 环境变量:用于单个变量,可以将 POD 信息和容器信息直接注入容器内部。

  • Volume挂载:将 POD 信息生成为文件,直接挂载到容器内部中去。

注意:Downward API 能够获取到的信息,一定是 Pod 里的容器进程启动之前就能够确定下来的信息。而如果你想要获取 Pod 容器运行后才会出现的信息,比如,容器进程的 PID,那就肯定不能使用 Downward API 了,而应该考虑在 Pod 里定义一个 sidecar 容器

环境变量的方式
# 注意: POD 的 name 和 namespace 属于元数据,是在 POD 创建之前就已经定下来了的,所以使用 metata 获取就可以了,但是对于 POD 的 IP 则不一样,因为POD IP 是不固定的,POD 重建了就变了,它属于状态数据,所以使用 status 去获取。
vim test-env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
    name: test-env-pod
    namespace: kube-system
spec:
    containers:
    - name: test-env-pod
      image: daocloud.io/library/nginx:latest
      env:
      - name: POD_NAME
        valueFrom:   # 使用valueFrom方式设置env的值
          fieldRef:
            fieldPath: metadata.name
      - name: POD_NAMESPACE
        valueFrom:   
          fieldRef:
            fieldPath: metadata.namespace
      - name: POD_IP
        valueFrom:
          fieldRef:
            fieldPath: status.podIP
volume挂载
apiVersion: v1
kind: Pod
metadata:
    name: test-volume-pod
    namespace: kube-system
    labels:
        k8s-app: test-volume
        node-env: test
    annotations:
        build: test
        own: qikqiak
spec:
    containers:
    - name: test-volume-pod-container
      image: daocloud.io/library/nginx:latest
      volumeMounts:
      - name: podinfo
        mountPath: /etc/podinfo
    volumes:
    - name: podinfo
      downwardAPI:          # 注意这里
        items:
        - path: "labels"
          fieldRef:
            fieldPath: metadata.labels
        - path: "annotations"
          fieldRef:
            fieldPath: metadata.annotations
目前支持的字段
# 使用 fieldRef 可以声明使用:
spec.nodeName - 宿主机名字
status.hostIP - 宿主机 IP
metadata.name - Pod 的名字
metadata.namespace - Pod 的 Namespace
status.podIP - Pod 的 IP
spec.serviceAccountName - Pod 的 Service Account 的名字
metadata.uid - Pod 的 UID
metadata.labels['<KEY>'] - 指定 <KEY> 的 Label 值
metadata.annotations['<KEY>'] - 指定 <KEY> 的 Annotation 值
metadata.labels - Pod 的所有 Label
metadata.annotations - Pod 的所有 Annotation
# 使用 resourceFieldRef 可以声明使用:
容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request

五、RBAC(未完成)

1、RBAC详解

Service Account为服务提供了一种方便的认证机制,但它不关心授权的问题。可以配合RBAC来为Service Account鉴权,在Kubernetes中,授权有6种模式。

  • ABAC(基于属性的访问控制)
  • RBAC(基于角色的访问控制)
  • Webhook
  • Node
  • AlwaysDeny(一直拒绝)
  • AlwaysAllow(一直允许)

要启用RBAC,使用–authorization-mode=RBAC启动API Server。

2、定义角色

通过yml定义role

描述"default"命名空间中的一个Role对象的定义,用于授予对pod的读访问权限

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: song
  namespace: default
rules: 
- apiGroups: [""]        # 空字符串""表明使用core API group
  resources: ["pods"]    # 定义对应的资源类型
  verbs: ["get","watch","list"]  # role的权限

大多数资源由代表其名字的字符串表示,如 “pods”,就像它们出现在相关API endpoint的URL中一样。然而,有一些Kubernetes API还包含了 “子资源”。这种情况下,“pods” 是命名空间资源,而 “log” 是pods的子资源。

为了在RBAC角色中表示出这一点,需使用斜线来划分 资源 与 子资源。

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]    #表示授予读取pods下log的权限
  verbs: ["get", "list"]
通过yml定义clusterrole

ClusterRole定义可用于授予用户对某一特定命名空间,或者所有命名空间中的secret(取决于其绑定方式)的读访问权限

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dong
  namespace: default
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get","watch","list"]
通过命令行定义

3、角色绑定

通过yml绑定role

定义的RoleBinding对象在"default"命名空间中将"song"角色授予用户"jane"。

这一授权将允许用户"jane"从"default"命名空间中读取pod。

subject可以为:

  • 用户–User
  • 用户组–Group
  • 服务账户–Service Account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: rolebinding
  namespace: default
subjects:
- kind: User  # 绑定对象为User
  name: jian
  apiGroup: rbac.authorization.k8s.io  # 必写,暂时不知道干嘛的
roleRef:
  kind: Role
  name: song 
  apiGroup: rbac.authorization.k8s.io  # 同上

RoleBinding对象也可以引用一个ClusterRole对象。用于在RoleBinding所在的命名空间内授予用户对所引用的ClusterRole中定义的命名空间资源的访问权限。

这一点允许管理员在整个集群范围内首先定义一组通用的角色,然后再在不同的命名空间中复用这些角色。

例如,尽管下面示例中的RoleBinding引用的是一个ClusterRole对象,但是用户"dave"(即角色绑定主体)还是只能读取"development" 命名空间中的secret(即RoleBinding所在的命名空间)。

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: read-secrets
  namespace: development       # 这里表明仅授权读取"development"命名空间中的资源。
subjects:
- kind: User
  name: dave
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader      #引用上面定义的clusterRole 名称(clusterRole没有指定命名空间,默认可以应用所有,但是在rolebinding时,指定了命名空间,所以只能读取本命名空间的文件)
  apiGroup: rbac.authorization.k8s.io
通过yml绑定clusterrole

ClusterRoleBinding在集群级别和所有命名空间中授予权限。

以下 ‘ClusterRoleBinding’ 对象允许在用户组 “manager” 中的任何用户都可以读取集群中任何命名空间中的secret。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: rolebinding
  namespace: default
subjects:
- kind: Group
  name: manager
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: dong
  apiGroup: rbac.authorization.k8s.io

通过命令行绑定

六、检查恢复机制(未完成)

在 k8s 中,可以为 Pod 里的容器定义一个健康检查"探针"。kubelet 就会根据这个 Probe 的返回值决定这个容器的状态,而不是直接以容器是否运行作为依据。这种机制,是生产环境中保证应用健康存活的重要手段。

1、命令模式探针

# 例子
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: daocloud.io/library/nginx
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

它在启动之后做的第一件事是在 /tmp 目录下创建了一个 healthy 文件,以此作为自己已经正常运行的标志。而 30 s 过后,它会把这个文件删除掉。

与此同时,定义了一个这样的 livenessProbe(健康检查)。它的类型是 exec,它会在容器启动后,在容器里面执行一句我们指定的命令,比如:“cat /tmp/healthy”。这时,如果这个文件存在,这条命令的返回值就是 0,Pod 就会认为这个容器不仅已经启动,而且是健康的。这个健康检查,在容器启动 5 s 后开始执行(initialDelaySeconds: 5),每 5 s 执行一次(periodSeconds: 5)。

如果健康检查失败,会立即重启容器,在这个过程中容器会一直处在running的状态。注意:Kubernetes 中并没有 Docker 的 Stop 语义。所以虽然是 Restart(重启),但实际却是重新创建了容器。

2、http get方式探针

apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget-pod
  namespace: default
spec:
  containers:
    - name: liveness-exec-container
      image: daocloud.io/library/nginx
      imagePullPolicy: IfNotPresent
      ports:
        - name: http
          containerPort: 80
      livenessProbe:
        httpGet:
          port: http
          path: /index.html
        initialDelaySeconds: 1
        periodSeconds: 3

3、pod恢复机制

restartPolicy。它是 Pod 的 Spec 部分的一个标准字段(pod.spec.restartPolicy),默认值是 Always,即:任何时候这个容器发生了异常,它一定会被重新创建。

Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点。这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去。

可以通过设置 restartPolicy,改变 Pod 的恢复策略。一共有3种:

  • Always:在任何情况下,只要容器不在运行状态,就自动重启容器;

  • OnFailure:只在容器异常时才自动重启容器;

  • Never: 从来不重启容器。

两个基本的设计原理

  • 只要 Pod 的 restartPolicy 指定的策略允许重启异常的容器(比如:Always),那么这个 Pod 就会保持 Running 状态,并进行容器重启。否则,Pod 就会进入 Failed 状态 。

  • 对于包含多个容器的 Pod,只有它里面所有的容器都进入异常状态后,Pod 才会进入 Failed 状态。在此之前,Pod 都是 Running 状态。此时,Pod 的 READY 字段会显示正常容器的个数。

七、pod亲和性

​ 通常情况下,使用的都是k8s默认的调度调度方式,但是在有些情况下,我们需要将pod运行在具有特点的标签的node上才能都运行,这个时候,pod的调度策略就不能使用k8s默认的调度策略了,这个时候,就需要指定调度策略,告诉k8s需要将pod调度到那些node(节点)上。

三种调度粘性

  • NodeSelector(定向调度)
  • NodeAffinity(Node亲和性)
  • PodAffinity(Pod亲和性)

1、nodeselector(定向调度)

​ 常规情况下,会直接使用nodeSelector这种调度策略。labels(标签) 是k8s里面用来编标记资源的一种常用的方式,我们可以给node标记特殊的标签,然后nodeSelector会将pod调度到带有指定labels的node上的。

# 先为node打标签
kubectl label nodes node1 who-is-baba=song

# 编写pod yml
apiVersion: v1
kind: Pod
metadata:
  name: test1
spec:
  containers:
  - name: test1
    image: nginx:1.8 
  nodeSelector:    # 标签选择
    who-is-baba: song

这种调度方式属于强制性的。如果node1上的资源不足,那么pod的状态将会一直是pending状态。

2、亲和性和反亲和性

​ k8s的默认调度流程实际上是经过了两个阶段predicates,priorities 。使用默认的调度流程的话,k8s会将pod调度到资源充裕的节点上,使用nodeselector的调度方法,又会将pod调度具有指定标签的pod上。

​ 然后在实际生产环境中,我们需要将pod调度到具有些label的一组node才能满足实际需求,这个时候就需要nodeAffinity、podAffinity以及 podAntiAffinity(pod 反亲和性)。

亲和性可以分为具体可以细分为硬和软两种亲和性

  • 软亲和性:如果调度的时候,没有满足要求,也可以继续调度,即能满足最好,不能也无所谓

    • preferredDuringSchedulingIgnoredDuringExecution #软性配置
  • 硬亲和性:是指调度的时候必须满足特定的要求,如果不满足,那么pod将不会被调度到当前node

    • requiredDuringSchedulingIgnoredDuringExecution #硬性强制

2.1、nodeAffinity 节点亲和性

​ 节点亲和性主要是用来控制 pod 能部署在哪些节点上,以及不能部署在哪些节点上的。它可以进行一些简单的逻辑组合了,不只是简单的相等匹配。

preferredDuringSchedulingIgnoredDuringExecution强调优先满足制定规则,调度器会尝试调度pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重值,以定义执行的先后顺序。

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:   # 设定亲和性
    nodeAffinity:  # node亲和性
      requiredDuringSchedulingIgnoredDuringExecution:  # 软性亲和
        nodeSelectorTerms:  # 节点选择
        - matchExpressions:
          - key: beta.kubernetes.io/arch
            operator: In
            values:
            - amd64
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: disk-type
            operator: In
            values:
            - ssd
  containers:
  - name: with-node-affinity
    image: nginx   

NodeAffinity规则设置的注意事项如下:

  • 如果同时定义了nodeSelector和nodeAffinity,node必须两个条件都得到满足,pod才能最终运行在指定的node上。

  • 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可。

  • 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该pod。

    • matchExpressions : 匹配表达式,这个标签可以指定一段,例如pod中定义的key为zone,operator为In(包含那些),values为 foo和bar。就是在node节点中包含foo和bar的标签中调度

      现在kubernetes提供的操作符有下面的几种
      In:label 的值在某个标签中
      NotIn:label 的值不在某个标签中
      Gt:label 的值大于某个值
      Lt:label 的值小于某个值
      Exists:某个 label 存在
      DoesNotExist:某个 label 不存在
      

2.2、podAffinity pod亲和性

​ Pod的亲和性主要用来解决pod可以和哪些pod部署在同一个集群里面,即拓扑域(由node组成的集群)里面;而pod的反亲和性是为了解决pod不能和哪些pod部署在一起的问题,二者都是为了解决pod之间部署问题。需要注意的是,Pod 间亲和与反亲和需要大量的处理,这可能会显著减慢大规模集群中的调度,不建议在具有几百个节点的集群中使用,而且Pod 反亲和需要对节点进行一致的标记,即集群中的每个节点必须具有适当的标签能够匹配topologyKey。如果某些或所有节点缺少指定的topologyKey标签,可能会导致意外行为。

Pod亲和性场景,我们的k8s集群的节点分布在不同的区域或者不同的机房,当服务A和服务B要求部署在同一个区域或者同一机房的时候,我们就需要亲和性调度了。

  • labelSelector : 选择跟那组Pod亲和

  • namespaces : 选择哪个命名空间

  • topologyKey : 指定节点上的哪个键

pod亲和性调度需要各个相关的pod对象运行于"同一位置", 而反亲和性调度则要求他们不能运行于"同一位置";

这里指定“同一位置” 是通过 topologyKey 来定义的,topologyKey 对应的值是 node 上的一个标签名称,比如各别节点zone=A标签,各别节点有zone=B标签,pod affinity topologyKey定义为zone,那么调度pod的时候就会围绕着A拓扑,B拓扑来调度,而相同拓扑下的node就为“同一位置”。

如果基于各个节点kubernetes.io/hostname标签作为评判标准,那么很明显“同一位置”意味着同一节点,不同节点既为不同位置,

apiVersion: v1
kind: Pod
metadata:
  name: pod-first
  labels:
    app: myapp
    tier: frontend
spec:
  containers:
  - name: myapp
    image: daocloud.io/library/nginx:latest
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-second
  labels:
    app: db
    tier: db
spec:
  containers:
  - name: busybox
    image: daocloud.io/library/busybox
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep 3600"]
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app, operator: In, values: ["myapp"]}
        topologyKey: kubernetes.io/hostname

八、污点与容忍

​ 对于nodeAffinity无论是硬策略还是软策略方式,都是调度 POD 到预期节点上,而Taints恰好与之相反,如果一个节点标记为 Taints ,除非 POD 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度pod。

​ 比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 POD,则污点就很有用了,POD 不会再被调度到 taint 标记过的节点。

1、设置污点和取消污点

# 设置污点
kubectl taint node 节点名  key=value:NoSchedule

# 查看污点
kubectl describe node 节点名 | grep Taint

# 删除污点
kubectl taint node 节点名 key=value:NoSchedule-

# 污点分类
# NoSchedule:新的不能容忍的pod不能再调度过来,但是之前运行在node节点中的Pod不受影响
# NoExecute:新的不能容忍的pod不能调度过来,老的pod也会被驱逐
# PreferNoScheduler:表示尽量不调度到污点节点中去

2、使用

apiVersion: v1
kind: Pod
metadata:
  name: sss-1
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: app
            operator: In
            values:
            - myapp
  containers:
  - name: with-node-affinity
    image: daocloud.io/library/nginx:latest
  tolerations:   #添加容忍策略  
  - key: "key"   #对应我们添加节点的变量名 
    operator: "Equal"  #操作符 
    value: "value"  #容忍的值  key=value对应 
    effect: "NoSchedule"  #添加容忍的规则,这里必须和我们标记的五点规则相同


# operator值是Exists,则value属性可以忽略
# operator值是Equal,则表示key与value之间的关系是等于
# operator不指定,则默认为Equal

九、私有仓库

1、Registry

​ 生产环境下,势必不能够每个机器都导入一遍从海外下载回来的镜像,这方法都不是可以长期使用的。

​ 可以通过搭建本地的私有镜像仓库(docker registry,这个镜像可以在国内直接下载)来解决这个问题。

部署docker registry
# 拉取registry镜像(master节点部署)
docker pull docker.io/registry

# 启动registry
docker run -d -p 5000:5000 --name=registry --restart=always --privileged=true   -v /home/data/registrydata:/tmp/registry registry

# 

十、部署应用

1、部署tomcat

基本上可以复制粘贴直接使用,需要注意的是将自己的war包放在指定目录下,然后修改对应的名字即可

# 制作自己的一个小小镜像,以共享文件
# 创建目录
mkdir -p tomcat/t/{lib64,app}
cd tomcat
# 复制有关命令及依赖,cp命令最重要
cp  $(ldd /usr/bin/ls |grep -oP '/lib64\S+') t/lib64/
cp  $(ldd /usr/bin/pwd |grep -oP '/lib64\S+') t/lib64/
cp  $(ldd /usr/bin/bash |grep -oP '/lib64\S+') t/lib64/
cp  $(ldd /usr/bin/cp |grep -oP '/lib64\S+') t/lib64/

cp  /usr/bin/ls  t
cp  /usr/bin/pwd  t
cp  /usr/bin/cp  t
cp  /usr/bin/bash  t
# 编写Dockerfile
vim Dockerfile
FROM scratch
ENV PATH=/
ADD t/ /
CMD ["/bash"]
ONBUILD COPY . /

# 创建基础镜像
docker build -t mycp:v1.0 .
#################################################################################看这里
# 为基础镜像添加自己的应用包
mkdir app 
cd app
vim Dockerfile
FROM mycp:v1.0
##################################################################################改这里
# 将自己的应用包放入tomcat/app目录中,然后构建终极基础镜像
docker build -t mycp:v2.0 .

cd ..

# 将自己的镜像上传到每个node节点上去
docker save -o mycp2.0.tar.gz mycp:v2.0

scp mycp2.0.tar.gz node1:/root

docker load -i mycp2.0.tar.gz
# 编写yml文件,启动pod
vim tomcat.yml
apiVersion: v1
kind: Pod
metadata:
 name: javaweb-2
spec:
 initContainers:
 - image: mycp:v2.0
   name: war
   command: ["cp", "/jenkins.war", "/app"]  ####可能要改
   volumeMounts:
   - mountPath: /app
     name: app-volume
 containers:
 - image: tomcat:8.5
   name: tomcat
   volumeMounts:
   - mountPath: /usr/local/tomcat/webapps
     name: app-volume
   ports:
   - containerPort: 8080
     hostPort: 8001
 volumes:
   - name: app-volume
     emptyDir: {}
# 创建pod
kubectl apply -f tomcat.yml

# 查看pod状态
kubectl describe pod javaweb-2

# 在对应的node节点访问tomcat
curl -I 127.0.0.1:8001/jenkins

r registry,这个镜像可以在国内直接下载)来解决这个问题。

部署docker registry
# 拉取registry镜像(master节点部署)
docker pull docker.io/registry

# 启动registry
docker run -d -p 5000:5000 --name=registry --restart=always --privileged=true   -v /home/data/registrydata:/tmp/registry registry

# 

十、部署应用

1、部署tomcat

基本上可以复制粘贴直接使用,需要注意的是将自己的war包放在指定目录下,然后修改对应的名字即可

# 制作自己的一个小小镜像,以共享文件
# 创建目录
mkdir -p tomcat/t/{lib64,app}
cd tomcat
# 复制有关命令及依赖,cp命令最重要
cp  $(ldd /usr/bin/ls |grep -oP '/lib64\S+') t/lib64/
cp  $(ldd /usr/bin/pwd |grep -oP '/lib64\S+') t/lib64/
cp  $(ldd /usr/bin/bash |grep -oP '/lib64\S+') t/lib64/
cp  $(ldd /usr/bin/cp |grep -oP '/lib64\S+') t/lib64/

cp  /usr/bin/ls  t
cp  /usr/bin/pwd  t
cp  /usr/bin/cp  t
cp  /usr/bin/bash  t
# 编写Dockerfile
vim Dockerfile
FROM scratch
ENV PATH=/
ADD t/ /
CMD ["/bash"]
ONBUILD COPY . /

# 创建基础镜像
docker build -t mycp:v1.0 .
#################################################################################看这里
# 为基础镜像添加自己的应用包
mkdir app 
cd app
vim Dockerfile
FROM mycp:v1.0
##################################################################################改这里
# 将自己的应用包放入tomcat/app目录中,然后构建终极基础镜像
docker build -t mycp:v2.0 .

cd ..

# 将自己的镜像上传到每个node节点上去
docker save -o mycp2.0.tar.gz mycp:v2.0

scp mycp2.0.tar.gz node1:/root

docker load -i mycp2.0.tar.gz
# 编写yml文件,启动pod
vim tomcat.yml
apiVersion: v1
kind: Pod
metadata:
 name: javaweb-2
spec:
 initContainers:
 - image: mycp:v2.0
   name: war
   command: ["cp", "/jenkins.war", "/app"]  ####可能要改
   volumeMounts:
   - mountPath: /app
     name: app-volume
 containers:
 - image: tomcat:8.5
   name: tomcat
   volumeMounts:
   - mountPath: /usr/local/tomcat/webapps
     name: app-volume
   ports:
   - containerPort: 8080
     hostPort: 8001
 volumes:
   - name: app-volume
     emptyDir: {}
# 创建pod
kubectl apply -f tomcat.yml

# 查看pod状态
kubectl describe pod javaweb-2

# 在对应的node节点访问tomcat
curl -I 127.0.0.1:8001/jenkins
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐