原文内容:https://gitee.com/dev-99cloud/training-kubernetes ,在此基础上有新增。

Lesson 02:Kubernetes Concepts

2.1 什么是 K8S?

  • 什么是 Kubernetes?容器编排工具。为什么叫 K8S?

  • K8S 和 Docker 有什么关系?参考:Container runtimes

    • Orchestration API -> Container API (cri-runtime) -> Kernel API(oci-runtime)

    • OCI:runC / Kata( 及其前身 runV 和 Clear Containers ),gVisor,Rust railcar

      • 容器镜像的制作标准,即 ImageSpec。大致规定:容器镜像这个压缩了的文件夹里以 xxx 结构放 xxx 文件
      • 容器要需要能接收哪些指令,以及这些指令的行为,即 RuntimeSpec。这里面的大致内容就是“容器”要能够执行 “create”,“start”,“stop”,“delete” 这些命令,并且行为要规范。
      • Docker 则把 libcontainer 封装了一下,变成 runC 捐献出来作为 OCI 的参考实现。
    • CRI:Docker( 借助 dockershim ),containerd( 借助 CRI-containerd ),CRI-O,Frakti。是一组 gRPC 接口,cri-api/pkg/apis/services.go

      • 一套针对容器操作的接口,包括创建,启停容器等等
      • 一套针对镜像操作的接口,包括拉取镜像删除镜像等
      • 一套针对 PodSandbox(容器沙箱环境)的操作接口
    • 正常的 K8S 调用 Docker 流程

    • containerd-1.0,对 CRI 的适配通过一个单独的进程 CRI-containerd 来完成

    • containerd-1.1,把适配逻辑作为插件放进了 containerd 主进程中

    • CRI-O,更为专注的 cri-runtime,非常纯粹,兼容 CRI 和 OCI,做一个 Kubernetes 专用的运行时

  • CRI-O 的架构:

  • K8S 和 Borg 有何关系?Start from Borg, however completely different!

2.2 K8S 是为了解决什么问题?

  • K8S 有什么优势?适用于哪些场景?自动化编排:自愈,快速缩放,一键部署和升降级,备份恢复
  • 如何查文档?K8SOpenShift Origin
  • 其它资料:slack、cncf、quay.io

2.3 K8S 不解决什么问题?

  • 用户管理
  • 限流熔断:istio
  • 监控审计:prometheus / grafana / alertmanager / elasticsearch / fluent / kibana
  • 用户界面
  • 中间件
  • 底层云平台支持

2.4 K8S 的模块架构是怎样的?

  • K8S 有哪些组件?api-server、kube-scheduler、kube-controller、etcd、coredns、kubelete、kubeproxy

  • 整体结构图

2.5 K8S 有哪些竞争产品?

  • OpenShift

    • 和 K8S 相比,OpenShift( 红帽最有价值的产品 )有哪些优势?

  • VMware

  • KubeSphere

  • Rancher

2.6 怎么部署出一个 K8S 群集?

  • kubeadm
  • Ansible

2.7 实验:K8S 的部署

2.7.1 在 centos 7 上部署 k8s

# 1. 关闭防火墙(在阿里云的 centos-7.9 镜像默认就是关闭的,不用做)
systemctl stop firewalld.service
systemctl disable firewalld.service

# 2. 关闭 selinx (在阿里云的 centos-7.9 镜像默认就是关闭的,不用做)
# 将 SELINUX=enforcing 改为 SELINUX=disabled
vi /etc/selinux/config

# 立刻停止 selinux
setenforce=0

# 3. 关闭 swap(在阿里云的 centos-7.9 镜像默认就是关闭的,不用做)
# 注释掉 swapoff xxx ,避免重启后 swap 重新启用
vi /etc/fstab
# 立刻停止 swap
swapoff -a

# 4.开启内核参数
# 持久化激活内核模块 br_netfilter
echo br_netfilter | tee -a /etc/modules
modprobe br_netfilter
# 开启 ipv4 forward
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

# 启动内核参数配置
sysctl -p
sysctl --system

# 5. 配置 kubernetes repo 源, 这里我们使用阿里云的 repo
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
        http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

# 6. [可选] 移除之前安装的 docker(可能有老版本的)
yum remove -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 7. 安装新版本的 docker
yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y

# 开机启动 containerd 和 docker
systemctl enable containerd --now
systemctl enable docker --now

# 初始化 containerd 配置
containerd config default > /etc/containerd/config.toml
vi /etc/containerd/config.toml
# 更新 sandbox 镜像
# sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.6" 用国内的镜像仓库
# 更新 SystemdCgroup,false 改成 true
# SystemdCgroup = true

vi /etc/docker/daemon.json

{
  "exec-opts": ["native.cgroupdriver=systemd"]
}

# 重新启动 containerd 和 docker
systemctl restart containerd
systemctl restart docker

# 8. 安装 kubernetes 1.23.3
export k8s_version="1.23.3"
# 安装 1.23.3 的repo包
yum install -y kubelet-${k8s_version}-0 kubeadm-${k8s_version}-0 kubectl-${k8s_version}-0  --disableexcludes=kubernetes

# 启动 kubelet
systemctl restart kubelet
systemctl enable kubelet

# 提前拉取镜像
kubeadm config images pull --kubernetes-version ${k8s_version} --image-repository registry.aliyuncs.com/google_containers

# 安装 master
kubeadm init --image-repository registry.aliyuncs.com/google_containers --kubernetes-version=v${k8s_version} --pod-network-cidr=10.244.0.0/16

# 配置 kubectl 的配置文件
mkdir $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 安装 calico
kubectl apply -f https://gitee.com/dev-99cloud/lab-openstack/raw/master/src/ansible-cloudlab-centos/playbooks/roles/init04-prek8s/files/calico-${k8s_version}.yml

上述部署可以让 K8S 对接 Docker,如果想直接对接 containerd,那么 kubeadm int 命令需要改成:kubeadm init --cri-socket unix:///run/containerd/containerd.sock --image-repository registry.aliyuncs.com/google_containers --kubernetes-version=v${k8s_version} --pod-network-cidr=10.244.0.0/16 ,参考 GithubGitee

如果要重新安装,可以 kubeadm reset -f

  • 检查集群状态

    # 检查节点状态
    $ kubectl get nodes
    NAME           STATUS   ROLES                                           AGE    VERSION
    cka-node1   Ready      control-plane,master,worker   30s      v1.23.3
    
  • 集群安装/加入 GPU 节点(仅当 cri 为 docker 时才行)

    # 1. 节点安装显卡驱动
    # 可以参考 https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#abstract
    
    # 2. 安装 docker
    # 见上文
    
    # 3. 安装 nvidia-docker
    distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \ && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo
    
    yum install -y nvidia-docker2
    # ubuntu 等其他系统安装参考文档 https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#installing-on-centos-7-8
    
    # 4. 更改默认容器运行时
    # 编辑 `/etc/docker/daemon.json`, 添加如下字段:
    {
        "default-runtime": "nvidia",
        "runtimes": {
            "nvidia": {
                "path": "nvidia-container-runtime",
                "runtimeArgs": []
            }
        }
    }
    systemctl daemon-reload && systemctl restart docker
    
    # 5. 安装 k8s 集群或将集群加入节点即可
    
    # 6. 安装 vgpu 插件
    # * Nvidia 官方的插件只能在 k8s 上整卡调度,调度力度太大,在我们的场景中没有太大实际意义
    # * 阿里云 gpushare 是阿里云公有云使用的 cgpu 的部分开源项目,能对 gpu 进行显存划分,多容器调度,但是没有实现显存隔离
    # * 第四范式 vgpu 是第四范式开源的 vgpu 上层实现,底层核心逻辑是 libvgpu.so提供的,没有开源,可以实现对物理 gpu 的切分,实现了显存隔离。
    # 安装第四范式 vgpu 参考 `https://github.com/4paradigm/k8s-device-plugin/blob/master/README_cn.md#Kubernetes%E5%BC%80%E5%90%AFvGPU%E6%94%AF%E6%8C%81`
    

2.7.2 在 Ubuntu 18.04 / 20.04 上部署 k8s

  1. 安装 kubeadm

    # iptables 看到 bridged 流量
    cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    EOF
    sudo sysctl --system
    
    # Install kubeadm
    apt-get update && apt-get install -y apt-transport-https curl
    curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
    cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
    deb https://apt.kubernetes.io/ kubernetes-xenial main
    EOF
    apt-get update -y && apt-get install -y kubelet kubeadm kubectl
    
    # 重启 kubelet
    systemctl daemon-reload
    systemctl restart kubelet
    
    # 检查 kubelet 状态,在加入 k8s 集群之前,kubelet 状态会一直处于 activating 状态
    systemctl status kubelet
    
  2. 用 kubeadm 创建一个 k8s 群集

    # 拉起 k8s 群集
    kubeadm init --pod-network-cidr 192.168.1.90/16
    
    # 配置 kubectl 客户端
    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    
  3. 此时可以观察到 node 并未 ready,导致 coredns 无法调度。接下来需要:安装网络插件插件列表,这里我们用 Calico

    # 添加网络插件
    # Install the Tigera Calico operator and custom resource definitions
    kubectl create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml
    # Install Calico by creating the necessary custom resource
    kubectl create -f https://docs.projectcalico.org/manifests/custom-resources.yaml
    
    # 查看 pods 和 nodes 状态
    kubectl get pods --all-namespaces
    kubectl get nodes
    

2.8 什么是 Pod?

  • Pod 和容器的关系是什么?

    # pod 里的 pause 容器和业务容器共享 ipc / net / user namespaces
    [root@cloud025 ~]# ll /proc/10982/ns/
    total 0
    lrwxrwxrwx 1 root root 0 Aug 10 16:14 ipc -> ipc:[4026532513]
    lrwxrwxrwx 1 root root 0 Aug 10 15:33 mnt -> mnt:[4026532592]
    lrwxrwxrwx 1 root root 0 Aug 10 15:33 net -> net:[4026532516]
    lrwxrwxrwx 1 root root 0 Aug 10 15:33 pid -> pid:[4026532594]
    lrwxrwxrwx 1 root root 0 Aug 10 16:14 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Aug 10 16:14 uts -> uts:[4026532593]
    
    [root@cloud025 ~]# ll /proc/10893/ns/
    total 0
    lrwxrwxrwx 1 root root 0 Aug 10 15:33 ipc -> ipc:[4026532513]
    lrwxrwxrwx 1 root root 0 Aug 10 15:33 mnt -> mnt:[4026532511]
    lrwxrwxrwx 1 root root 0 Aug 10 15:33 net -> net:[4026532516]
    lrwxrwxrwx 1 root root 0 Aug 10 16:15 pid -> pid:[4026532514]
    lrwxrwxrwx 1 root root 0 Aug 10 16:15 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Aug 10 16:15 uts -> uts:[4026532512]
    
  • 为什么调度的基本单位是 pod 不是容器?

2.9 启动一个 pod

  • Pod YAML

    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx
      labels:
        env: test
    spec:
      containers:
      - name: nginx
        image: nginx
    
  • 启动一个 Pod

    # 起 nginx pod
    kubectl apply -f nginx.yaml
    
    # 可以看到 pod 无法被调度,进行诊断
    kubectl describe pod nginx
    
    # 去污点、允许 pod 调度到 master
    kubectl taint nodes $(hostname) node-role.kubernetes.io/master:NoSchedule-
    
    # 查看 pods
    kubectl get pods
    
    # 查看容器
    docker ps | grep nginx
    
  • 在容器平台上发布 Python 应用

    • 我们将要做什么?

    • 预置条件有哪些?

      • 本地具备 Python 3.6+ 的运行环境
      • 安装了 Git(非必须)
      • 安装了 Docker
      • 一个 Kubernetes 平台
    • 代码

      • 其中 requirements.txt 里只有一行:flask,是依赖包列表文件

      • 另一个文件是 main.py,是应用的逻辑代码

        from flask import Flask
        import os
        import socket
        
        app = Flask(__name__)
        
        @app.route("/")
        def hello():
            html = "<h3>Hello {name}!</h3>" \
                   "<b>Hostname:</b> {hostname}<br/>"
            return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
        
        if __name__ == "__main__":
            app.run(host='0.0.0.0')
        
    • 下载代码

      mkdir ~/test-hello-world
      cd ~/test-hello-world/
      
      wget https://gitee.com/dev-99cloud/training-kubernetes/raw/master/src/hello-python/main.py
      wget https://gitee.com/dev-99cloud/training-kubernetes/raw/master/src/hello-python/requirements.txt
      wget https://gitee.com/dev-99cloud/training-kubernetes/raw/master/src/hello-python/Dockerfile
      wget https://gitee.com/dev-99cloud/training-kubernetes/raw/master/src/hello-python/deployment.yaml
      
    • 本地运行:

      • 安装依赖:pip3 install -r requirements.txt
      • 本地运行:python3 main.py,会在本地启动一个用于开发的 web 服务器。
      • 你可以打开浏览器,访问 http://<IP>:5000 端口
    • Docker 镜像构建文件,目录下有一个 Dockerfile 文件

      FROM python:3.7
      
      RUN mkdir /app
      WORKDIR /app
      ADD . /app/
      RUN pip install -r requirements.txt
      
      EXPOSE 5000
      CMD ["python", "/app/main.py"]
      
    • 构建 Docker 镜像并运行

      • 运行命令构建镜像:docker build -f Dockerfile -t hello-python:latest .
      • 构建完成后,可以通过 docker image ls 看到刚才构建的镜像
      • 然后运行此镜像:docker run -p 5001:5000 hello-python
      • 然后访问 http://<IP>:5001,同样可以看到 Hello form Python! 字符串
    • 确定 Kubernetes 和 kubectl 运行正常

      • 运行命令:kubectl version,如果该命令不报错,就说明 kubectl 安装完成。
      • 运行命令:kubectl get nodes,如果返回节点信息,说明 K8S 运行正常,kubectl 配置正常。
    • K8S deployment YAML 文件:

      apiVersion: v1
      kind: Service
      metadata:
        name: hello-python-service
      spec:
        type: NodePort
        selector:
          app: hello-python
        ports:
        - protocol: "TCP"
          port: 6000
          targetPort: 5000
          nodePort: 31000
      
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: hello-python
      spec:
        selector:
          matchLabels:
            app: hello-python
        replicas: 4
        template:
          metadata:
            labels:
              app: hello-python
          spec:
            containers:
            - name: hello-python
              image: hello-python:latest
              imagePullPolicy: Never
              ports:
              - containerPort: 5000
      
    • 将 Deployment 部署到 K8S 平台

      • 运行命令:kubectl apply -f deployment.yaml
      • 运行命令:kubectl get pods,检查 pods
      • 然后可以打开浏览器,在 http://<IP>:31000 看到 Hello form Python! 信息
    • 实验

      # 观察 service / deployment 和 pod 的关系,通过 label 关联
      kubectl get pods -l app=hello-python
      kubectl get deploy
      kubectl get deployment
      
      kubectl get svc
      kubectl get service
      kubectl get pods -l app=hello-python -o wide
      
      # 访问应用
      curl http://<pod-ip>:5000
      curl http://<cluster-ip>:6000
      curl http://<host-ip>:31000