K8S 学习笔记

作者: 王炳明 分类: Python 教程 发布时间: 2021-03-18 08:22 热度:392

1. 安装依赖

安装基本依赖及 Docker 仓库

$ curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
$ yum install yum-utils device-mapper-persistent-data lvm2 wget vim net-tools ntp -y
$ yum-config-manager     --add-repo   https://download.docker.com/Linux/centos/docker-ce.repo

安装数据库、git 及 docker

yum install -y mysql git docker-ce-19.03.11

systemctl start docker
systemctl enable docker

拉取 java 项目代码

mkdir kubeblog
cd kubeblog

git init
git pull https://git.imooc.com/coding-464/kubeblog.git
# wongbingming@163.com  yang1994,.

启动 mysql 前的预备工作

mkdir /opt/mysql
mkdir /opt/mysql/conf.d
mkdir /opt/mysql/data/

cat <<EOF >/opt/mysql/my.cnf
[mysqld]
user=mysql
character-set-server=utf8
default_authentication_plugin=mysql_native_password
secure_file_priv=/var/lib/mysql
expire_logs_days=7
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
max_connections=1000

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8
EOF

运行 mysql 的容器

docker run \
 --name mysql57 \
 -p 3306:3306 \
 -v /opt/mysql/data:/var/lib/mysql \
 -v /opt/mysql/log:/var/log/mysql \
 -v /opt/mysql/my.cnf:/etc/mysql/my.cnf:rw \
 -e MYSQL_ROOT_PASSWORD=password \
 -d registry.cn-beijing.aliyuncs.com/qingfeng666/mysql:5.7 --default-authentication-plugin=mysql_native_password

使用默认的密码 password 尝试登陆并创建 blogDB 数据库

mysql -uroot -h127.0.0.1 -p
MySQL [(none)]> CREATE DATABASE `blogDB` CHARACTER SET utf8 COLLATE utf8_general_ci;

安装 java、maven 并打包项目

yum install maven java-1.8.0-openjdk-devel
cd /root/kubeblog/Final/
mvn package

运行 java 项目

java -jar target/kubeblog.jar

如一切正常

  • 访问博客:http://localhost:5000
  • 访问博客管理员端:http://localhost:5000/admin,用户名/密码:admin/password

https://developer.aliyun.com/mirror/centos

unshare --fork --pid --mount-proc bash


grant all privileges on *.* to 'root'@'localhost' identified by '123456';

2. k8s 架构

ip 域名 角色 安装软件
192.168.56.200 master 主节点 docker kubeadm kubelet kubectl finanel
192.168.56.201 node1 从节点 docker kubeadm kubelet kubectl finanel
192.168.56.202 node2 从节点 docker kubeadm kubelet kubectl finanel

对环境的要求:

  • 3 台虚拟机:每台 2CPU,硬盘30G,内存2GB
  • 3 台虚拟机内网互通
  • 3 台虚拟机可访问外网
  • 禁止 swap 分区

3. 安装 K8S

先安装源

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

然后再安装 net-tools,如果报如下的错

repomd.xml signature could not be verified for kubernetes

可以关闭 /etc/yum.repos.d/kubernetes.repo 的验证

gpgcheck=0
repo_gpgcheck=0

同步下时间

ntpdate 0.asia.pool.ntp.org

加载 netfilter 模块,并查看一下文件是否为1

$ modprobe br_netfilter
$ cat /proc/sys/net/bridge/bridge-nf-call-iptables
1

新建 /etc/sysctl.d/k8s.conf,内容如下

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

关闭防火墙

systemctl stop firewalld
systemctl disable firewalld
setenforce 0

关闭 SELinux,编辑 /etc/selinux/config 修改如下值

SELINUX=disable

关闭 swap

swapoff -a

新建文件

cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"]
}
EOF

完了后,重启 docker

# Restart Docker

systemctl daemon-reload
systemctl restart docker
systemctl enable docker

执行命令安装

yum install -y kubelet-1.19.3 kubeadm-1.19.3 kubectl-1.19.3

构建 k8s 集群,如果你的虚机 cpu 不足2 核这边就会报错,这次命令只要执行一次,以后都不需要

kubeadm init --kubernetes-version=1.19.2 \
--apiserver-advertise-address=192.168.56.200 \
--image-repository registry.aliyuncs.com/google_containers \
--service-cidr=10.1.0.0/16 \
--pod-network-cidr=10.244.0.0/16

顺便把最后的输出结果中,把如下这个记录下,后面添加 worker 节点会用到,如果没有记也没有关系,可以重新生成的

kubeadm join 192.168.56.200:6443 --token p2tz0y.1h3e0c8yriqlcpcr \
    --discovery-token-ca-cert-hash sha256:8918da682f9cefd47bb240bf6ec32da3eec45c396a1125bb116111352e559e19

# 重新生成命令
kubeadm token create --print-join-command

完成后,再执行这四条命令

mkdir -p HOME/.kube
sudo cp -i /etc/kubernetes/admin.confHOME/.kube/config
sudo chown (id -u):(id -g) $HOME/.kube/config

echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile

然后要安装 flannel 插件

git clone https://git.imooc.com/coding-464/kubeblog.git

kubectl apply -f kubeblog/docs/Chapter4/flannel.yaml

此时执行命令

[root@master ~]# kubectl get po -n kube-system
NAME                             READY   STATUS    RESTARTS   AGE
coredns-6d56c8448f-cbwg5         1/1     Running   0          8m5s
coredns-6d56c8448f-pmcvb         1/1     Running   0          8m5s
etcd-master                      1/1     Running   0          8m22s
kube-apiserver-master            1/1     Running   0          8m22s
kube-controller-manager-master   0/1     Running   1          8m22s
kube-flannel-ds-b7lgp            1/1     Running   0          95s
kube-proxy-qs7wp                 1/1     Running   0          8m5s
kube-scheduler-master            0/1     Running   1          8m21s

再使用 kubectl get node 可以发现已经装好了

[root@master Chapter4]# kubectl get node
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   9m12s   v1.19.3

设置开机自启

systemctl start kubelet.service
systemctl enable kubelet.service

4. 概念性术语

Cluster

是计算、存储和网络资源的集合,Kubernetes 利用这些资源运行各种基于容器的应用。一个 Cluster 由 Master 节点和 Node 节点两种角色组成。

Master

在 Cluster 中起到管理和控制的作用。基本上所有的 Kubernetes 控制命令都是直接发给 Master 。

它决定了应用放在哪个 Node 上运行,Master 是 Kubernetes 集群的大脑,非常地重要,因此为了避免 Master 宕机造成的巨大影响,通常会启用三台独立的机器 部署 Master 实现高可用。

在 Master 节点上起着四个关键服务

  • Kubernetes API Server(kube-apiserver):提供了HTTP Rest接口的关键服务进程,是Kubernetes里所有资源的增、删、改、查等操作的唯 一入口,也是集群控制的入口进程
  • Kubernetes Controller Manager ( kube-controller-manager ) : Kubernetes里所有资源对象的自动化控制中心,可以将其理解为资源对象的“大总管”
  • Kubernetes Scheduler(kube-scheduler):负责资源调度(Pod调度)的进程,相当于公交公司的“调度室”。
  • etcd:这是一个高可用的分布式 key-value 数据库,用于存储 Kubernetes 中所有的资源对象数据。

Node

在 Cluster 中的作用是运用容器应用。Node 由 Master 管理,Node 负责监控并汇报容器的状态,并根据 Master 的要求管理容器的生命周期。Master 本身也可以作用 Node 节点工作。

在 Node 节点上,运行的关键服务有如下三个:

  • kubelet:负责Pod对应的容器的创建、启停等任务,同时与Master密切协作,实现集群管理的基本功能
  • kube-proxy:实现Kubernetes Service的通信与负载均衡机制的重要组件
  • Docker Engine(docker):Docker引擎,负责本机的容器创建和管理工作

这三个同样也可安装运行于 Master,然后就可以将 Master 也变成 Node 节点 join 到 集群中。

# 把 Master 当作 Node 使用
kubectl taint node master node-role.kubernetes.io/master-

# 恢复 Master Only 状态
kubectl taint node master node-role.kubernetes.io/master="":NoSchedule

另外,在Master 和 Node 上还需要安装网络插件,实现 Pod 间的通信,通常使用的是 flannel ,对应的服务是 flanneld,主要负责数据包的转发处理,详情可参考:k8s的Flannel网络,由于这只是一个插件,如果不想用它,也可以用别的,因此是非必须的。

一旦Node 被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的情报,例 如操作系统、Docker版本、机器的CPU和内存情况,以及当前有哪些Pod 在运行等,这样Master就可以获知每个Node的资源使用情况,并实现高 效均衡的资源调度策略。

而某个Node在超过指定时间不上报信息时,会 被Master判定为“失联”,Node的状态被标记为不可用(Not Ready),随 后Master会触发“工作负载大转移”的自动流程。

Pod

Kubernetes 的最小的工作和管理单元。每个 Pod 里包含一个或者多个容器,Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。一个 Pod 上的多个容器,共享 ip 和 port,使用同一个 namespace。

Pod 还可以细分为两种类型:

  1. 静态Pod:其比较特殊,它并没被存放在Kubernetes的etcd存储里,而是被存放在某个具体的Node上的一个具体文件中,并且只在此Node上启动、运行
  2. 普通的Pod:一旦被要求创建,就会被存放入 etcd 存储中,随后被 Master 调度到某个具体的 node 上由该 node 上的 kubelet 创建,然后实例化成一组相关的 Docker 容器并启动。当该 Pod 中的某个容器停止时,Kubernetes会自动检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),如果Pod所在的Node宕机, 就会将这个Node上的所有Pod重新调度到其他节点上。

Container

也即容器,是 Pod 的基本组成单位,一个 Pod 必须包含至少一个 Container。

多个联系非常紧密且需要直接共享资源(比如磁盘、ip等)的容器可以放在一个 Pod 中运行。

每一个 Pod 里,除了业务容器外,还都有一个特殊的根容器,我们称之为 Pause 容器,它属于 Kubernetes平台的一部分。

为什么Kubernetes会设计Pod这样特殊的组成结构?

  • 原因之一:在一组容器作为一个单元的情况下,我们难以简单地对 “整体”进行判断及有效地行动。比如,一个容器死亡了,此时算是整体死亡么?是N/M的死亡率么?引入业务无关并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,就简单、巧妙地解决了这个难题
  • 原因之二:Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样既简化了密切关联的业务容器之间的通信问题, 也很好地解决了它们之间的文件共享问题

5. 认识 Pod

5.1 如何创建资源对象

Kubernetes 和 Ansible 的设计上非常相似,只不过

  • Ansible 是用来编排任务的
  • 而 Kubernetes 则用来编排容器应用的

那怎么编排呢?Kubernetes 的任务都是使用的 Yaml 文件进行的,因此在学习 Kubernetes 之前,有必要大致先了解一下 Yaml 文件编写规范。

那么如何将 容器应用的各个属性及所需的参数,写到 Yaml 文件中呢?

Kubernetes 有一套自己的协议,这也是我们需要花长时间去学习并适应的。

有了 Kubernetes 可以解析的 Yaml 文件后 ,就可以通过 kubectl create 或者 kubectl apply 这两命令去创建并运行它。

那么这两条命令有什么区别呢?区别在于修改pod的配置时,操作的姿势:

  • kubectl create:可以使用 kubectl edit /kubectl set image 或者直接修改 nginx.yaml 先修改,再使用 kubectl replace -f nginx.yaml 去替换(是使用新的 YAML 文件中的 API 对象,替换原有的 API 对象)
  • kubectl apply:只能通过直接编辑 nginx.yaml 修改配置,再通过 kubectl apply -f nginx.yaml 应用(执行了一个对原有 API 对象的 PATCH 操作。)

像上面一样,先定义好 yaml 配置,有几个好处

  • yaml 配置文件提供了创建资源的模板,能够复用实现重复部署
  • 可以像管理代码一样,管理项目的部署
  • 适合正式的、跨环境的、规模化的部署

好处虽然多多,却也有弊端,那就是上手困难,要先熟悉 Kubernetes 的模板协议,才知道如何编写。

除了使用yaml 配置文件的方法外,也可以使用命令行的方式去创建资源对象,这种方法上手快,适合那种一次性的临时测试使用

kubectl run <pod_name> --image <image>

5.2 创建一个 Pod

首先以 Pod 为例进行演示,先准备好 nginx.yaml 文件,内容如下

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
  labels:
    name: my-nginx
spec:
  containers:
  - name: my-nginx
    image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    ports:
      - containerPort: 80

然后执行下面两条命令中的任意一条即可

$ kubectl create -f nginx.yaml
$ kubectl apply -f nginx.yaml

创建完成后,就可以通过 kubectl get pod 来查询到你刚刚所创建的 Pod

5.3 创建 init 容器

在一个 Pod 里,可以分为三种容器:

  1. Pause 容器:上面已经讲过
  2. init 容器:用于应用环境的初始化,仅当 init 容器运行完成后,才会开始运行普通容器
  3. 普通容器:除上面两种容器外的,都是普通容器,里面运行着跟业务相关的程序。

一旦 init 容器启动失败,Pod 会不断的重启该 Pod,直到 init 容器成功为止。但如果 Pod 的 restartPolicy 值设成了 Never,那就不会重启了。

以下是一个带有 init 容器的 Pod 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"]

6. 认识 Controller

创建 Deployment 对象

创建双副本的 nginx

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
        ports:
        - containerPort: 80

执行后就会创建两副本的 nginx pod

K8S 学习笔记插图

创建 Service 对象

每个 Pod 在创建的时候,都会分配有一个 IP 地址,当这个 Pod 因为故障或者其他原因重启时,这个 IP 地址就会重新分配,IP 地址会发生变化。

对于一个应用来说,如果 IP 地址一直发生变化,客户端的访问就会出现问题。

对此,Kubernetes 给出的解决方案是 Service 。

使用 Service ,可以解决两个问题:

  • 应用的 IP 固定问题
  • 应用的负载均衡问题

Service 可以将一组 Pod 上的应用程序公开为网络服务,实现负载均衡。

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

通过 kubectl get svc 就可以看到 Cluster-IP,通过这个IP去访问的话,会分发请求给后端的两个 pod。

K8S 学习笔记插图(1)

创建 Ingress 对象

ingress 对象的作用是管理 HTTP 请求的路由转发规则

# kubectl apply -f minimal-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

每个 HTTP 规则都包含如下信息

  1. 可选的host,在上面的示例中没有指定 host,因此 该规则适用于指定 ip地址进行访问的请求,如果指定了host(如 foo.bar.com),那么只有使用 foo.bar.com 这个域名进行访问的请求才能匹配
  2. path 路径,比如访问 http://192.168.56.200:80/testpath 就能匹配上上面的规则
  3. backend 后端,意思是想把这个请求转发给哪个 service 。

要想让 ingress 资源工作,集群必须要有一个 ingress 控制器

# kubectl apply -f ingress-nginx-controller.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx
---
kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  clusterIP: 10.1.211.240
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 31686
    port: 80
    protocol: TCP
    targetPort: http
  - name: https
    nodePort: 30036
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  sessionAffinity: None
  type: NodePort
---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller
          image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
          args:
            - /nginx-ingress-controller
            - --configmap=(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=(POD_NAMESPACE)/udp-services
            - --publish-service=(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown

---

然后再创建一个 web 的 deployment 对象

$ kubectl create deployment web --image=registry.cn-beijing.aliyuncs.com/qingfeng666/hello-app:1.0

然后将 deployment 暴露出来

$ kubectl expose deployment web --type=NodePort --port=8080

K8S 学习笔记插图(2)

创建 ConfigMap 对象

ConfigMap 里可以设定一系列的配置,方便其他 Pod 直接引用导入。

# kubectl create -f configmap-multikeys.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  SPECIAL_LEVEL: very
  SPECIAL_TYPE: charm

那怎么引用呢?使用的是 envFrom 这个关键词。

# kubectl create -f pod-configmap-envFrom.yaml
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - configMapRef:
          name: special-config
  restartPolicy: Never

DaemenSet

一种应用通常会有多个副本同时运行,正常情况下,这些副本会随机分布在各个 Node 上,有的 Node 可能会有一个甚至多个的副本,在大多数场景下,这样的调度结果并不是我们所预期的。

我们希望的是,在每个 node 上只会运行一个副本。

要实现的这样的效果,就要借助 DaemenSet 了。

其实在 Kubernetes 中就用 DaemonSet 来运行系统组件,你可以通过如下命令查得

kubectl get daemonset --namespace=kube-system

如果想学习 DaemonSet 的编排,可以直接 kubectl edit 去查看

kc edit daemonset kube-proxy --namespace=kube-system

Job

容器按照持续运行的时间可分为两类:服务类容器和工作类容器。

  • 服务类容器:需要一直运行,比如 http
  • 工作类容器:可能是一次性的任务,结束后退出

下面是一个 Job 类型的简单任务:myjob.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  template:
    spec:
      containers:
      - name: myjob
        image: busybox
        command: ["echo",  "Hello, K8S~"]
      restartPolicy: Never

通过 kubectl apply -f myjob.yaml 运行后,就可以通过 kubectl get job 命令查看其状态

[root@master ~]# kubectl get job
NAME    COMPLETIONS   DURATION   AGE
myjob   1/1           88s        2m34s

job 虽然是一次的任务,但也是一个 pod,同样可以通过 kubectl get pod 查看

[root@master ~]# kc get po
NAME                   READY   STATUS      RESTARTS   AGE
myjob-mxkns            0/1     Completed   0          2m45s

这个 job 任务输出了一行内容,可以通过 kubectl logs 查看

[root@master ~]# kubectl logs myjob-mxkns
Hello, K8S~

一个任务太慢,如果想多个任务同时运行,就像开启多线程那样,可以用上这两个参数 completionsparallelism

  • parallelism:指同时可以运行多少个 job ,可以理解为线程数
  • completions:指整个 job 的结束条件,必须要满足有多少个 pod 运行成功,大任务才能结束退出

比如 completions 设置为 4 ,parallelism 设置为2 ,那么每次运行会开启两个 job 或者 pod,结束后,再开启两个 job 或者 pod,此时已完成的 job 达到 4,满足预设的结束条件,整个大任务退出。

apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  completions: 4
  parallelism: 2
  template:
    spec:
      containers:
      - name: myjob
        image: busybox
        command: ["echo",  "Hello, K8S~"]
      restartPolicy: Never

CronJob

Job 是一次性任务,如果想实现定时任务呢,可以用 CronJob。

定时计划的编写规则,和 Linux 上的 Crond 一样。

下面我写了一个每分钟打印一行 Hello, K8S~ 的定时任务

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: myjob
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: myjob
            image: busybox
            command: ["echo",  "Hello, K8S~"]
          restartPolicy: OnFailure

使用如下命令可以查看定时任务

[root@master ~]# kubectl get cronjob
NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
myjob   */1 * * * *   False     0        34s             2m50s

经过观察,每过一分钟,就会创建一个 pod 。如果过了一天,你发现 kubectl get po 有一大堆的 job po 怎么办?如何实现自动清理呢?可以设置一下 successfulJobsHistoryLimit 参数,指定要保留运行成功的 pod 数

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: myjob
spec:
  schedule: "*/1 * * * *"
  successfulJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: myjob
            image: busybox
            command: ["echo",  "Hello, K8S~"]
          restartPolicy: OnFailure

其他概念

标签:label

默认配置下,Scheduler 会将 Pod 调度到所有可用的 Node。不过有些情况我们希望将 Pod 部署到指定的 Node,比如将有大量磁盘 I/O 的 Pod 部署到配置了 SSD 的 Node;或者 Pod 需要 GPU,需要运行在配置了 GPU 的节点上。

Kubernetes 是通过 label 来实现这个功能的。

label 是 key-value 对,各种资源都可以设置 label,灵活添加各种自定义属性。比如执行如下命令标注 node1 是配置了 SSD 的节点。

kubectl label node node1 disktype=ssd

然后再使用如下命令,可以查看 node 上的 label

kubectl get node --show-labels

有了 label 后,如何在创建资源对象时,指定这些标签呢?

K8S 使用的是 nodeSelector 这个属性来指定的

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    name: myapp
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
  nodeSelector:
    disktype: ssd

如果想要移除一个资源上的 label,可以执行如下命令

kubectl label node node1 disktype-

如果想要修改一个资源上的 label,只要加上 --overwrite 即可

kubectl label node node1 disktype=ssd --overwrite

流程理解

滚动更新

一个应用若有多个副本同时运行,在更新升级的时候,为了不影响服务,通常会使用滚动更新的方式进行升级,一次只升级一个副本,成功后,再升级另一个副本。

下面来给大家演示一下

我先准备一个 3 副本的 Deployment httpd.yaml文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd
spec:
  selector:
    matchLabels:
      run: httpd
  replicas: 3
  template:
    metadata:
      labels:
        run: httpd
    spec:
      containers:
      - name: httpd
        image: httpd:2.2.31
        ports:
        - containerPort: 80

通过 kubectl apply 进行部署,此时的 httpd 是 2.3.1 的版本

K8S 学习笔记插图(3)

再使用 vi 编辑 httpd.yaml ,把 httpd 的版本改成 2.2.32 后,再次执行 kubectl apply 进行部署,再次查看,image 已经刷新成 2.2.32 版本

K8S 学习笔记插图(4)

但是 pod 的更新需要时间, deployment 下 image 的版本是 2.2.32,并不代表所有的 pod 都已经升级成功。

通过 kubectl describe deployment httpd 可以看到整个更新的详细过程。

  • 用 2.2.32 去创建第一个新的 pod,再删除一个 2.2.31 的 pod
  • 用 2.2.32 去创建第二个新的 pod,再删除一个 2.2.31 的 pod
  • 用 2.2.32 去创建第三个新的 pod,再删除一个 2.2.31 的 pod
  • 更新完成

K8S 学习笔记插图(5)

回退版本

kubectl apply 每次更新应用时 Kubernetes 都会记录下当前的配置,保存为一个 revision(版次),通过 kc rollout history deployment httpd 这条命令可以查看历史的 revision。

K8S 学习笔记插图(6)

从上面的截图来看,有几个问题:

  1. revision 只有序号,用户根本不知道 1 和 2 分别对应哪个版本
  2. 默认情况下,Kubernetes 只会保留最近几个 revision。

对此,相应的解决方法是:

  1. 不同的 revision 使用不同的 yaml,比如 httpd.v1.yaml 和 httpd.v2.yaml,然后在 apply 的时候加上 --record 参数,就能把整个命令记录在 CHANGE-CAUSE 里,如此一来就能区分版本了。
  2. 通过在 yaml 中指定 .spec.revisionHistoryLimit 项来指定保留多少旧的ReplicaSet,0 代表永远不保留,这里你可以设置大一些。

下面来演示一下

可以看到 CHANGE-CAUSE 里已经有了信息

K8S 学习笔记插图(7)

现在我要回退到 revision=1 这个版本,可以执行下面这条命令

kubectl rollout undo deployment httpd --to-revision=1

再用 kubectl describe 查看,可以看到连版本回退也是滚动回退的

K8S 学习笔记插图(8)

再次查看 revision ,会发现 v1 的序号由 1 变成了 3.

K8S 学习笔记插图(9)

健康检查

每个容器应用都有可能发生故障,这种故障具体可以表现为两种形式

  1. 程序意外退出,返回码非零
  2. 程序无法提供服务,比如 web 服务返回 500 内部错误

Kubernetes 之所以如此强大,一部分是因为它的自愈能力非常的出色,当它检查到有如上两种情况时,并且你的 .spec.restartPolicy = OnFailure 时就会重启我们的 Pod。

对于这第一种形式,程序意外退出,返回码非零的,这是 Kubernetes 默认的健康检查机制,一般不需要我们进行配置。

而第二种形式,情况就比较复杂了,服务不可用的场景可太多了,具体如何检测呢?根本不能有一个统一的标准,于是 Kubernetes 干脆给出接口,让你来自定义检测方法。

根据目的的不同,探测方法可以分为两种:

  1. 通过Liveness 探测:设置的是达成 Failure 的条件,判断是否需要重启实现自愈
  2. 通过 Readiness 探测:设置的是达成 Ready 的条件,判断是否准备好对外提供服务,在滚动更新中有妙用

在滚动更新中,有两个非常重要的参数

  • .spec.strategy.rollingUpdate.maxSurge:设置的是副本数的最大上限,可以是整数,也可以是百分比
  • .spec.strategy.rollingUpdate.maxUnavailable:设置的是不可用副本数的上限,可以是整数,也可以是百分比

具体的案例,可以查看 在滚动更新中使用 Health Check

相关命令

$ kubectl get configmap -n kube-system
$ kubectl describe configmap -n kube-system

# 查看 k8s 系统空间的服务
$ kubectl get po -n kube-system

# 进入pod
$ kubectl exec -it <pod_name> sh

# 获取 pod
$ kubectl get po 

# 查看pod信息
$ kubectl describe pod nginx

# 查看 service
$ kubectl get svc

#查看某个 service 信息
$ kubectl describe svc <svc_name>

# 删除命令
$ kubectl delete po <pod>
$ kubectl delete svc <service>
$ kubectl delete deployment <deployment>
$ kubectl delete ing <ingress>

# 更改命令
$ kubectl edit po <pod>
$ kubectl edit svc <service>
$ kubectl edit deployment <deployment>
$ kubectl edit ing <ingress>

查看日志输出

# 查看一个pod的所有输出
kubectl logs mypod --all-containers=true

# 查看一个pod的某容器的输出 kubectl logs <pod_name> -c <container_name>

# 如果不指定容器,那么只会选一个输出
kubectl logs <pod_name>

# 以 tail -f 的方式去查看日志 kubectl logs mypod --all-containers=true -f

标签操作

# 加标签
kubectl label nodes <node_name> <key=value>

# 查看标签 kubectl get nodes --show-labels

查看pod 的ip地址,如果有标签的话,可以再加个-l key=value 来过滤

$ kubectl get pods -o yaml | grep podIP

环境变量

下面这是一个可以用来验证环境变量的 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: "172.17.0.1"
        - 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)"

运行后,可以得出结论

  1. 在 env 中定义的变量,是有依赖关系的:意味着,你不能在一个变量还没定义前就引用它,否则将是一个普通的字符串。
  2. 如果你在一个变量中想表示 ${VAR_NAME} 这个字符串,需要再在前面加一个 $ 进行转义,也即 $${VAR_NAME}
  3. 上面的 value 中的变量名,必须使用 $(VAR) 的形式书写,括号不能省略。在 command 或者 args 字段中 也有类似的要求。
env:
- name: MESSAGE
  value: "hello world"
command: ["/bin/echo"]
args: ["$(MESSAGE)"]

cgroup

cd /sys/fs/cgroup/cpu
创建一个目录,在里面有一个cpu.cfs_quota_us,使用 Echo 填入 20000,也就是 20% 的cpu时间,然后再入 task,往里面写入 进程id,就会限制 这个进程id的cpu使用时间,实现cpu的配额。

对应到 docker 中

# .2 也就是 0.2 的意思
$ docker run -it --cpu=".2" nginx /bin/sh

运行后,再进入 /sys/fs/cgroup/cpu 查看cpu.cfs_quota_us,就是 50000 了。

编写技巧

如果你使用 VS Code,可以装一个叫做 Kubernets 的插件,装完后,只要你先输入 kind: 再输入你要创建的对象类型,就可以自动导入模板

K8S 学习笔记插图(10)

选中后回车,就变成这样子,你再这个模板的基础上填填补补就好啦~ 这样就方便多了。

K8S 学习笔记插图(11)

5. 部署 Dashboard

先准备如下 yaml 文件 kubernetes-dashboard.yaml

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: Namespace
metadata:
  name: kubernetes-dashboard

---

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 31111
  selector:
    k8s-app: kubernetes-dashboard

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-csrf
  namespace: kubernetes-dashboard
type: Opaque
data:
  csrf: ""

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-key-holder
  namespace: kubernetes-dashboard
type: Opaque

---

kind: ConfigMap
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-settings
  namespace: kubernetes-dashboard

---

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
rules:
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
    verbs: ["get", "update", "delete"]
    # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["kubernetes-dashboard-settings"]
    verbs: ["get", "update"]
    # Allow Dashboard to get metrics.
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["heapster", "dashboard-metrics-scraper"]
    verbs: ["proxy"]
  - apiGroups: [""]
    resources: ["services/proxy"]
    resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
    verbs: ["get"]

---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
rules:
  # Allow Metrics Scraper to get metrics from the Metrics server
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list", "watch"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
        - name: kubernetes-dashboard
          image: registry.cn-beijing.aliyuncs.com/qingfeng666/dashboard:v2.0.4
          imagePullPolicy: Always
          ports:
            - containerPort: 8443
              protocol: TCP
          args:
            - --auto-generate-certificates
            - --namespace=kubernetes-dashboard
            # Uncomment the following line to manually specify Kubernetes API server Host
            # If not specified, Dashboard will attempt to auto discover the API server and connect
            # to it. Uncomment only if the default does not work.
            # - --apiserver-host=http://my-address:port
          volumeMounts:
            - name: kubernetes-dashboard-certs
              mountPath: /certs
              # Create on-disk volume to store exec logs
            - mountPath: /tmp
              name: tmp-volume
          livenessProbe:
            httpGet:
              scheme: HTTPS
              path: /
              port: 8443
            initialDelaySeconds: 30
            timeoutSeconds: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      volumes:
        - name: kubernetes-dashboard-certs
          secret:
            secretName: kubernetes-dashboard-certs
        - name: tmp-volume
          emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 8000
      targetPort: 8000
  selector:
    k8s-app: dashboard-metrics-scraper

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: dashboard-metrics-scraper
  template:
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
      annotations:
        seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
    spec:
      containers:
        - name: dashboard-metrics-scraper
          image: registry.cn-beijing.aliyuncs.com/qingfeng666/metrics-scraper:v1.0.4
          ports:
            - containerPort: 8000
              protocol: TCP
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 8000
            initialDelaySeconds: 30
            timeoutSeconds: 30
          volumeMounts:
          - mountPath: /tmp
            name: tmp-volume
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      volumes:
        - name: tmp-volume
          emptyDir: {}

然后执行命令创建

kubectl apply -f kubernetes-dashboard.yaml

查看服务

kubectl get svc -n kubernetes-dashboard

此时访问如下地址,在界面上输入 thisisunsafe,会跳转到 token 登陆界面

https://192.168.56.201:31111

然后到集群中输入如下命令,获取 token

kubectl -n kube-system describe $(kubectl -n kube-system get secret -n kube-system -o name | grep namespace) | grep token

使用阿里云的镜像源

要想使用阿里云的镜像源,首先你得有阿里云帐号,然后再开通容器镜像服务

链接:https://cr.console.aliyun.com/cn-shenzhen/instances/images

K8S 学习笔记插图(12)

文档学习

weixin

明哥公众号

文章有帮助,请作者喝杯咖啡?

发表评论