# Kubernetes

## 容器编排工具

* docker 官方编排工具

```
docker compose    # 单机编排工具
docker swarm      # 将多台 docker 提供的计算资源整合的接口，随后 docker compose 编排的时候只需要面向这个整合的接口进行编排就行，无论接口下有多少个主机。
docker mechine    # 将一个主机初始化为一个能够加入 docker swarm 集群中的预置程序
```

* mesos IDC 操作系统

```
IDC 操作系统，能将一个 IDC 提供的硬件资源，统一调度和分配，它只是一个资源分配工具，非能够直接托管容器的，所以它提供了以个能够直接编排框架，marathon。
```

* kubernetes

```bash
目前最流行的容器编排工具,市场占有率最高
```

## kubernetes

kubernetes 是希腊语，翻译过来是：舵手的意思，它的原型是谷歌内部使用 Borg 集群管理系统，可以说是集结了 Borg 设计思想的精华，并且吸收了 Borg 系统中的经验和教训。

它的目标不仅仅是一个编排系统，而是提供一个规范，可以让你来描述集群的架构，定义服务的最终状态，Kubernetes可以帮你将系统自动地达到和维持在这个状态。Kubernetes作为云原生应用的基石，相当于一个云操作系统，其重要性不言而喻。

kubernetes 在 2014 年发布了第一个版本，目前开源并托管在 Github 上。

```bash
https://github.com/Kubernetes
```

目前，AWS、阿里云、微软云，目前已经原生支持 K8S ，目前已经可以让用户直接部署云原生的服务。

* 有什么优势

```
- 基于 Borg 系统，设计成熟，开源、且轻量级，简单易学、容易理解；
- 模块化，可插拔，支持钩子，可任意组合，例如：网络组件 flannel，存储插件；
- 故障发现（存活性探针）和自我修复能力（副本数量）、服务滚动升级（就绪探针）和在线扩容（副本数量）密钥和配置管理；
- 可扩展的资源自动调度机制（多维度的水平自动扩容）、多粒度的资源配额管理能力（资源限制）。
```

## 环境架构

Kubernetes 是一个集群，整合多台计算机的计算能力，它是一种有中心节点模式的集群，在 K8S 集群中主机分为两种角色：

```
Master：集群的管理节点，有一个或者一组节点，一般 3 个足够了。
nodes：提供计算资源的节点，就是运行容器的节点，可以扩展。
```

客户端创建启动容器的请求交给 Master ，Master 上有一个**调度器**它能分析各 nodes 节点上的资源状态，找一个最适合运行用户容器的节点，并在这个节点上使用 Docker 启动这个容器，node 节点的 Docker 在启动容器时候会首先检查本地有没有镜像，如果没有就从仓库中 pull 然后运行。

那么仓库可以运行为容器，所以也可以托管在 Kubernetes 之上，其实 Kubernetes 可以托管自身，即自托管。

* ApiServer

kubernetes 接收用户创建容器等请求的是 Kubernetes Cluster，那么它对外提供服务的接口就是一个 API 接口 ，这个接口需要编程来访问，或者通过编写好的客户端程序来访问，Kubernetes Master 上有一个组件就是 ApiServer，来接收客端请求，解析客户端请求，其主要功能包括认证授权、数据校验以及集群状态变更，以及负责其他模块直接的相互通讯和数据交互，只有api server才能操作etcd，其他模块想要获取数据需要通过api server提供的接口进行相关数据操作

* Scheduler

scheduler watch apiserver，接受系统或用户请求是运行，如何要运行一个pod，那么 Master 会使用调度器（scheduler）根据请求来分配一个能够运行容器的 nodes 节点，例如：根据用户对资源要求，CPU、内存、来评估哪个 nodes 最合适运行。

大概的过程就是：首先是预选，从 nodes 中挑选出符合用户容器运行要求的，然后在这些预选结果中进行优选，选出最佳的适配 node。

* Controller（控制器）

如果运行容器的节点宕机或者容器本身运行出现问题，kubernetes 能够在其他节点再启动一个一模一样的容器，这就是 Kubernetes 提供的自愈能力。

控制器就实现了监控它所负责的每一个容器的健康状态，一旦发现不健康了，那么控制器会向 Master 发送请求，Master 会再次由调度器挑选出合适的节点再次运行这个容器。

它能持续性探测所管理的容器，一旦不健康，或不符合用户定义的健康状态，就会由它发起来请求，来保证容器向用户希望的健康状态迁徙。

而 Kubernets 支持众多的控制器，支持容器健康的控制器只是其中一种。

* ControllerManager（制器管理器）

在 Master 内置组件中有一个控制器管理器，它负责监视着每一个控制器，如果控制器不健康无法工作，那么由控制器管理器来确保控制器的健康，由于 Master 有多个，所以具有冗余性。

* Pod（原子调度单元，是容器的封装）

在 Kubernetes 上调度的原子单元，Kubernetes 不直接调度容器，而是 Pod，Pod可以理解为容器的二次封装，可以由一个或者多个容器组成，多个容器共享同一个网络名称空间：NET、UTS、IPC。

同一个 POD 里的容器，还能共享同一个存储卷，存储卷可以属于 POD。

一般一个 POD 只运行一个容器，如果需要在POD放多个容器，那么一般有一个主容器，其他容器是为主容器提供服务的。

* Node（工作节点）

提供计算资源的节点，就是运行 Pod 的主机，Kubenetes Cluster 统一管理所有的 node 节点的计算资源，当用户请求创建资源的时候，可以检查目前集群还有没有资源可以运行用户的容器，这实现了统一调度统一管理的一个平台。

* Label（标签）

一个由 `key = value` 组成的标签，可以为 POD 打上一个标签。

* Selecter（标签选择器）

集群中运行的众多 POD ，前面提到一个控制器可以管理若干个 POD ，那么控制器如何从集群中运行的所有 POD 中挑选出来自己需要管理的 POD 呢?

在创建一个 POD 的时候为 POD 打上一个标签，让程序可以通过这个标签来识别出来这个POD，还可以用来区分一组相同功能的POD，例如：创建四个nginx pod，可以给每个pod加一个 K/V类型的标签如：app=nginx，将来找出这四个 nginx pod，那么条件就是根据 拥有 key 为 app 的pod 并且 value 为 nginx 来挑出这组 POD。

标签不是 POD 唯一具有的机制，其他的组件同样可以有标签。

## 架构和组件

* Etcd

```bash
用于 Kubernetes 的后端数据存储,所有集群数据都存储在此处
```

* Master 节点负责维护集群的目标状态，上面运行的主控组件有

```bash
kube-apiserver                 # 对外暴露了 Kubernetes API，它是的 Kubernetes 前端控制层，只有 API Server 会与 etcd 通信，其它模块都必须通过 API Server 访问集群状态
kube-controller-manager        # 处理集群中常规任务，它是单独的进程，内部包含多个控制器，例如维护 POD 数量
kube-scheduler                 # 监视新创建的 Pod 为新创建的 POD 分配合适的 node 节点
```

* Node 节点实际负责实施，也就是运行 POD 的节点，上面运行的组件有

```bash
kubelet                        # 节点自注册和节点状态更新，它监测已经分配给自己的 Pod，为 POD 准备卷，下载 POD 所需的 Secret，下载镜像并运行，进行生命周期探测，上报 POD 和节点状态
kube-proxy                     # 通过维护主机上的网络规则并执行连接转发，将 Kubernetes 提供的网络服务代理到每个节点上，实现了Kubernetes服务抽象
docker                         # 用于运行容器
```

* 插件

```bash
插件是增强集群功能的 Pod 和 Service,插件对象本身是受命名空间限制的,被创建于 kube-system 命名空间.
```

* DNS

```bash
虽然其他插件并不是必需的,但所有 Kubernetes 集群都应该具有Cluster DNS,许多应用依赖于它,为 Kubernetes 服务提供DNS记录,容器启动该后会自动将 DNS 服务器包含在 resolv.conf 中.
```


# Kubernetes之etcd数据库

etcd是CoreOS团队于2013年6月发起的开源项目，它的目标是构建一个高可用的分布式键值(key-value)数据库。etcd内部采用raft协议作为一致性算法，etcd基于Go语言实现。

etcd作为服务发现系统，有以下的特点：

* 简单：安装配置简单，而且提供了HTTP API进行交互，使用也很简单
* 安全：支持SSL证书验证
* 快速：根据官方提供的benchmark数据，单实例支持每秒2k+读操作
* 可靠：采用raft算法，实现分布式系统数据的可用性和一致性

由于上面的特点和优势，etcd也被作为k8s默认的存储数据库，今天我们来讲一讲如何部署etcd数据库集群以及etcd的一些常见使用方法。

## Docker-compose搭建etcd集群

编写docker-compose.yml文件，具体内容如下

```
version: '2'
networks:
  byfn:

services:
  etcd1:
    image: quay.io/coreos/etcd
    container_name: etcd1
    command: etcd -name etcd1 -advertise-client-urls http://0.0.0.0:2379 -listen-client-urls http://0.0.0.0:2379 -listen-peer-urls http://0.0.0.0:2380 -initial-cluster-token etcd-cluster -initial-cluster "etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380" -initial-cluster-state new
    ports:
      - 2379
      - 2380
    networks:
      - byfn

  etcd2:
    image: quay.io/coreos/etcd
    container_name: etcd2
    command: etcd -name etcd2 -advertise-client-urls http://0.0.0.0:2379 -listen-client-urls http://0.0.0.0:2379 -listen-peer-urls http://0.0.0.0:2380 -initial-cluster-token etcd-cluster -initial-cluster "etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380" -initial-cluster-state new
    ports:
      - 2379
      - 2380
    networks:
      - byfn

  etcd3:
    image: quay.io/coreos/etcd
    container_name: etcd3
    command: etcd -name etcd3 -advertise-client-urls http://0.0.0.0:2379 -listen-client-urls http://0.0.0.0:2379 -listen-peer-urls http://0.0.0.0:2380 -initial-cluster-token etcd-cluster -initial-cluster "etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380" -initial-cluster-state new
    ports:
      - 2379
      - 2380
    networks:
      - byfn
```

参数介绍：

* data-dir 指定节点的数据存储目录，这些数据包括节点ID，集群ID，集群初始化配置，
* Snapshot文件，若未指定—wal-dir，还会存储WAL文件；
* wal-dir 指定节点的was文件的存储目录，若指定了该参数，wal文件会和其他数据文件分开存储。
* name 节点名称
* initial-advertise-peer-urls 告知集群其他节点url.
* listen-peer-urls 监听URL，用于与其他节点通讯
* advertise-client-urls 告知客户端url, 也就是服务的url
* initial-cluster-token 集群的ID
* initial-cluster 集群中所有节点
* initial-cluster-state 监听客户端状态
* listen-client-urls 监听客户端地址
* initial-cluster-state new 初始化集群 为新节点

然后docker-compose运行etcd的yaml文件，并且插入数据进行检查验证

```
[root@VM-0-13-centos docker-compose]# docker-compose up -d
//查看集群节点状态
[root@VM-0-13-centos docker-compose]# etcdctl --endpoints 127.0.0.1:32771 member list
ade526d28b1f92f7, started, etcd1, http://etcd1:2380, http://0.0.0.0:2379, false
bd388e7810915853, started, etcd3, http://etcd3:2380, http://0.0.0.0:2379, false
d282ac2ce600c1ce, started, etcd2, http://etcd2:2380, http://0.0.0.0:2379, false
//给etcd1节点插入数据
[root@VM-0-13-centos docker-compose]# curl -L http://127.0.0.1:32771/v2/keys/etcd -XPUT -d value="Hello etcd1"
{"action":"set","node":{"key":"/etcd","value":"Hello etcd1","modifiedIndex":8,"createdIndex":8}}
//检查在etcd2中是否能够查到
[root@VM-0-13-centos docker-compose]# curl -L http://127.0.0.1:32773/v2/keys/etcd
{"action":"get","node":{"key":"/etcd","value":"Hello etcd1","modifiedIndex":8,"createdIndex":8}}
//检查在etcd3中是否能够查到
[root@VM-0-13-centos docker-compose]# curl -L http://127.0.0.1:32769/v2/keys/etcd
{"action":"get","node":{"key":"/etcd","value":"Hello etcd1","modifiedIndex":8,"createdIndex":8}}
```

## 二进制部署etcd集群

这个可以参考之前的文章 二进制搭建k8s集群 <https://www.niewx.cn/kubernetes/docker/2020/09/20/%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2k8s/>

其中部署etcd集群章节有说明如何搭建

## kubeadm集群如何使用etcd集群

一般我们如果通过kubeadm创建的集群都是单节点的etcd，那么如何配置一个高可用的etcd集群给kubeadm的集群。首先你需要通过kubeadm搭建一个集群，你可以参考文章进行部署<https://www.niewx.cn/kubernetes/docker/2020/09/15/kubeadm%E9%83%A8%E7%BD%B2k8s/>

也可以通过<https://www.niewx.cn/kubernetes/docker/2020/05/10/k8s%E4%B8%80%E9%94%AE%E9%83%A8%E7%BD%B2%E8%84%9A%E6%9C%AC/> 一键部署包进行安装，这里就不细说了

集群部署好之后，我们下面来部署etcd集群，并接入对应的集群中，大致步骤如下

* 新建一个 2 节点的 etcd cluster
* 查看 etcd 的状态
* 迁移原来 master 节点上的 etcd 数据到上面新建的 etcd cluster 中
* 切换 kube-apiserver 使用新的 etcd endpoint 地址
* 清理掉原来的单节点 etcd 服务
* 重建一个 etcd 服务，加入新集群
* 部署新的 etcd 节点
* 更新另外2个节点的 etcd.yaml 配置

### 新建一个2节点的 etcd cluster

```
//基于当前 master 节点 tvm-00 的 etcd 配置来修改：
[root@tvm-00 ~]# scp /etc/kubernetes/manifests/etcd.yaml 10.10.9.68:/tmp/
[root@tvm-00 ~]# scp /etc/kubernetes/manifests/etcd.yaml 10.10.9.69:/tmp/
//修改 etcd 配置，设置成一个全新的 cluster
[root@tvm-01 ~]# cat /tmp/etcd.yaml // (略过部分没有改动的输出内容)
spec:
 containers:
 - command:
 - etcd
 - --name=etcd-01
 - --initial-advertise-peer-urls=http://10.10.9.68:2380
 - --listen-peer-urls=http://10.10.9.68:2380
 - --listen-client-urls=http://127.0.0.1:2379,http://10.10.9.68:2379
 - --advertise-client-urls=http://10.10.9.68:2379
 - --initial-cluster-token=etcd-cluster
 - --initial-cluster=etcd-01=http://10.10.9.68:2380,etcd-02=http://10.10.9.69:2380
 - --initial-cluster-state=new
 - --data-dir=/var/lib/etcd
 image: gcr.io/google_containers/etcd-amd64:3.1.10 // (略过部分没有改动的输出内容)
[root@tvm-02 ~]# cat /tmp/etcd.yaml // (略过部分没有改动的输出内容)
spec:
 containers:
 - command:
 - etcd
 - --name=etcd-02
 - --initial-advertise-peer-urls=http://10.10.9.69:2380
 - --listen-peer-urls=http://10.10.9.69:2380
 - --listen-client-urls=http://127.0.0.1:2379,http://10.10.9.69:2379
 - --advertise-client-urls=http://10.10.9.69:2379
 - --initial-cluster-token=etcd-cluster
 - --initial-cluster=etcd-01=http://10.10.9.68:2380,etcd-02=http://10.10.9.69:2380
 - --initial-cluster-state=new
 - --data-dir=/var/lib/etcd
 image: gcr.io/google_containers/etcd-amd64:3.1.10 //(略过部分没有改动的输出内容)
//启动 etcd cluster### 配置文件同步到 manifests 后将会被 kubelet 检测到然后自动将 pod 启动
[root@tvm-01 ~]# rm /var/lib/etcd -fr
[root@tvm-01 ~]# cp -a /tmp/etcd.yaml /etc/kubernetes/manifests/
[root@tvm-02 ~]# rm /var/lib/etcd -fr
[root@tvm-02 ~]# cp -a /tmp/etcd.yaml /etc/kubernetes/manifests/
```

### 查看 etcd 的状态

```
//下载一个 etcdctl 工具来管理集群：
[root@tvm-00 ~]# cd /usr/local/bin/
[root@tvm-00 ~]# wget 
https://github.com/coreos/etcd/releases/download/v3.1.10/etcd-v3.1.10-linux-amd64.tar.gz
[root@tvm-00 ~]# tar zxf etcd-v3.1.10-linux-amd64.tar.gz
[root@tvm-00 ~]# mv etcd-v3.1.10-linux-amd64/etcd* .
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl --endpoints
 "http://10.10.9.68:2379,http://10.10.9.69:2379" endpoint status
http://10.10.9.68:2379, 21b9c7066a7e525, 3.1.10, 25 kB, true, 7, 194
http://10.10.9.69:2379, 516e519b2158e83a, 3.1.10, 25 kB, false, 7, 194
//注意：输出的列从左到右分别表示：endpoint URL, ID, version, database size, leadership status, raft term, and raft status.### 符合预期。
```

### 迁移原来 master 节点上的 etcd 数据到上面新建的 etcd cluster 中

```
//注意：etcdctl 3.x 版本提供了一个 make-mirror 功能来同步数据### 在当前 master 节点 
tvm-00 上执行：
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl make-mirror --no-dest-prefix=true 
--endpoints=127.0.0.1:2379 --insecure-skip-tls-verify=true 10.10.9.68:2379
//将数据同步到远端刚才新建的 etcd 集群中### 注意1：数据是从 127.0.0.1:2379 写入到 10.10.9.68:2379### 注意2：这个同步只能是手动中止，间隔 30s 打印一次输出
//通过对比集群到状态来判断是否同步完成：###（新开一个窗口）
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl endpoint status
127.0.0.1:2379, 8e9e05c52164694d, 3.1.10, 1.9 MB, true, 2, 342021
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl --endpoints 
"http://10.10.9.68:2379,http://10.10.9.69:2379" endpoint status
http://10.10.9.68:2379, 21b9c7066a7e525, 3.1.10, 1.9 MB, true, 7, 1794
http://10.10.9.69:2379, 516e519b2158e83a, 3.1.10, 1.9 MB, false, 7, 1794
```

### 切换 kube-apiserver 使用新的 etcd endpoint 地址

```
//停止 kubelet 服务：
[root@tvm-00 ~]# systemctl stop kubelet
//更新 kube-apiserver.yaml 中 etcd 服务到地址，切到我们到新集群中：
[root@tvm-00 ~]# sed -i 's#127.0.0.1:2379#10.10.9.68:2379#' 
/etc/kubernetes/manifests/kube-apiserver.yaml
//启动 kubelet 服务：
[root@tvm-00 ~]# systemctl start kubelet
[root@tvm-00 ~]# kubectl get pods --all-namespaces |grep 'etcd-tvm'
kube-system etcd-tvm-00 1/1 Running 1 4h
kube-system etcd-tvm-01 1/1 Running 0 1h
kube-system etcd-tvm-02 1/1 Running 0 1h
```

### 清理掉原来的单节点 etcd 服务

```
[root@tvm-00 ~]# mv /etc/kubernetes/manifests/etcd.yaml /tmp/orig.master.etcd.yaml
[root@tvm-00 ~]# mv /var/lib/etcd /tmp/orig.master.etcd // 观察 pods 的变化：
[root@tvm-00 ~]# kubectl get pods --all-namespaces |grep 'etcd-tvm'
kube-system etcd-tvm-01 1/1 Running 0 1h
kube-system etcd-tvm-02 1/1 Running 0 1h
//符合预期 etcd-tvm-00 停止服务
```

### 重建一个 etcd 服务，加入新集群

```
[root@tvm-00 ~]# cat /tmp/etcd.yaml //(略过部分没有改动的输出内容)
spec:
 containers:
 - command:
 - etcd
 - --name=etcd-00
 - --initial-advertise-peer-urls=http://10.10.9.67:2380
 - --listen-peer-urls=http://10.10.9.67:2380
 - --listen-client-urls=http://127.0.0.1:2379,http://10.10.9.67:2379
 - --advertise-client-urls=http://10.10.9.67:2379
 - --initial-cluster-token=etcd-cluster
- --initial-cluster=etcd-00=http://10.10.9.67:2380,etcd-01=http://10.10.9.68:2380,etcd-02=http://10.10.9.69:2380
 - --initial-cluster-state=existing
 - --data-dir=/var/lib/etcd
 image: gcr.io/google_containers/etcd-amd64:3.1.10 //(略过部分没有改动的输出内容)
//注意：上述新节点的配置有一个地方不一样：
--initial-cluster-state=existing
```

### 先配置 etcd cluster 增加一个 member 用于后续操作

```
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl --endpoints="http://10.10.9.68:2379" member list
21b9c7066a7e525, started, etcd-01, http://10.10.9.68:2380, http://10.10.9.68:2379
516e519b2158e83a, started, etcd-02, http://10.10.9.69:2380, http://10.10.9.69:2379
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl --endpoints="http://10.10.9.68:2379" member add etcd-00 --peer-urls=http://10.10.9.67:2380
 Member 6cc2e7728adb6b28 added to cluster 3742ed98339167da
ETCD_NAME="etcd-00"
 ETCD_INITIAL_CLUSTER="etcd-01=http://10.10.9.68:2380,etcd-02=http://10.10.9.69:2380,etcd-00=http://10.10.9.67:2380"
 ETCD_INITIAL_CLUSTER_STATE="existing"
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl --endpoints="http://10.10.9.68:2379" member list
 21b9c7066a7e525, started, etcd-01, http://10.10.9.68:2380, http://10.10.9.68:2379
 516e519b2158e83a, started, etcd-02, http://10.10.9.69:2380, http://10.10.9.69:2379
 6cc2e7728adb6b28, unstarted, , http://10.10.9.67:2380,
//部署新的 etcd 节点
[root@tvm-00 ~]# rm /var/lib/etcd -fr
[root@tvm-00 ~]# cp -a /tmp/etcd.yaml /etc/kubernetes/manifests/
//再次查看 k8s cluster 信息
[root@tvm-00 ~]# kubectl get pods --all-namespaces |grep 'etcd-tvm'
kube-system etcd-tvm-00 1/1 Running 1 4h
kube-system etcd-tvm-01 1/1 Running 0 1h
kube-system etcd-tvm-02 1/1 Running 0 1h
//etcd 的日志：
[root@tvm-00 ~]# kubectl logs -n kube-system --tail=20 etcd-tvm-00
//etcd clister 状态：
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl 
--endpoints="http://10.10.9.67:2379,http://10.10.9.68:2379,http://10.10.9.69:2379" member List
21b9c7066a7e525, started, etcd-01, http://10.10.9.68:2380, http://10.10.9.68:2379
516e519b2158e83a, started, etcd-02, http://10.10.9.69:2380, http://10.10.9.69:2379
6cc2e7728adb6b28, started, etcd-00, http://10.10.9.67:2380, http://10.10.9.67:2379
[root@tvm-00 ~]# ETCDCTL_API=3 etcdctl --endpoints
"http://10.10.9.67:2379,http://10.10.9.68:2379,http://10.10.9.69:2379" endpoint status
http://10.10.9.67:2379, 6cc2e7728adb6b28, 3.1.10, 3.8 MB, false, 7, 5236
http://10.10.9.68:2379, 21b9c7066a7e525, 3.1.10, 3.3 MB, true, 7, 5236
http://10.10.9.69:2379, 516e519b2158e83a, 3.1.10, 3.3 MB, false, 7, 5236
```

### 更新另外2个节点的 etcd.yaml 配置

```
//区别之处：
- --initial-cluster=etcd-00=http://10.10.9.67:2380,etcd-01=http://10.10.9.68:2380,etcd-02=http://10.10.9.69:2380
- --initial-cluster-state=existing
//将节点 tvm-00 上 kube-apiserver 使用的 etcd endpoint 切换回来
[root@tvm-00 ~]# sed -i 's#10.10.9.68:2379#127.0.0.1:2379#' 
/etc/kubernetes/manifests/kube-apiserver.yaml
[root@tvm-00 ~]# kubectl get pods --all-namespaces |grep api
kube-system kube-apiserver-tvm-00 1/1 Running 0 1m
```

### kubeadm使用已有etcd集群

假如我们已经提前搭建好了一个etcd集群，那么在kubeadm进行部署的时候如何去使用这个集群，其实只需要在kubeadm中进行配置即可

```
cat <<EOF > /etc/kubernetes/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: v1.13.0
apiServer:
 certSANs:
 - 10.127.24.179
 - 127.0.0.1
networking:
 podSubnet: 10.244.0.0/16
etcd:
 external:
 endpoints:
 - https://10.39.14.204:2379
 - https://10.39.14.205:2379
 - https://10.39.14.206:2379
 caFile: /etc/kubernetes/pki/etcd/ca.crt
 certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
 keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
EOF
```

## 如何在k8s中搭建etcd集群

这里我们可以使用 StatefulSet 这个控制器来运行 etcd 集群，etcd 集群的编排的资源清单文件我们可以使用 Kubernetes 源码中提供的，位于目录：test/e2e/testing-manifests/statefulset/etcd下面。

```
git clone https://github.com/kubernetes/kubernetes
cd kubernetes/test/e2e/testing-manifests/statefulset/etcd
ll //查看对应的yaml文件如下
-rw-r--r-- 1 nieweixing 197121  184 11月 11 00:10 pdb.yaml
-rw-r--r-- 1 nieweixing 197121  258 11月 11 00:10 service.yaml
-rw-r--r-- 1 nieweixing 197121 6619 11月 11 00:10 statefulset.yaml
-rw-r--r-- 1 nieweixing 197121  577 11月 11 00:10 tester.yaml
```

service.yaml文件中就是一个用户 StatefulSet 使用的 headless service：

```
apiVersion: v1
kind: Service
metadata:
  name: etcd
  labels:
    app: etcd
spec:
  ports:
  - port: 2380
    name: etcd-server
  - port: 2379
    name: etcd-client
  clusterIP: None
  selector:
    app: etcd
  publishNotReadyAddresses: true
```

pdb.yaml文件是用来保证 etcd 的高可用的一个 PodDisruptionBudget 资源对象，PodDisruptionBudget 说明详情可以参考文档<https://kubernetes.io/zh/docs/tasks/run-application/configure-pdb/>

```
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: etcd-pdb
  labels:
    pdb: etcd
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: etcd
```

statefulset.yaml，这里修改下<http://${HOSTNAME}.${SET_NAME}成http://${POD_IP}:PORT这样>

```
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: etcd
  name: etcd
spec:
  replicas: 3
  selector:
    matchLabels:
      app: etcd
  serviceName: etcd
  template:
    metadata:
      labels:
        app: etcd
    spec:
      containers:
        - name: etcd
          image: k8s.gcr.io/etcd:3.2.24
          imagePullPolicy: IfNotPresent
          ports:
          - containerPort: 2380
            name: peer
            protocol: TCP
          - containerPort: 2379
            name: client
            protocol: TCP
          env:
          - name: INITIAL_CLUSTER_SIZE
            value: "3"
          - name: MY_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
          - name: SET_NAME
            value: "etcd"
          command:
            - /bin/sh
            - -ec
            - |
              HOSTNAME=$(hostname)

              ETCDCTL_API=3

              eps() {
                  EPS=""
                  for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                      EPS="${EPS}${EPS:+,}http://${SET_NAME}-${i}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2379"
                  done
                  echo ${EPS}
              }

              member_hash() {
                  etcdctl member list | grep -w "$HOSTNAME" | awk '{ print $1}' | awk -F "," '{ print $1}'
              }

              initial_peers() {
                  PEERS=""
                  for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                    PEERS="${PEERS}${PEERS:+,}${SET_NAME}-${i}=http://${SET_NAME}-${i}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2380"
                  done
                  echo ${PEERS}
              }

              # etcd-SET_ID
              SET_ID=${HOSTNAME##*-}

              # adding a new member to existing cluster (assuming all initial pods are available)
              if [ "${SET_ID}" -ge ${INITIAL_CLUSTER_SIZE} ]; then
                  # export ETCDCTL_ENDPOINTS=$(eps)
                  # member already added?

                  MEMBER_HASH=$(member_hash)
                  if [ -n "${MEMBER_HASH}" ]; then
                      # the member hash exists but for some reason etcd failed
                      # as the datadir has not be created, we can remove the member
                      # and retrieve new hash
                      echo "Remove member ${MEMBER_HASH}"
                      etcdctl --endpoints=$(eps) member remove ${MEMBER_HASH}
                  fi

                  echo "Adding new member"

                  echo "etcdctl --endpoints=$(eps) member add ${HOSTNAME} --peer-urls=http://${HOSTNAME}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2380"
                  etcdctl member --endpoints=$(eps) add ${HOSTNAME} --peer-urls=http://${HOSTNAME}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2380 | grep "^ETCD_" > /var/run/etcd/new_member_envs

                  if [ $? -ne 0 ]; then
                      echo "member add ${HOSTNAME} error."
                      rm -f /var/run/etcd/new_member_envs
                      exit 1
                  fi

                  echo "==> Loading env vars of existing cluster..."
                  sed -ie "s/^/export /" /var/run/etcd/new_member_envs
                  cat /var/run/etcd/new_member_envs
                  . /var/run/etcd/new_member_envs

                  echo "etcd --name ${HOSTNAME} --initial-advertise-peer-urls ${ETCD_INITIAL_ADVERTISE_PEER_URLS} --listen-peer-urls http://${POD_IP}:2380 --listen-client-urls http://${POD_IP}:2379,http://127.0.0.1:2379 --advertise-client-urls http://${HOSTNAME}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2379 --data-dir /var/run/etcd/default.etcd --initial-cluster ${ETCD_INITIAL_CLUSTER} --initial-cluster-state ${ETCD_INITIAL_CLUSTER_STATE}"

                  exec etcd --listen-peer-urls http://${POD_IP}:2380 \
                      --listen-client-urls http://${POD_IP}:2379,http://127.0.0.1:2379 \
                      --advertise-client-urls http://${HOSTNAME}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2379 \
                      --data-dir /var/run/etcd/default.etcd
              fi

              for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                  while true; do
                      echo "Waiting for ${SET_NAME}-${i}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local to come up"
                      ping -W 1 -c 1 ${SET_NAME}-${i}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local > /dev/null && break
                      sleep 1s
                  done
              done

              echo "join member ${HOSTNAME}"
              # join member
              exec etcd --name ${HOSTNAME} \
                  --initial-advertise-peer-urls http://${HOSTNAME}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2380 \
                  --listen-peer-urls http://${POD_IP}:2380 \
                  --listen-client-urls http://${POD_IP}:2379,http://127.0.0.1:2379 \
                  --advertise-client-urls http://${HOSTNAME}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2379 \
                  --initial-cluster-token etcd-cluster-1 \
                  --data-dir /var/run/etcd/default.etcd \
                  --initial-cluster $(initial_peers) \
                  --initial-cluster-state new
          lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - -ec
                  - |
                    HOSTNAME=$(hostname)

                    member_hash() {
                        etcdctl member list | grep -w "$HOSTNAME" | awk '{ print $1}' | awk -F "," '{ print $1}'
                    }

                    eps() {
                        EPS=""
                        for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                            EPS="${EPS}${EPS:+,}http://${SET_NAME}-${i}.${SET_NAME}.${MY_NAMESPACE}.svc.cluster.local:2379"
                        done
                        echo ${EPS}
                    }

                    export ETCDCTL_ENDPOINTS=$(eps)
                    SET_ID=${HOSTNAME##*-}

                    # Removing member from cluster
                    if [ "${SET_ID}" -ge ${INITIAL_CLUSTER_SIZE} ]; then
                        echo "Removing ${HOSTNAME} from etcd cluster"
                        etcdctl member remove $(member_hash)
                        if [ $? -eq 0 ]; then
                            # Remove everything otherwise the cluster will no longer scale-up
                            rm -rf /var/run/etcd/*
                        fi
                    fi
          volumeMounts:
          - mountPath: /var/run/etcd
            name: datadir
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes:
      - "ReadWriteOnce"
      resources:
        requests:
          # upstream recommended max is 700M
          storage: 10Gi
```

执行命令进行部署

```
kubectl apply -f .
```

等pod运行成功后，我们该如何访问呢，这我们给service改成对应的lb类型，这样可以直接通过公网ip个接口就可以访问了

```
[root@VM-0-13-centos etcd]# kubectl get svc
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)             AGE
etcd         ClusterIP      None             <none>          2380/TCP,2379/TCP   22h
etcd-test    LoadBalancer   172.16.83.34     106.53.131.xx   2379:31354/TCP      21h
kube-user    LoadBalancer   172.16.100.133   10.0.0.4        443:31745/TCP       28h
kubernetes   ClusterIP      172.16.0.1       <none>          443/TCP             29h
```

这里我们将2379端口通过LoadBalancer 类型的service映射成公网访问了，下面我们来用命令检查下集群。 首先下载etcdctl工具，我们登录客户端机器执行下面命令安装etcdctl工具

```
[root@tvm-00 ~]# cd /usr/local/bin/
[root@tvm-00 ~]# wget 
https://github.com/etcd-io/etcd/releases/download/v3.4.13/etcd-v3.4.13-linux-amd64.tar.gz
[root@tvm-00 ~]# tar zxf etcd-v3.4.13-linux-amd64.tar.gz
[root@tvm-00 ~]# mv etcd-v3.4.13-linux-amd64/etcd* .
```

然后执行命令检查集群，这边查看集群都是正常，说明这边集群部署成功

```
[root@VM-0-13-centos etcd]# etcdctl --endpoints 106.53.131.xx:2379 member list
42c8b94265b9b79a, started, etcd-2, http://etcd-2.etcd.default.svc.cluster.local:2380, http://etcd-2.etcd.default.svc.cluster.local:2379, false
9869f0647883a00d, started, etcd-1, http://etcd-1.etcd.default.svc.cluster.local:2380, http://etcd-1.etcd.default.svc.cluster.local:2379, false
c799a6ef06bc8c14, started, etcd-0, http://etcd-0.etcd.default.svc.cluster.local:2380, http://etcd-0.etcd.default.svc.cluster.local:2379, false

[root@VM-0-13-centos etcd]# etcdctl --endpoints 106.53.131.xx:2379 endpoint status --write-out=table
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|      ENDPOINT      |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 106.53.131.xx:2379 | c799a6ef06bc8c14 |  3.4.13 |   20 kB |     false |      false |         3 |          9 |                  9 |        |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
```

下面我们对etcd的pod进行扩缩容看看是否会有影响

```
[root@VM-0-13-centos etcd]# kubectl scale statefulsets etcd --replicas=5
statefulset.apps/etcd scaled
[root@VM-0-13-centos etcd]# kubectl get pod | grep etcd
etcd-0   1/1     Running   0          22h
etcd-1   1/1     Running   0          22h
etcd-2   1/1     Running   0          22h
etcd-3   2/2     Running   0          78s
etcd-4   2/2     Running   2          53s
[root@VM-0-13-centos etcd]# etcdctl --endpoints 106.53.131.xx:2379 endpoint status --write-out=table
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|      ENDPOINT      |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 106.53.131.xx:2379 | 42c8b94265b9b79a |  3.4.13 |   20 kB |      true |      false |         3 |         11 |                 11 |        |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
[root@VM-0-13-centos etcd]# etcdctl --endpoints 106.53.131.xx:2379 member list
42c8b94265b9b79a, started, etcd-2, http://etcd-2.etcd.default.svc.cluster.local:2380, http://etcd-2.etcd.default.svc.cluster.local:2379, false
9869f0647883a00d, started, etcd-1, http://etcd-1.etcd.default.svc.cluster.local:2380, http://etcd-1.etcd.default.svc.cluster.local:2379, false
c799a6ef06bc8c14, started, etcd-0, http://etcd-0.etcd.default.svc.cluster.local:2380, http://etcd-0.etcd.default.svc.cluster.local:2379, false
ef933addf9d37a32, started, etcd-3, http://etcd-3.etcd.default.svc.cluster.local:2380, http://etcd-3.etcd.default.svc.cluster.local:2379, false
[root@VM-0-13-centos etcd]# kubectl scale statefulsets etcd --replicas=3
statefulset.apps/etcd scaled
[root@VM-0-13-centos etcd]# kubectl get pod | grep etcd
etcd-0   1/1     Running   0          22h
etcd-1   1/1     Running   0          22h
etcd-2   1/1     Running   0          22h
[root@VM-0-13-centos etcd]# etcdctl --endpoints 106.53.131.xx:2379 member list
42c8b94265b9b79a, started, etcd-2, http://etcd-2.etcd.default.svc.cluster.local:2380, http://etcd-2.etcd.default.svc.cluster.local:2379, false
9869f0647883a00d, started, etcd-1, http://etcd-1.etcd.default.svc.cluster.local:2380, http://etcd-1.etcd.default.svc.cluster.local:2379, false
c799a6ef06bc8c14, started, etcd-0, http://etcd-0.etcd.default.svc.cluster.local:2380, http://etcd-0.etcd.default.svc.cluster.local:2379, false
[root@VM-0-13-centos etcd]# etcdctl --endpoints 106.53.131.xx:2379 endpoint status --write-out=table
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|      ENDPOINT      |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 106.53.131.xx:2379 | c799a6ef06bc8c14 |  3.4.13 |   20 kB |     false |      false |         3 |         12 |                 12 |        |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
```

这边扩缩容都是正常，这边可以给etcd配置hpa来实现整真正的高可用。

## etcdctl常用的命令

为了执行命名方便，我们在客户端机器直接写这个alias，将下面命令写到root目录下的.bashrc文件然后bash一下这个文件，新开一个session窗口，后续直接执行etcdcluster这个命令加参数即可。

```
etcdcluster='etcdctl --endpoints 106.53.131.xx:2379'
```

### PUT \[options]&#x20;

```
[root@VM-0-13-centos etcd]# etcdcluster  put foo bar 
OK
[root@VM-0-13-centos etcd]# etcdcluster  get foo
foo
ba
[root@VM-0-13-centos etcd]# etcdcluster put foo bar1 --prev-kv //覆盖之前的值
OK
foo
bar[root@VM-0-13-centos etcd]# etcdcluster get foo
foo
bar1
[root@VM-0-13-centos etcd]# etcdcluster put foo "bar1 2 3" //插入多个value值
OK
```

### GET \[options]  \[range\_end]

```
[root@VM-0-13-centos ~]# etcdcluster put foo1 bar1
OK
[root@VM-0-13-centos ~]# etcdcluster put foo2 bar2
OK
[root@VM-0-13-centos ~]# etcdcluster put foo3 bar3
OK
[root@VM-0-13-centos ~]# etcdcluster put foo bar
OK
[root@VM-0-13-centos ~]# etcdcluster get foo
foo
bar
[root@VM-0-13-centos ~]# etcdcluster get --from-key '' //获取所有的键
foo
bar
foo1
bar1
foo2
bar2
foo3
bar3
[root@VM-0-13-centos ~]# etcdcluster get --from-key foo1  //获取名称大于或等于的所有键
foo1
bar1
foo2
bar2
foo3
bar3[root@VM-0-13-centos ~]# etcdcluster get foo1 foo3 //获取名称大于等于foo1且小于foo2的所有键
foo1
bar1
foo2
bar2
```

### DEL \[options]  \[range\_end]

```
[root@VM-0-13-centos ~]# etcdcluster put foo bar
OK
[root@VM-0-13-centos ~]# etcdcluster del foo 
1
[root@VM-0-13-centos ~]# etcdcluster put key val //删除某个键
OK
[root@VM-0-13-centos ~]# etcdcluster del --prev-kv key //返回删除的键值
1
key
val
[root@VM-0-13-centos ~]# etcdcluster put a 123
OK
[root@VM-0-13-centos ~]# etcdcluster put b 456
OK
[root@VM-0-13-centos ~]# etcdcluster put c 789
OK
[root@VM-0-13-centos ~]# etcdcluster del --from-key a //从某个键开始删除
6
[root@VM-0-13-centos ~]# etcdcluster get --from-key a //检查a后面的是否删除
[root@VM-0-13-centos ~]# etcdcluster put zoo val
OK
[root@VM-0-13-centos ~]# etcdcluster put zoo1 val1
OK
[root@VM-0-13-centos ~]# etcdcluster put zoo2 val2
OK
[root@VM-0-13-centos ~]# etcdcluster del --prefix zoo //键值前缀匹配删除
3
[root@VM-0-13-centos ~]# etcdcluster get --from-key zoo
```

### 数据备份

```
[root@VM-0-13-centos ~]# etcdcluster snapshot save snapshot.db
{"level":"info","ts":1605871537.932119,"caller":"snapshot/v3_snapshot.go:119","msg":"created temporary db file","path":"snapshot.db.part"}
{"level":"info","ts":"2020-11-20T19:25:37.933+0800","caller":"clientv3/maintenance.go:200","msg":"opened snapshot stream; downloading"}
{"level":"info","ts":1605871537.9336457,"caller":"snapshot/v3_snapshot.go:127","msg":"fetching snapshot","endpoint":"106.53.131.85:2379"}
{"level":"info","ts":"2020-11-20T19:25:37.935+0800","caller":"clientv3/maintenance.go:208","msg":"completed snapshot read; closing"}
{"level":"info","ts":1605871537.9402754,"caller":"snapshot/v3_snapshot.go:142","msg":"fetched snapshot","endpoint":"106.53.131.85:2379","size":"25 kB","took":0.00810019}
{"level":"info","ts":1605871537.9403143,"caller":"snapshot/v3_snapshot.go:152","msg":"saved","path":"snapshot.db"}
Snapshot saved at snapshot.db
```

### 数据恢复

```
[root@VM-0-13-centos ~]# etcdcluster snapshot restore snapshot.db --data-dir=/var/run/etcd
{"level":"info","ts":1605871714.2255018,"caller":"snapshot/v3_snapshot.go:296","msg":"restoring snapshot","path":"snapshot.db","wal-dir":"/var/run/etcd/member/wal","data-dir":"/var/run/etcd","snap-dir":"/var/run/etcd/member/snap"}
{"level":"info","ts":1605871714.2265532,"caller":"membership/cluster.go:392","msg":"added member","cluster-id":"cdf818194e3a8c32","local-member-id":"0","added-peer-id":"8e9e05c52164694d","added-peer-peer-urls":["http://localhost:2380"]}
{"level":"info","ts":1605871714.242329,"caller":"snapshot/v3_snapshot.go:309","msg":"restored snapshot","path":"snapshot.db","wal-dir":"/var/run/etcd/member/wal","data-dir":"/var/run/etcd","snap-dir":"/var/run/etcd/member/snap"}
```

这里只做一些常用简单的命令进行操作，更多的命令使用可以参考<https://github.com/etcd-io/etcd/tree/master/etcdctl进行操作使用。>


# Kubernetes中pod的创建流程

一般我们在创建pod的过程中都是，执行kubectl命令去apply对应的yaml文件，但是在执行这个操作的过程到pod被完成创建，k8s的组件都做了哪些操作呢？下面我们简要说说pod被创建的过程。

![upload-image](/files/-MN2FKxyni7PYEAZ69hl)

1. 用户通过kubectl命名发起请求。
2. apiserver通过对应的kubeconfig进行认证，认证通过后将yaml中的po信息存到etcd。
3. Controller-Manager通过apiserver的watch接口发现了pod信息的更新，执行该资源所依赖的拓扑结构整合，整合后将对应的信息写到etcd，此时pod已经可以被调度了。
4. Scheduler同样通过apiserver的watch接口更新到pod可以被调度，通过算法给pod分配节点，并将pod和对应节点绑定的信息写到etcd，然后将pod交给kubelet。
5. kubelet收到pod后，调用CNI接口给pod创建pod网络，调用CRI接口去启动容器，调用CSI进行存储卷的挂载。
6. 网络，容器，存储创建完成后pod创建完成，等业务进程启动后，pod运行成功。


# k8s一键部署脚本

本章主要介绍了k8s一键部署脚本。

## k8s一键安装包介绍

### 版本说明

* kubernetes为1.15.2版本 &#x20;
* docker版本为docker-ce-19.03.1-3.el7 &#x20;
* docker-comnpose版本为1.18.0。 &#x20;

### 脚本功能说明

#### start.sh

启动部署k8s脚本,必须在master上执行脚本,并且脚本执行方式:

sh start.sh masterIP node1Ip node2Ip node3Ip ....

#### add\_node\_join\_k8s.sh

新加节点到k8s集群中，在master上执行必须保证master可以免密登录新的节点，执行方式:

add\_node\_join\_k8s.sh new-node-ip

#### modify\_docker\_storage\_path.sh

新增或者替换docker存储目录脚本，在对应的机器上执行,执行方式:

modify\_docker\_storage\_path.sh new\_storage\_path

#### push\_image\_registry.sh

上传镜像到新的register仓库，将上传的镜像load到任何一台机器上,执行方式:

sh push\_image\_registry.sh 按照提示输入仓库地址，用户名和密码

## 使用说明

1. 准备k8s部署节点，配置master节点到node节点免密登录，免密登录配置参考<https://www.cnblogs.com/www-yang-com/p/10419861.html>
2. 上传包到master节点，并解压到root目录，切换/root/auto-install-k8s/k8s-install-1.15.2
3. 执行脚本start.sh masterip node1-ip node2-ip ..... (注意需要在master上执行)
4. 稍等2分钟安装完毕后可以通过kubectl get node进行验证。
5. 本集群中有搭建私有镜像仓库在master节，用户名为registry，密码为000000

## 部署包下载地址

百度网盘链接: <https://pan.baidu.com/s/13ILXvdGwjnEXHN1cnkJ5UQ>

提取码: nw4b


# TKE操作笔记01

本文主要介绍了如何入门腾讯云上的TKE(Tencent Kubernetes Engine)服务。

## TKE简介

腾讯云容器服务（Tencent Kubernetes Engine，TKE）是高度可扩展的高性能容器管理服务，您可以在托管的云服务器实例集群上轻松运行应用程序。使用该服务，您将无需安装、运维、扩展您的集群管理基础设施，只需进行简单的 API 调用，便可启动和停止 Docker 应用程序，查询集群的完整状态，以及使用各种云服务。您可以根据资源需求和可用性要求在集群中安排容器的置放，满足业务或应用程序的特定要求。\
腾讯云容器服务基于原生 Kubernetes 提供以容器为核心的解决方案，解决用户开发、测试及运维过程的环境问题、帮助用户降低成本，提高效率。腾讯云容器服务完全兼容原生 Kubernetes API，并扩展了腾讯云的云硬盘、负载均衡等 Kubernetes 插件，同时以腾讯云私有网络为基础，实现了高可靠、高性能的网络方案。

![upload-image](/files/-MN2J96tPIx1Riiex_2M)

## 创建镜像仓库并授权连接github和gitlab

**镜像仓库概述**

镜像仓库用于存放 Docker 镜像，Docker 镜像用于部署容器服务，每个镜像有特定的唯一标识（镜像的 Registry 地址+镜像名称+镜像 Tag）。

**镜像类型**

目前镜像支持 Docker Hub 官方镜像和用户私有镜像(自己生成的业务镜像和一些订制的镜像)。

**授权步骤**

将腾讯云docker镜像仓库和Github授权连接，分为以下四步：

1.开通镜像仓库

2.新建命名空间

3.新建镜像仓库

4.源代码授权

### 腾讯云上开通镜像仓库

![upload-image](/files/-MN2J96umI2yo0OQ8vvb)

### 创建命名空间

![upload-image](/files/-MN2J96vpZddHclt3am0)

在容器服务中，点击镜像仓库会展开下拉框，点击我的镜像，首次使用镜像仓库的用户，需要先开通镜像仓库，输入用户名和密码。

* 用户名：默认是当前用户的账号，是您登录到腾讯云docker镜像仓库的身份。
* 密码：是您登录到腾讯云docker镜像仓库的凭证。

### 新建镜像仓库

![upload-image](/files/-MN2J96w5XKoJ_B2HaMU)

![upload-image](/files/-MN2J96xlkNH8Ym-FmrB)

点击我的镜像，点击我的镜像，点击新建，新建镜像仓库，输入名称，类型选择私有，命名空间选择之前新建的liangfeng，描述可自定义填写该仓库的用途。

* 类型：分为公有和私有，即公有镜像仓库和私有镜像仓库。如果你想将自己仓库下的镜像暴露在公网，且其他人都能够访问，则选择公有；如果只用于个人用户访问，则选择私有。

### 登录仓库上传下载镜像

现在我们举例将busybox的最新镜像上传到仓库并在另外的机器上下载镜像

```
登录仓库
[root@master]# docker login ccr.ccs.tencentyun.com/nwx_registry/busybox -u 100011007491 -p ********

WARNING! Using --password via the CLI is insecure. Use --password-stdin.

WARNING! Your password will be stored unencrypted in /root/.docker/config.json.

Configure a credential helper to remove this warning. See

https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

tag镜像
[root@master]# docker tag busybox:latest ccr.ccs.tencentyun.com/nwx_registry/busybox:latest

push镜像
[root@master]# docker push ccr.ccs.tencentyun.com/nwx_registry/busybox:latest

The push refers to repository [ccr.ccs.tencentyun.com/nwx_registry/busybox]

eac247cb7af5: Pushed

latest: digest: sha256:24fd20af232ca4ab5efbf1aeae7510252e2b60b15e9a78947467340607cd2ea2 size: 527

下载镜像
登录镜像仓库

[root@master]# docker login ccr.ccs.tencentyun.com/nwx_registry/busybox -u 100011007491 -p ********

WARNING! Using --password via the CLI is insecure. Use --password-stdin.

WARNING! Your password will be stored unencrypted in /root/.docker/config.json.

Configure a credential helper to remove this warning. See

https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

下载镜像

[root@master ~]# docker pull ccr.ccs.tencentyun.com/nwx_registry/busybox:latest

latest: Pulling from nwx_registry/busybox

Digest: sha256:24fd20af232ca4ab5efbf1aeae7510252e2b60b15e9a78947467340607cd2ea2

Status: Downloaded newer image for ccr.ccs.tencentyun.com/nwx_registry/busybox:latest
```

### 源代码授权

#### github上源代码授权

![upload-image](/files/-MN2J96y5HWfQZKSWBz3)

![upload-image](/files/-MN2J96z0I52cwn4932R)

github上源代码授权只需要确认授权，并输入github账户密码登录即可

#### gitlab上源代码授权

![upload-image](/files/-MN2J971OiIIqm83hEKd)

![upload-image](/files/-MN2J972z7H8glGSz0UL)

![upload-image](/files/-MN2J973aURueQAZtRA2)

在我的镜像，点击源代码授权，点击立即授权同步 Gitlab代码源。

* 服务地址：Gitlab HTTP或HTTPS地址
* 用户名：登录Gitlab 的用户名。
* 私有Token：连接Gitlab 的Personal Access Token。

至此源授代码完成。

**备注**：如何新建Gitlab Personal Access Token。

![upload-image](/files/-MN2J974_2JZJUDT2uRB)

![upload-image](/files/-MN2J975195s7VRD8gv2)

点击右上角个人资料，点击Setting，点击Access Tokens，输入Token Name和过期时间，勾选api和read\_user或其他权限，点击Create Personal Access Token。之后可以看到生成的Personal Access Token。

## 制作nginx和php基础镜像

### 容器云平台Docker镜像

1.Docker基础镜像：提供基础应用型的Docker软件服务（例如：nginx，php，jdk等），所以dockerhub镜像，公有镜像，自定义私有镜像都可以理解为基础镜像。

2.Docker业务镜像：将Gitlab上的源代码，或通过maven打出来的jar或tar包，添加至基础镜像中，通过构建打包成的Docker业务镜像。

### 通过commit的方式制作镜像

镜像制作步骤如下

1.安装docker软件（yum install docker或apt-get install docker）

2.下载docker centos镜像(docker pull)

3.创建并进入容器（docker run）

4.nginx docker基础镜像制作

5.将容器提交docker基础镜像（docker commit）

6.php docker基础镜像制作

### docker镜像制作

#### 安装docker

```
#  cat /etc/redhat-release
CentOS Linux release 7.7.1908 (Core)
# yum install docker -y                //安装docker软件
# systemctl start dockerd            //启动docker服务
# systemctl status docker        //查看docker状态，返回active (running)说明成功。
```

![upload-image](/files/-MN2J976spgGVeI8zKjE)

#### 下载docker镜像

```
[root@VM_0_13_centos ~]# docker pull centos:7.5.1804

7.5.1804: Pulling from library/centos

5ad559c5ae16: Pull complete

Digest: sha256:7a45e4a1efbaafc1d9aa89925b6fdb33288a96d35ea0581412316e2f0ad3720a

Status: Downloaded newer image for centos:7.5.1804

docker.io/library/centos:7.5.1804

[root@VM_0_13_centos ~]# docker images| grep centos

centos                          7.5.1804            cf49811e3cdb        14 months ago       200MB

REPOSITORY是仓库名，TAG 是标签，IMAGE ID是镜像ID，CREATED是镜像创建到至今的时间，SIZE是镜像大小
```

![upload-image](/files/-MN2J977jnf-n4H3qzyW)

#### 启动容器并进入容器

```
[root@VM_0_13_centos ~]# docker run -it centos:7.5.1804 /bin/bash
```

#### 制作nginx镜像

```
[root@34c805aa9433 /]# yum install epel-release -y
[root@34c805aa9433 /]# yum install nginx net-tools -y
[root@34c805aa9433 /]# vi  /etc/nginx/nginx.conf  //修改nginx配置文件，日志路径可跟进自身需求设置
```

1.将user nginx;修改成user root; (容器中nginx 要以root用户运行)

2.添加daemon off; （nginx和php等应用型软件安装在容器里面，必须要已守护进程的方式运行）

3.设置worker\_processes参数为auto

4.设置access\_log对应的路径为/data/logs/nginx/access.log

配置文件如下

![upload-image](/files/-MN2J978je6VX1XtUnch)

\[root\@34c805aa9433 nginx]# cat /etc/nginx/conf.d/localhost.conf //日志路径可跟进自身需求设置

添加localhost.conf配置文件

1.设置为nginx 80端口启动

2.server\_name为wordpress.tencent.com localhost; （wordpress.tencent.com是wordpress的访问域名，根据业务需求设置）

3.error\_log对应路径为/data/logs/nginx/wordpress.tencent.com\_error.log

4.wordpress网站根目录是/data/www/wordpress（这个目录暂时不创建，之后会讲解制作Docker业务镜像将源码添加至/data/www/wordpress目录）

5.设置fastcgi\_pass unix:/dev/shm/php-fpm.sock; （以nginx和php-fpm 使用uninx socket通信）

![upload-image](/files/-MN2J979v97x73Cm5MjO)

\[root\@34c805aa9433 nginx]# mkdir /data/logs/nginx -p //创建日志目录

#### 将容器提交为镜像

查看容器id,可以新开窗口或者执行ctrl+p+q（该命令退出容器但是不会关闭容器）

![upload-image](/files/-MN2J97AlgsbIDjCNTLw)

CONTAINER ID是容器ID，IMAGE是使用的镜像，COMMAND是容器启动运行的命令，CREATED是容器创建到运行至今时间，STATUS是容器当前的状态，PORTS是容器运行的端口，NAMES是容器的名称。

```
[root@VM_0_13_centos ~]# docker commit 34c805aa9433 my-nginx:v1  

sha256:e2710bd032cb580b224a1cb794ac6bde04685adeaccdc6b064ce4a5b7c8a09a4

[root@VM_0_13_centos ~]# docker images | grep my-nginx
```

![upload-image](/files/-MN2J97B60oRfE13tcFu)

### php镜像的制作

```
1.首先利用docker run启动一个centos容器 （参考步骤3.3.3）

[root@VM_0_13_centos ~]# docker run -it centos:7.5.1804 /bin/bash

2.在容器中进行php的安装和配置

[root@44a713255c35 /]# yum install epel-release -y

[root@44a713255c35 /]# rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm

[root@44a713255c35 /]# yum install php70w php70w-fpm php70w-cli php70w-common php70w-devel php70w-gd php70w-pdo php70w-mysql php70w-mbstring php70w-bcmath php70w-xml php70w-pecl-redis php70w-process php70w-intl php70w-xmlrpc php70w-soap php70w-ldap php70w-opcache -y     //安装php-fpm及扩展

# vi /etc/php-fpm.conf

error_log = /data/logs/php/error.log       //替换路径

daemonize = no     //设置php-fpm已守护进行方式运行
```

![upload-image](/files/-MN2J97CDjc_nkTJaMZS)

```
# vi /etc/php-fpm.d/www.conf                             //日志路径可跟进自身需求设置

user = root                          //将user = apache修改成user = root

group = root                       //将group = apache修改成group = root

listen = /dev/shm/php-fpm.sock                 //将listen = 127.0.0.1:9000 修改成 listen = /dev/shm/php-fpm.sock

listen.owner = root                                     //将listen.owner = user修改成listen.owner = root

listen.group = user                                     //将listen.group = user修改成listen.group = root

slowlog = /data/logs/php/www-slow.log                  //设置slow日志路径为/data/logs/php

php_admin_value[error_log] = /data/logs/php/www-error.log   //设置error日志路径为/data/logs/php
```

![upload-image](/files/-MN2J97DrMjJdfAQ9AIv)

```
# mkdir /data/logs/php -p           //创建php日志目录

3.最后通过commit命令提交成本地镜像，请参考上面第3.3.5步（将容器提交成docker基础镜像）

[root@VM_0_13_centos ~]# docker commit 44a713255c35 my-php:v1

sha256:42b5502112cc5870c76e932e8a986363c2256ef5b92fa0f3ab60ff9865c24385

[root@VM_0_13_centos ~]# docker images | grep my-php

my-php                          v1                  42b5502112cc        17 seconds ago      507MB
```

## 上传制作的镜像到腾讯云仓库

### 创建镜像仓库

![upload-image](/files/-MN2J97ETrvVFrMyOMRD)

### 查看指定上传镜像

![upload-image](/files/-MN2J97FtUz8A4RD-Dw-)

### 上传镜像

```
[root@VM_0_13_centos ~]# sudo docker login --username=100011007491 ccr.ccs.tencentyun.com

Password:

WARNING! Your password will be stored unencrypted in /root/.docker/config.json.

Configure a credential helper to remove this warning. See

https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

[root@VM_0_13_centos ~]# docker tag my-nginx:v1 ccr.ccs.tencentyun.com/nwx_registry/my-nginx:v1

[root@VM_0_13_centos ~]# docker push ccr.ccs.tencentyun.com/nwx_registry/my-nginx:v1

The push refers to repository [ccr.ccs.tencentyun.com/nwx_registry/my-nginx]

48d696542a84: Pushed

4826cdadf1ef: Pushed

v1: digest: sha256:f7261062bf7fab57b1501d7fd9282b9d1501ad14ccbe9feb9236d00fa6288cf7 size: 741

[root@VM_0_13_centos ~]# docker tag my-php:v1 ccr.ccs.tencentyun.com/nwx_registry/my-php:v1

[root@VM_0_13_centos ~]# docker push ccr.ccs.tencentyun.com/nwx_registry/my-php:v1

The push refers to repository [ccr.ccs.tencentyun.com/nwx_registry/my-php]

674c5524920e: Pushed

4826cdadf1ef: Mounted from nwx_registry/my-nginx

v1: digest: sha256:216a1bea5b1436852ba67dfff57be38811a4a32478f799df39753de4e66e2faa size: 742
```

![upload-image](/files/-MN2J97GvbpmSzpVGl7P)

![upload-image](/files/-MN2J97HdWWeuK0u2gIQ)

至此，我们已将nginx和php两个docker基础镜像push至腾讯云仓库。

Nginx基础镜像地址：ccr.ccs.tencentyun.com/nwx\_registry/my-nginx:v1

PHP基础镜像地址：ccr.ccs.tencentyun.com/nwx\_registry/my-php:v1

## 自动化构建生成springboot业务镜像

**腾讯容器云的构建功能（持续集成）**

1.将giltab源代码和Dockerfile文件拉取到本地

2.基于Dockefile文件，制作Docker业务镜像（Dockerfile会引用腾讯云镜像仓库中的Docker基础镜像，将源代码添加至基础镜像中，打包成Docker业务镜像）

3.构建又分自动和手动，自动构建：当用户往代码仓库发起push操作时，如果符合自动构建规则，那么就会在腾讯云容器平台上进行容器镜像的自动构建，并将构建出来的容器镜像自动推送到腾讯云容器镜像仓库中。手动构建：用户需要人为手动进行触发构建。

**构建（持续集成）步骤：**

1.Dockerfile文件上传至github

2.构建配置

3.构建日志内容说明

4.构建镜像验证

### Dockerfile上传到github上

![upload-image](/files/-MN2J97I9xDgFjn-q1Ka)

Dockerfile的内容

![upload-image](/files/-MN2J97J9Mm-5e2-qtfa)

第1行： FROM引用DockerHub上的maven基础镜像

第2行： 通过MAINTAINER 说明作者和作者邮箱地址；

第3行：将代码拷贝到镜像中用于后续打包

第4-5行：执行打包命令，并将jar拷贝到指定目录便于后续运行

第6行：EXPOSE声明服务端口。（容器内的服务端口，这里是springbboot的8080）

第7行：通过ENTRYPOINT设置业务镜像，开机运行springboot服务

说明：开头FROM和MAINTANER必须要指定；容器业务端口必须要用EXPOSE声明；开机自启动必须要用ENTYPOINT；由于Docker镜像是基于”层”，Dockerfile的内容越精简越好。

### 构建配置

![upload-image](/files/-MN2J97KNzvT3zqZ_tOS)

![upload-image](/files/-MN2J97LN39i-RSwegfj)

![upload-image](/files/-MN2J97MMqieKkVksx19)

在我的镜像中，点击进入构建配置，填写好构建参数，然后点击完成。

1.代码源选择github，Repository选择test-springboot（github上的项目）

2.触发方式勾选分为：添加新的Tag时触发和提交代码到分支时触发（意思是我们在gitlab上添加tag或进行提交代码操作，容器云平台会自动拉取代码，进行打包构建）

3.镜像版本命名规范自定义填写，分支/标签，更新时间，commit号根据需求勾选。（若都勾选，例如：构建成业务镜像名称是springboot-master-201907261035-1d096112584d036167c1cd50a335c0a58ff43f6a，springboot

是命名规则，master

是gitlab上的分支号，201907261035是生成业务镜像的当前时间，1d096112584d036167c1cd50a335c0a58ff43f6a是每次在github提交后生成的commit号）

4.覆盖镜像版本：生成的镜像同时会包含该tag。（可以理解为镜像别名，多打个tag）

5.Dockerfile路径：Dockerfile在文件源代码中的路径。（根据步骤1中Dockerfile和代码在同级目录，所以直接写Dockerfile即可）

6.构建目录：构建时的工作目录。（我这里填写的".",意思是执行当前目录下的Dockerfile文件）

点击完成，会跳转到镜像构建页面

![upload-image](/files/-MN2J97NS8KTt-l2fM85)

![upload-image](/files/-MN2J97OROuSUzi_ntIO)

我先在github上提交代码，然后在镜像构建，执行浏览器F5刷新，出现了一条构建记录，点击查看日志，可以看到右侧的构建日志内容

![upload-image](/files/-MN2J97PCyYvhUoTumtv)

### 构建日志内容说明

![upload-image](/files/-MN2J97QVHbm7vhWOXzB)

![upload-image](/files/-MN2J97RVCz_dUmvSidg)

![upload-image](/files/-MN2J97SabApnP-FRLme)

![upload-image](/files/-MN2J97TmXGpCn41uh9q)

![upload-image](/files/-MN2J97UhQ98nby-hluJ)

1.可以观察到，图中框选Step 1/6至Step 6/6 都是Dockerfile的文件内容，表示正在执行Dockerfile文件语句。

2.第1304行：表示docker build成功。

3.第1301行：构建成业务镜像的地址是，ccr.ccs.tencentyun.com/nwx\_registry/springboot:springboot-master-202006011634-d36b9ed0b51a80ed36477fb35d53012e1a05fd4c

4.第1304行：Build successfully，表示构建成功。

5.到镜像仓库下可以看到镜像已经上传成功

至此springboot业务镜像构建成功，同理，其他镜像构建也可以参照如上步骤

## 手动构建docker镜像

手动构建方式

1.指定源码分支构建

2.指定commit号构建

3.使用Dockerfile进行构建

### 指定源码分支构建

![upload-image](/files/-MN2J97V17onvXI9sDGq)

![upload-image](/files/-MN2J97W2WMh-1bozJKV)

在镜像构建中，点击立即构建，会弹出立即构建镜像界面，构建方式选择指定源码分支构建，镜像版本（可自定义填写，这里我填写v1），分支列表（当前需要构建代码源中的分支，这里我选的master），最后点击构建。在镜像构建中，会多出一条构建记录。

![upload-image](/files/-MN2J97Xp_U-akYJdYUd)

### 指定commit id构建

获取commit id

![upload-image](/files/-MN2J97YKzoc13xfXwy-)

![upload-image](/files/-MN2J97ZFyl83PQDrCSb)

在镜像构建中，点击立即构建，会弹出立即构建镜像界面，构建方式选择指定commit号构建，填写镜像版本(我这里设置的是v2)和Git commit号，最后点击构建。

![upload-image](/files/-MN2J97_We4nEX3f-RAS)

### 指定Dockerfile构建

![upload-image](/files/-MN2J97a-jIL-yR8HBDm)

![upload-image](/files/-MN2J97b8Su1d-qsiD6B)

在镜像构建中，点击立即构建，会弹出立即构建镜像界面，构建方式选择使用dockerfile进行构建，填写镜像版本和Dokcerfile文件内容，最后点击构建。 这里的构建来源是dockerfile构建，是获取不到github源码文件，所以没法进行java源码打包，Dockerfile构建是用于自定义dockerfile，可从第三方拉取镜像，自定义docker业务镜像，一般采用Dockerfile的方式构建都是构建一些基础镜像，不设计源代码或者其他本地文件的构建

修改下Dockerfile，让构建时候不涉及文件的拷贝

![upload-image](/files/-MN2J97cuR0xq6pl48C1)

![upload-image](/files/-MN2J97dRN7Lf9tbpycq)


# TKE操作笔记02

本次笔记主要讲述了如何在腾讯云控制台创建并使用你的第一个kubernetes集群，创建过程中每个步骤的区别以及如何选择，保证自己的集群资源达到最优。

## 使用TKE的优势

### TKE集群类型

TKE CVM容器集群支持以下两种类型：

* 托管集群（Master、Etcd 腾讯云容器服务管理）
* 独立部署集群（Master、Etcd 采用用户自有主机搭建）

如果你需要对master的组件和ETCD有一定的订制，可以独立部署在CVM上，如果你仅仅只需要部署服务，则可以把master和Etcd托管

### TKE容器集群业务优势

1. 业务交付周期短：只需制作好业务镜像，容器基于业务镜像在秒级内启动，且可动态快速设置实例数量，相对物理机和CVM等业务交付和机器数量变更，极大的节省了交付周期。
2. 业务半自动化：可通过TKE配置管理功能，快速变更pod下的容器批量配置，实现业务的自动化。（替代了ansible，saltstack部分功能）
3. 业务自愈：只需设置好容器实例数，部署在上层的业务容器，即使容器异常退出，业务中止访问，在秒级内又会基于业务镜像启动新的容器实例，保证业务的可持续访问。
4. 业务高可用：可通过设置容器实例数量大于或等于2，快速搭建高可用业务。
5. 业务快速横向扩展：应对国庆，春节等高峰期访问量，可通过设置容器实例数量，实现业务的快速横向扩展。
6. 业务透明管理：相对物理机和CVM，单个应用只会部署在单个容器中，相对在物理机和CVM上的混部方式，业务架构清晰，管理透明化。
7. 业务资源成本低：容器器需要分配的 CPU 量 ( 单位：mU (千分之一核) )，最小100，既0.1核；容器需要分配内存的量 ( 单位：MiB )，建议最小为4MiB。若部署单个PHP业务应用，容器建议分配0.1核128M。相对物理机分配8核16G 和 CVM分配4核4G，极大的节省了成本，提高了资源利用率。
8. 业务运维成本低：能够极大的降低运维成本，提升服务质量。（例如：相对在物理机和CVM上实现业务高可用，需要部署haproxy等软件，且搭建配置繁琐，在容器设置实例数即可，极大的降低运维成本。）

### TKE容器对比与自建容器的优势

| 优势   | 腾讯云容器服务（TKE）                                                                                                                                                                                                    | 自建容器服务                                              |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| 简单易用 | 简化集群管理腾讯云容器服务提供超大规模容器集群管理、资源调度、容器编排、代码构建，屏蔽了底层基础构架的差异，简化了分布式应用的管理和运维，您无需再操作集群管理软件或设计容错集群架构，因此也无需参与任何相关的管理或扩展工作。您只需启动容器集群，并指定想要运行的任务即可，腾讯云容器服务帮您完成所有的集群管理工作，让您可以集中精力开发 Docker 化的应用程序。                            | 自建容器管理基础设施通常涉及安装、操作、扩展自己的集群管理软件、配置管理系统和监控解决方案，管理复杂。 |
| 灵活扩展 | 灵活集群托管，集成负载均衡您可以使用腾讯云容器服务灵活安排长期运行的应用程序和批量作业。您还可以使用 API 获得最新的集群状态信息，以便集成您自己的自定义计划程序和第三方计划程序。腾讯云容器服务与负载均衡集成，支持在多个容器之间分配流量。您只需指定容器配置和要使用的负载均衡器，容器服务管理程序将自动添加和删除。另外腾讯云容器服务可以自动恢复运行状况不佳的容器，保证容器数量满足您的需求，以便为应用程序提供支持。 | 需要根据业务流量情况和健康情况人工确定容器服务的部署，可用性和可扩展性差。               |
| 安全可靠 | 资源高度隔离，服务高可用腾讯云容器服务在您自己的云服务器中启动，不与其他客户共享计算资源。您的集群在私有网络中运行，因此您可以使用您自己的安全组和网络 ACL，这些功能为您提供了高隔离水平，并帮助您使用云服务器构建高度安全可靠的应用程序。容器服务采用分布式服务架构，保证服务的故障自动恢复、快速迁移；结合有状态服务后端的分布式存储，实现服务和数据的安全、高可用。                           | 自建容器服务因其内核问题及 Namespace 不够完善，租户、设备、内核模块隔离性都比较差。     |
| 高效   | 镜像快速部署，业务持续集成腾讯云容器服务运行在您的私有网络中，高品质的 BGP 网络保证镜像极速上传下载，轻松支持海量容器秒级启动，极大程度降低了运行开销，使您的部署更加专注于业务运行。您可以在腾讯云容器服务上部署业务，开发人员在 GitHub 或其他代码平台提交代码后，容器服务可立即进行构建、测试、打包集成，将集成的代码部署到预发布环境和现网环境上。                               | 自建容器服务的网络无保证，因此无法保证使用镜像创建容器的效率。                     |
| 低成本  | 容器服务免费腾讯云容器服务没有任何附加费用，您可以在容器中免费调用 API 构建您的集群管理程序。您只需为您创建的用于存储和运行应用程序的云服务资源（例如云服务器、云硬盘等）付费。                                                                                                                      | 需要投入资金构建、安装、运维、扩展自己的集群管理基础设施，成本开销大。                 |

## 创建TKE CVM容器集群创建和使用

### 创建私有VPC网络

![upload-image](/files/-MN2J85qyDj4WQtKBz5D)

现在跳转到了私有网络创建界面，由于容器集群网络只支持私有网络，点击现在新建（容器集群网络只支持私有网络），输出私有网络名称（vpc-gz），和子网名称（subnet-vpc-gz），可用区我这里选择的是广州三区，点击创建。 至此，私有网络创建完成。

### 创建TKE集群

注意这里集群管理需要选择你新建的vpc的区域，因为创建集群需要用到vpc私有网络，如果该区域没有创建vpc网络，则无法创建集群

![upload-image](/files/-MN2J85rC3lg-pFnIexk)

在容器服务中，点击集群，点击新建，跳转到创建集群的界面，填写集群信息。 集群名称：要创建的集群的名称。不超过60个字符。（我填写的集群名称是First-k8s）

* 新增资源所属项目：根据实际需求进行选择，新增的资源将会自动分配到该项目下。（和腾讯云服务器所在项目对应，因为没有创建，选择默认即可，之后创建容器集群是运行在CVM上，CVM也在默认项目下）
* Kubernetes版本：提供多个 Kubernetes 版本选择。各版本特性对比请查看 。（1.12.4版本才支持containerd组件，我这里选择containerd，如果你需要在节点上调用docker api接口或者执行docker命令，则需要选择docker）
* 所在地域：建议您根据所在地理位置选择靠近的地域。可降低访问延迟，提高下载速度。（我选择广州）
* 集群网络：为集群内主机分配在节点网络地址范围内的 IP 地址。（我选择刚刚创建的子网vpc-gz）
* 容器网络：为集群内容器分配在容器网络地址范围内的 IP 地址。（默认即可，详情可查看使用指引）
* 操作系统：Master和Node CVM机器所使用的操作系统。（这里我选择CentOS 7.6 64bit TKE-Optimized）
* 集群描述：填写集群的相关信息，该信息将显示在集群信息页面。
* 高级设置：可设置 ipvs。ipvs 适用于将在集群中运行大规模服务的场景，开启后不能关闭。（我这里选择开始）

单击【下一步】。

![upload-image](/files/-MN2J85sco7Nd0p7n9yI)

主要参数信息如下：

* 创建集群：因为初次创建，默认项目下没有云主机，所以不用勾选。（如果你之前已经购买了CVM，可以选择购买的CVM）
* Master：Master 的部署方法决定了您集群的管理模式，我们提供了两种集群托管模式选择。（这里可以根据个人需求来选择类型，如果是选择托管模式，则可以节省master节点的部署费用，但是无法对master组件进行配置修改。相反，独立部署有master节点CVM等配置的支出，但是可以自己修改master组件配置。我们这里选择托管）
* Node：Node 配置的是集群运行服务真正使用的工作节点。您可以在创建集群时购置云服务器作为 Node 节点，也可以在集群创建完成后再添加 Node 节点。（我这里选择新增，在创建TKE集群时，node节点同时创建好）

  计费方式：选择按量计费（按照你的使用时长收费）
* Node机型：当 “Node” 选择为 “新增” 时，可选。
  * 可用区：选择广州三区，因为我的私有子网络创建在广州三区。
  * 节点网络：选择刚刚创建的私有网络即可
  * 机型：我这里选择的是标准型S1 4核4G
  * 系统盘：默认为“本地硬盘50G”，您可以根据机型选择本地硬盘、云硬盘、SSD 云硬盘、高性能云硬盘等。
  * 数据盘：（我这里选择保持默认，暂不购买）
  * 公网带宽：勾选分配免费公网IP，系统将免费分配公网 IP。（我这里保持默认）
  * 数量：Node节点设置>=1台。（我这里选择保持默认，1台）

主要参数信息如下：

* 数据盘挂载：不用勾选。（之前我没有选择购买数据盘）
* 安全组：安全组具有防火墙的功能，用于设置云服务器的网络访问控制。（这里我点击新建安全组，选择容器节点放通30000-32768端口）
* 登录方式：提供三种登录方式。
* 设置密码：请根据提示设置对应密码。（我们选择该方式）
* 立即关联密钥：密钥对是通过算法生成的一对参数，是一种比常规密码更安全的登录云服务器的方式。
* 自动生成密码：自动生成的密码将通过站内信发送给您。
* 安全加固：默认勾选
* 云监控：默认勾选
* 节点启动配置：可设置容器的开机启动脚本，我这里没有填写。
* Lable：可以给你的节点打上对应的标签，可以用于后续pod的调度使用
* 封锁：开启封锁后，容器不会调度创建到该node节点上，这里不勾选。

单击【下一步】，检查并确认配置信息。

单击【完成】，即可完成创建。

完成后可以在集群管理页面找到你创建的集群查看集群创建的进度

至此，集群已经创建完成

## TKE容器集群的创建

### 如何访问k8s集群

![upload-image](/files/-MN2J85zlj0oO_wEY-fP)

![upload-image](/files/-MN2J86-U_UO3_L_zhAN)

![upload-image](/files/-MN2J8603M9Kbe3R1hOU)

我们通过kubectl的方式访问创建的集群主要分为以下步骤

* 开启集群的访问权限：如果你是在同一个vpc下访问集群，则开启内网访问即可，如果你是需要在公网上访问，则需要开机外网访问的权限，注意这里在开启访问权限时候尽量指定ip或者ip网段访问，这样保证集群的安全。
* 获取kubeconfig配置：可以复制或者下载kubeconfig文件到本地，方便后续使用。
* 客户端机器上安装kubectl：如果是已经安装了集群机器，则不需要安装kubectl，新的集群则采用下面命令安装

  ```
  # curl -LOhttps://storage.googleapis.com/kubernetes-release/release/v1.10.0/bin/linux/amd64/kubectl
  # chmod +x ./kubectl
  # sudo mv ./kubectl /usr/local/bin/kubectl
  ```
* 配置集群的kubeconfig文件：配置kubeconfig文件分为2种情况，已经配置其他集群的访问凭证，还有就是没有配置过

已经配置过其他集群执行下面操作

```
# cd /root/.kube/
# touch new-config1    #将一个集群kubeconfig内容写入new-config1文件中
# touch new-config2    #将另一个集群kubeconfig内容写入new-config2文件中
# KUBECONFIG=new-config1:new-config2  kubectl config view --flatten > $HOME/.kube/config
# kubectl config get-contexts   #获取集群信息
# kubectl config use-context cls-hzywbn88-context-default   #切换集群
```

未配置过集群的执行如下操作

```
# mkdir -p /root/.kube
# touch config  #将kubeconfig内容写入config文件中
```

![upload-image](/files/-MN2J861e7pM82twq9If)

可以查看自带的pod查看集群信息

### 创建工作负载

![upload-image](/files/-MN2J862CKTkQSouAMK_)

集群中提供了以下五种负载

* deployment：Deployment 声明了 Pod 的模板和控制 Pod 的运行策略，适用于部署无状态的应用程序。您可以根据业务需求，对 Deployment 中运行的 Pod 的副本数、调度策略、更新策略等进行声明。
* statefulset：StatefulSet 主要用于管理有状态的应用，创建的 Pod 拥有根据规范创建的持久型标识符。Pod 迁移或销毁重启后，标识符仍会保留。在需要持久化存储时，您可以通过标识符对存储卷进行一一对应。如果应用程序不需要持久的标识符，建议您使用 Deployment 部署应用程序。
* daemonset：通过daemon进程的方式在每个节点上部署一个pod，一般用于日志和告警指标的收集场景。
* job：Job 控制器会创建 1 - N 个 Pod，这些 Pod 按照运行规则运行，直至运行结束。Job 可用于批量计算及数据分析等场景，通过重复执行次数、并行度、重启策略等设置满足业务述求。Job 执行完成后，不再创建新的 Pod，也不会删除已有 Pod，您可在“日志”中查看已完成的 Pod 的日志。如果您删除了 Job，Job 创建的 Pod 也会同时被删除，将查看不到该 Job 创建的 Pod 的日志。
* cronjob：一个 CronJob 对象类似于 crontab（cron table）文件中的一行。它根据指定的预定计划周期性地运行一个 Job，格式可以参考 Cron。

一般我们部署pod的之前都要先创建你所部署的命名空间和从镜像仓库拉取镜像的秘钥。

#### 创建namespace

![upload-image](/files/-MN2J863yWdmKy-0S81M)

#### 创建镜像拉取secret

![upload-image](/files/-MN2J866x9NVDQTxxj-f)

#### 配置deployment

![upload-image](/files/-MN2J8671wErINUKJQWZ)

![upload-image](/files/-MN2J868eAtio9lamEzQ)

根据实际需求，设置 Deployment 参数。关键参数信息如下：

* 工作负载名：自定义。（我填写是nginx-test名称）
* 命名空间：根据实际需求进行选择。这里选择之前创建的test。
* 类型：选择 “Deployment（可扩展的部署 Pod）”。
* 数据卷：不用添加。因为选择的是方案一，对网站源码存储方式无特殊要求。
* 实例内容器：根据实际需求，为 Deployment 的一个 Pod 设置一个或多个不同的容器,我们选择一个nginx
  * 名称：自定义。（我填写my-nginx）
  * 镜像：根据实际需求进行选择。（我这里选择nwx-nginx仓库）
  * 镜像版本：根据实际需求进行填写。（我这里没写，默认为最新）
  * 镜像拉取策略：本地没有则去远程拉取
  * CPU/内存限制：可根据 Kubernetes 资源限制 进行设置 CPU 和内存的限制范围，提高业务的健壮性。（默认的参数即可）
  * 高级设置：可设置 “工作目录”，“运行命令”，“运行参数”，“容器健康检查”，“特权级”等参数。（高级功能这里先不设置）
  * 添加容器：我们就运行一个容器
* 实例数量：根据实际需求选择调节方式，设置实例数量。
* imagepullsecrets：镜像的拉取秘钥，选择我们之前创建的test-secret
* 节点调度策略：节点的亲和性调度功能，默认即可。

### 创建service

#### 配置service

Service 定义访问后端 Pod 的访问策略，提供固定的虚拟访问 IP。您可以通过 Service 负载均衡地访问到后端的 Pod。 Service 支持以下类型：

* 公网访问： 使用 Service 的 Loadbalance 模式，自动创建公网 CLB。 公网 IP 可直接访问到后端的 Pod。
* VPC内网访问：使用 Service 的 Loadbalance 模式，自动创建内网 CLB。指定 annotations:service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxxxxx，VPC 内网即可通过内网 IP 直接访问到后端的 Pod。
* 集群内访问：使用 Service 的 ClusterIP 模式，自动分配 Service 网段中的 IP，用于集群内访问。
* 主机端口访问：通过node节点IP+端口访问业务。

![upload-image](/files/-MN2J8693iTofR6BIgDM)

根据实际需求，设置 Service 参数。关键参数信息如下：

* Service：勾选启用。
* 服务访问方式：根据实际需求，选择对应的访问方式。因为我要从本地windows机器访问wordpress服务，所以我这里选择主机端口访问，我们可以通过节点CVM的共有ip访问。
* 端口映射：根据实际需求进行设置。协议选择TCP，容器端口是指容器内服务运行的端口（我这里填写的80，也就是nginx服务启动端口）,主机端口为30001，服务端口为80
* ExternalTrafficPolicy：保持默认。
* Session Affinity：保持默认。

点击创建

![upload-image](/files/-MN2J86AqFWjUsWj4l3H)

此时会自动跳转到事件页面，可以看到的pod日志没有报错。

![upload-image](/files/-MN2J86Bc3GlnFkApil-)

pod状态都为running则表示pod启动正常

#### 访问好创建的服务

![upload-image](/files/-MN2J86C-N-gmhD2PBkj)

从节点管理获取到节点的ip

![upload-image](/files/-MN2J86E0v9z1piJ2JUH)

通过节点ip:30001的访问即可访问到对应的服务

![upload-image](/files/-MN2J86FeRNXFd6iGYUM)

可以查看service中的公网ip

![upload-image](/files/-MN2J86GmNxkMfvzgsqF)

通过公网ip和映射的端口进行访问（我们设置的映射端口为81）

#### 配置ingress

![upload-image](/files/-MN2J86HxbRUgl8c4lks)

创建ingress来设置域名来访问对应的集群服务

参数设置如下

* 命名空间：选你所需要转发的服务所在命名空间
* 监听端口：http监听为80，https监听为443，https需要配置ssl证书
* 转发配置：配置你的域名，如果后端多个路径对应不同域名则配置配置路径，没有就配置/，选择服务名称和端口

![upload-image](/files/-MN2J86IQmBhOA9sO9Ve)

域名解析，我们只需要在自己购买的域名里面将对应的子域名解析到vip的ip即可

![upload-image](/files/-MN2J86JFpRAOTW5Bd-y)

在浏览器输入域名即可访问到对应的服务

### 新加节点到集群中

![upload-image](/files/-MN2J86K3G5ohHY4dSRQ)

![upload-image](/files/-MN2J86L532wfDcPtpYi)

![upload-image](/files/-MN2J86MYqVdeg6E_1z8)

![upload-image](/files/-MN2J86NxyIiYS3T7fvQ)

![upload-image](/files/-MN2J86OaH7VfssFBgdp)

## TKE容器配置项操作

### 配置项介绍和作用

配置用来规定一些程序在启动时读入设定，提供了一种修改程序设置的手段， 针对不同的对象可以使用不同的配置。

配置项是多个配置的集合，配置项的值可以是字符串，也可以是文件

* 使用配置项功能可以帮您管理不同环境、不同业务的配置，支持多版本,支持Yaml格式
* 方便您部署相同应用的不同环境，配置文件支持多版本，方便您进行更新和回滚应用
* 方便您快速将您的配置以文件的形式导入到容器中

### 创建配置项

![upload-image](/files/-MN2J86Pt4e3iBsEssrv)

我们这里新建一个配置项，只修改nginx的错误日志名称作为示例

配置说明：

* 名称：configmap的名称
* 命名空间：挂在服务所在的命名空间
* 内容：你挂在的文件及文件内容

### 挂载configmap

![upload-image](/files/-MN2J86QwGooFYAKkEu3)

```
修改yaml文件如下
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    description: test nginx
  creationTimestamp: "2020-06-02T07:28:45Z"
  generation: 1
  labels:
    k8s-app: nignx-test
    qcloud-app: nignx-test
  name: nignx-test
  namespace: test
  resourceVersion: "8580475695"
  selfLink: /apis/apps/v1beta2/namespaces/test/deployments/nignx-test
  uid: b13877fe-a4a2-11ea-9c35-e28957d7d0b3
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: nignx-test
      qcloud-app: nignx-test
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: nignx-test
        qcloud-app: nignx-test
    spec:
      containers:
      - image: ccr.ccs.tencentyun.com/tmptest/nwx-nginx
        imagePullPolicy: IfNotPresent
        name: my-nginx
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        volumeMounts:
        - mountPath: /etc/nginx/nginx.conf
          name: config-volume
          subPath: nginx.conf
        securityContext:
          privileged: false
          procMount: Default
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: test-secret
      restartPolicy: Always
      schedulerName: default-schedule
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          defaultMode: 466
          name: nginx-conf
        name: config-volume
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2020-06-02T07:28:54Z"
    lastUpdateTime: "2020-06-02T07:28:54Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2020-06-02T07:28:45Z"
    lastUpdateTime: "2020-06-02T07:28:54Z"
    message: ReplicaSet "nignx-test-8ddf5b469" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 1
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1
```

![upload-image](/files/-MN2J86R116AeJT6j0eD)

提交修改后会自动滚动更新pod

![upload-image](/files/-MN2J86SEirsPhIJTOnM)

可以进入容器中看看日志名称是否修改，如果发现日志名称修改，则挂载成功

### 新建容器通过configmap挂载环境变量

![upload-image](/files/-MN2J86T8TL81sq-O50l)

![upload-image](/files/-MN2J86U1sV_aUauAwjW)


# TKE操作笔记03

该文章主要介绍了存储卷在TKE上的使用。

## PV，PVC，StoragClass配置使用

### StoragClass配置使用

StorageClass 描述存储的类型，集群管理员可以为集群定义不同的存储类别。腾讯云 TKE 服务默认提供块存储类型的 StorageClass，通过 StorageClass 配合 PersistentVolumeClaim 可以动态创建需要的存储资源。

![upload-image](/files/-MN2J7grza-NsTipygTw)

找到你的集群，选择存储中的storaeclass，点击新建

![upload-image](/files/-MN2J7gsqE8LpGrcOdU6)

根据实际需求，设置 StorageClass 参数。关键参数信息如下：

* 名称：自定义。（我填写的是nwx-sc）
* 计费模式：根据实际需求进行选择。（我选择的是按量计费）
* 可用区：根据实际需求进行设置，默认为 “随机可用区”。（我这里选择广州三区）
* 云盘类型：根据实际需求进行选择。（我选择的是高性能云硬盘）
* 回收策略：根据实际需求进行选择。（我选择的是删除）

单击【创建StorageClass】，完成创建。

![upload-image](/files/-MN2J7gte0jgHRAodFTA)

至此，storageclass创建完成！

### PVC的配置使用

#### 创建PVC

PersistentVolumeClaim（PVC）：集群内的存储请求。例如，PV 是 Pod 使用节点资源，PVC 则声明使用 PV 资源。当 PV 资源不足时，PVC 也可以动态创建 PV。

![upload-image](/files/-MN2J7gumdMUHerfIcD9)

点击存储，选择PVC,再点击新建

![upload-image](/files/-MN2J7gvLGu0iHs7VtY7)

填写pvc名称，我这里写的是nwx-test-pvc，选择命名空间为test，读写权限为单机读写，SC选择我们之前创建的nwx-sc，大小为10G。

![upload-image](/files/-MN2J7gwo8P3_H_aypLL)

PVC已经创建完成

#### 控制台配置挂载PVC

我们把新建的pvc挂载到之前的nginx服务中，我们可以通过修改yaml和在控制台修改配置进行挂载

![upload-image](/files/-MN2J7gxyY1Csx0tXP0s)

找到我们之前创建的deployment，然后点击pod配置修改

![upload-image](/files/-MN2J7gybewLJ0tr-r11)

数据卷选择我们之前创建的PVC，然后在挂载点钟选择挂载的PVC和挂载路径，我们这里挂载到/tmp

#### 修改yaml挂载PVC

在控制台修改yaml

![upload-image](/files/-MN2J7gzQwyz_MIO-vnd)

命令修改yaml

```
[root@VM_0_13_centos ~]# kubectl get deployment -n test
[root@VM_0_13_centos ~]# kubectl edit deployment new-nginx -n test
```

![upload-image](/files/-MN2J7h2LMw0esE6x6A2)

完整的yaml文件

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "2"
  creationTimestamp: "2020-06-02T09:36:11Z"
  generation: 2
  labels:
    k8s-app: new-nginx
    qcloud-app: new-nginx
  name: new-nginx
  namespace: test
  resourceVersion: "8614784222"
  selfLink: /apis/apps/v1beta2/namespaces/test/deployments/new-nginx
  uid: 7e77455e-a4b4-11ea-9c35-e28957d7d0b3
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: new-nginx
      qcloud-app: new-nginx
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: new-nginx
        qcloud-app: new-nginx
    spec:
      containers:
      - env:
        - name: RUNTIME
          valueFrom:
            configMapKeyRef:
              key: RUNTIME
              name: nginx-env
              optional: false
        - name: TZ
          valueFrom:
            configMapKeyRef:
              key: TZ
              name: nginx-env
              optional: false
        image: ccr.ccs.tencentyun.com/tmptest/nwx-nginx
        imagePullPolicy: IfNotPresent
        name: new-my-nginx
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: false
          procMount: Default
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /tmp
          name: data-volume
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: test-secret
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - name: data-volume
        persistentVolumeClaim:
          claimName: nwx-pvc1
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2020-06-03T02:41:32Z"
    lastUpdateTime: "2020-06-03T02:41:32Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2020-06-02T09:36:11Z"
    lastUpdateTime: "2020-06-03T02:51:59Z"
    message: ReplicaSet "new-nginx-86774775f6" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 2
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1
```

#### 验证PVC是否挂载成功

![upload-image](/files/-MN2J7h4Dmno7kwIDsyG)

### PV的配置使用

#### 动态创建PV

PersistentVolume（PV）：集群内的存储资源，例如节点是集群的资源。PV 独立于 Pod 的生命周期，根据不同的 StorageClass 类型创建不同类型的 PV。

![upload-image](/files/-MN2J7h5n_Y_0PolPGBl)

当我们在PVC中引用对应的SC，会动态的创建PV。

#### 静态创建CBS类型PV

![upload-image](/files/-MN2J7h61Vl_fplW8zeN)

![upload-image](/files/-MN2J7h7paWN62BhnlV7)

静态PV支持三种类型，CFS和COS需要去扩展组件中安装组件

![upload-image](/files/-MN2J7h88J9VFjxjqyXl)

安装COS和CFS组件

![upload-image](/files/-MN2J7h9yPQ_oZGoX-VS)

静态创建CBS类型pv，选择关联的SC即可

#### 静态创建CFS类型PV并关联到PVC

![upload-image](/files/-MN2J7hA-nABjXk72Rbb)

先要创建好CFS类型的SC

![upload-image](/files/-MN2J7hBC0qJNPvfIuMk)

![upload-image](/files/-MN2J7hCcgTCB1NTIvDp)

在集群的同一个私有网络下创建NFS

![upload-image](/files/-MN2J7hDDfKQxLdT54t6)

![upload-image](/files/-MN2J7hEF0q8MQtwYifT)

静态创建pv,并关联之前的sc和新建的NFS

![upload-image](/files/-MN2J7hF6R1sIfRNocdF)

创建PVC，关联创建的nfs类型pv,创建成功，即可引用

![upload-image](/files/-MN2J7hGLaYuniLZldkN)

创建成功，可以在yaml中或者控制台修改deployment配置进行挂载

#### 静态创建CBS类型PV并关联到PVC

首先在云服务器中创建你所需要的CBS卷

![upload-image](/files/-MN2J7hHppJr3nLw2YVA)

创建pv，选择你创建的云盘

![upload-image](/files/-MN2J7hIlG4GA5wJBRMB)

创建PVC，选择你的容量，注意，如果你之前创建了多个PV，PVC关联的规则选择容量大于或等于当前PVC设置的容量大小的静态创建的PersistentVolume

![upload-image](/files/-MN2J7hJxxKkAJ5pOQLB)

创建成功，可以在yaml中或者控制台修改deployment配置进行挂载


# TKE操作笔记04

本周讲述了在TKE上如何监控集群并发送告警，简单部署wordpress到集群中

## TKE监控使用和查看

良好的监控环境为腾讯云容器服务高可靠性、高可用性和高性能提供重要保证。您可以方便为不同资源收集不同维度的监控数据，能方便掌握资源的使用状况，轻松定位故障。 腾讯云容器服务提供集群、节点、工作负载、Pod、Container 5个层面的监控数据收集和展示功能。 收集监控数据有助于您建立容器集群性能的正常标准。通过在不同时间、不同负载条件下测量容集群的性能并收集历史监控数据，您可以较为清楚的了解容器集群和服务运行时的正常性能，并能快速根据当前监控数据判断服务运行时是否处于异常状态，及时找出解决问题的方法。例如，您可以监控服务的 CPU 利用率、内存使用率和磁盘 I/O

### 集群整体监控

![upload-image](/files/-MN2J7NdaTyMDd473BOD)

![upload-image](/files/-MN2J7NekjmDsUIWPsK8)

可以选择不同的时间段，时间间隔等选项来查看对应的指标数据

### 节点监控

![upload-image](/files/-MN2J7NjnmXQGHIVinLU)

![upload-image](/files/-MN2J7NkoiX4HY9brI8B)

我们可以查看某一个节点或者所有节点的监控指标，根据其他选项来选择数据的时间段和类型

### pod监控

![upload-image](/files/-MN2J7NlpE0OsMUEa675)

![upload-image](/files/-MN2J7NmQSNq_ynqmxo5)

我们要选择pod所在的节点，然后再进行其他选择来查看某个pod或者所有pod的监控指标数据

### deployment监控

![upload-image](/files/-MN2J7NnAvwS8_89XTiG)

![upload-image](/files/-MN2J7NoBpwzbWJcoolr)

可以选择所有负载或者某一个负载不同时间段的监控数据

### 查看某个deployment中具体pod的监控

![upload-image](/files/-MN2J7NpRz8lOfgRB5R2)

![upload-image](/files/-MN2J7Nq-o2uOx3yTBCP)

![upload-image](/files/-MN2J7NrqWZtYGws9uy2)

### 查看pod内某个容器的指标

![upload-image](/files/-MN2J7Nsg8Hl3JUC6riC)

单击【Container】，将【所属 Pod】选择为您想查看的 Pod，即可查看该 Pod 内 Container 的监控指标对比图

## helm的安装和使用

### helm的安装

#### helm服务端的安装

![upload-image](/files/-MN2J7Ntzh2Q5ysyn7Vu)

找到扩展插件，选择你的集群，选择helm，点击安装到你的集群中即可

#### helm客户端的安装

在你配置了集群的访问凭证下执行如下操作

```
curl -O https://storage.googleapis.com/kubernetes-helm/helm-v2.10.0-linux-amd64.tar.gz
tar xzvf helm-v2.10.0-linux-amd64.tar.gz
sudo cp linux-amd64/helm /usr/local/bin/helm
```

能够正确查询到版本则成功

```
[root@VM_0_13_centos ~]# helm version
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
```

配置 Helm 为 Client-only

执行以下命令，将 Helm 配置为 Client-only。

```
helm init --client-only
```

### helm的使用

#### helm仓库

```
[root@VM_0_13_centos ~]# helm repo list
NAME    URL
stable  https://kubernetes-charts.storage.googleapis.com
local   http://127.0.0.1:8879/charts
```

一般默认的远程仓库为google的，下载应用比较慢，我们可以设置为阿里的。 客户端命令设置如下

```
[root@VM_0_13_centos ~]# helm repo remove stable
[root@VM_0_13_centos ~]# helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
"stable" has been added to your repositories
[root@VM_0_13_centos ~]# helm repo list
NAME    URL
local   http://127.0.0.1:8879/charts
stable  https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
```

我们也可以在控制台安装应用的时候指定chart包地址进行下载

![upload-image](/files/-MN2J7NuylQCnIWGYy79)

![upload-image](/files/-MN2J7NvDJXV_Ddge2gu)

新建应用中填写指定的chart包地址进行安装即可

## 日志的采集

### 创建日志采集

![upload-image](/files/-MN2J7NwfIRbvXIyr9nJ)

首先要在集群中安装扩展插件才能进行日志采集

![upload-image](/files/-MN2J7Nxez_fC7FgqeOf)

![upload-image](/files/-MN2J7Nydgbn933-9qUR)

参数说明

* 名称：填写你日志采集器的名称
* 类型：收集说明类型的日志
* 日志源：可以指定某个容器，也可以选择全部容器（我们这里选择全部）
* 消费端：消费端也就是日志存储的地方，我们选择CLS（没有创建CLS，要先创建）

### 创建CLS日志采集服务

![upload-image](/files/-MN2J7NziFUgcwT-q1wg)

![upload-image](/files/-MN2J7O-7sLoxlyji8v5)

![upload-image](/files/-MN2J7O0aPc1IvDLAmuJ)

可以根据需要选择你日志保存的天数

![upload-image](/files/-MN2J7O11C_yC1kNR8cF)

参数说明：

日志主题名称：采集日志名称

主题分区数量：通过合并或分裂操作可以自由划分区间，从而控制服务的整体吞吐性能，最多为50个

### 收集日志

![upload-image](/files/-MN2J7O2yzw-l9Ax8ZIG)

引用创建的日志主题，点击完成

![upload-image](/files/-MN2J7O3wFl4LD8wtQhd)

日志采集创建完成

### 检索日志

![upload-image](/files/-MN2J7O4ID4p99Rg907d)

日志服务中找到【检索分析】，然后选择你的主题，设置你的日志时间，可以搜索你想要查看的日志内容

## 告警设置

![upload-image](/files/-MN2J7O54WtfFoRV-yJP)

![upload-image](/files/-MN2J7O6mPxUPZq_pRpa)

可以设置不同的告警指标条件，然后将告警通过不同的方式来给不同的用户组

## 事件持久化

Kubernetes Events 包括了 Kuberntes 集群的运行和各类资源的调度情况，对维护人员日常观察资源的变更以及定位问题均有帮助。TKE 支持为您的所有集群配置事件持久化功能，开启本功能后，会将您的集群事件实时导出到配置的存储端。TKE 还支持使用腾讯云提供的 PAAS 服务或开源软件对事件流水进行检索

![upload-image](/files/-MN2J7O77EjCH0R9-PXq)

![upload-image](/files/-MN2J7O8FeLSu_5eFEXg)

可以将持久化日志存储到ES或者CLS中

## 健康检查

![upload-image](/files/-MN2J7O95tKowWd7wJCr)

点击健康检查，选择你的集群，可以选择立即手动检查，也可以设置某个时间定时检查。

![upload-image](/files/-MN2J7OAmAn3rNRFKsk_)

点击健康检查，选择你的集群，可以选择立即手动检查，也可以设置某个时间定时检查。

![upload-image](/files/-MN2J7OBsLlu3EsF0mEm)

根据报告可以适当的调整集群，修复告警项

## TecentDB部署WordPress

### 创建mysql数据库

![upload-image](/files/-MN2J7OCv8UrGDKsh5BQ)

![upload-image](/files/-MN2J7OD4Bb5loJXt4i-)

![upload-image](/files/-MN2J7OET9wMV1M2_5Wb)

![upload-image](/files/-MN2J7OFi7sNbv8CuOOQ)

初始化完成后mysql数据库即创建完成

### 部署WordPress服务

![upload-image](/files/-MN2J7OGSdZfnhMF2fPR)

![upload-image](/files/-MN2J7OHAlaYfGcduMUW)

![upload-image](/files/-MN2J7OIGAV8CaFmdzjR)

主要参数信息如下，其余选项保持默认设置：

* 名称：输入自定义容器名称，本文以 my-wordpress为例。
* 镜像：输入 wordpress。
* 镜像版本（Tag）：输入 latest。
* 镜像拉取策略：提供以下3种策略，请按需选择，本文以不进行设置使用默认策略为例。若不设置镜像拉取策略，当镜像版本为空或 latest 时，使用 Always 策略，否则使用 IfNotPresent 策略。
  * Always：总是从远程拉取该镜像。
  * IfNotPresent：默认使用本地镜像，若本地无该镜像则远程拉取该镜像。
  * Never：只使用本地镜像，若本地没有该镜像将报异常。
* 环境变量：依次输入以下配置信息：
  * WORDPRESS\_DB\_HOST = 云数据库 MySQL 的内网 IP
  * WORDPRESS\_DB\_PASSWORD = 初始化时填写的密码
* Service：勾选“启用”。
* 服务访问方式：选择“提供公网访问”。
* 负载均衡器：根据实际需求进行选择。
* 端口映射：选择 TCP 协议，将容器端口和服务端口都设置为80 。

### 访问wordpress服务

![upload-image](/files/-MN2J7OJ_6ZYe_Lz-Kyv)


# TKE学习笔记

该文章介绍了在TKE学习使用过程中的一些记录，tke集群中的各类组件如何选择。

## TKE集群网络模式

### GlobalRouter 模式

![upload-image](https://github.com/nieweixing/gitbook/tree/5b9a6d508599b5d01e513a34e5511386cfaa9dd4/assets/images/bolg/tke-study/1.png)

GlobalRouter模式是在每个节点下起一个agent从整个VPC中指定一个子网进行通信和数据的传输。该模式其实就是在VPC下为每个节点分配一个子网进行网络通讯和传输

### VPC-CNI 模式

![upload-image](/files/-MN2J81EwWlmG1QQubSQ)

VPC-CNI模式是在某个VPC下提前规划好多个子网，pod服务通过每个节点上的弹性网卡从子网中随机分配ip来进行pod之间的通讯和数据传输。固定IP模式其实就是单独固定某个子网作为pod的ip和service分配使用。

### VPC-CNI和GlobalRouter对比

![upload-image](/files/-MN2J81F_3vwA58ITA-q)

* 绝大多数情况下应该选择 GlobalRouter，容器网段地址充裕，扩展性强，能适应规模较大的业务
* 如果后期部分业务需要用到 VPC-CNI 模式，可以在 GlobalRouter 集群再开启 VPC-CNI 支持，也就是 GlobalRouter 与 VPC-CNI 混用，仅对 部分业务使用 VPC-CNI 模式 &#x20;
* 如果完全了解并接受 VPC-CNI 的各种限制，并且需要集群内所有 Pod 都用 VPC-CNI 模式，可以创建集群时选择 VPC-CNI 网络插件

## TKE集群容器运行组件

### Docker

![upload-image](/files/-MN2J81GqgA8fp6nzVs1)

docker运行容器，主要是通过kubelet调用dockerd的进程，调用docker-containerd接口去启动对应的容器

### Containerd

![upload-image](/files/-MN2J81HSADurigvYHVR)

containerd则主要是kubelet通过CRI插件去调用containerd的api接口来启动容器

### docker和containerd对比

* containerd 方案由于绕过了 dockerd，调用链更短，组件更少，占用节点资源更少，绕过了 dockerd 本身的一些 bug，但 containerd 自身也还存在一些 bug (已修复一些，灰度中)&#x20;
* docker 方案历史比较悠久，相对更成熟，支持 docker api，功能丰富，符合大多数人的使用习惯

推荐使用docker方式，这样可以调用docker api以及命令，如果想对docker做优化也可以执行

## servie的转发

### iptables

![upload-image](/files/-MN2J81IIX1k8gEzoRKY)

iptables支持的小场景下应用，更加稳定

### ipvs

![upload-image](/files/-MN2J81JO6AWkV6tZhLi)

* IPVS为大型集群提供了更好的可扩展性和性能。（规则的存储方式使用的数据结构更高效）
* IPVS支持比iptables更复杂的负载平衡算法（最小负载，最少连接，位置，加权等）。
* IPVS支持服务器健康检查和连接重试等。

## 集群故障定位

### pod退出错误码分析

* 129-255 表示进程因外界中断信号退出，最常见的是 137，表示被 SIGKILL 杀死，可能是 Cgroup OOM，系统 OOM，存 活检查失败或者被其它进程杀死导致&#x20;
* 1-128 表示进程主动退出 (只是约定)，具体状态码含义取决于应用程序逻辑；有时主动退出也会是 255 状态码: 代码里使 用类似 exit(-1) 时，-1 被自动转成 255，通常状态码为 1 和 255 是一般性错误，看不错具体含义，需要结合日志分析

### 容器内抓包

nsenter命令仅进入该容器的网络命名空间，使用宿主机的命令调试容器网络

```
[root@VM_0_13_centos kubernetes-elasticsearch]# docker ps
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS                PORTS                                         NAMES
1421f7bbc523        goharbor/nginx-photon:v2.0.0         "nginx -g 'daemon of…"   6 days ago          Up 6 days (healthy)   0.0.0.0:80->8080/tcp, 0.0.0.0:443->8443/tcp   nginx
e9ab5bf15849        goharbor/harbor-jobservice:v2.0.0    "/harbor/entrypoint.…"   6 days ago          Up 6 days (healthy)                                                 harbor-jobservice
d04f14741f3d        goharbor/harbor-core:v2.0.0          "/harbor/entrypoint.…"   6 days ago          Up 6 days (healthy)                                                 harbor-core
c31d291b2425        goharbor/redis-photon:v2.0.0         "redis-server /etc/r…"   6 days ago          Up 6 days (healthy)   6379/tcp                                      redis
13f1de98a114        goharbor/registry-photon:v2.0.0      "/home/harbor/entryp…"   6 days ago          Up 6 days (healthy)   5000/tcp                                      registry
9e154ff22c54        goharbor/harbor-registryctl:v2.0.0   "/home/harbor/start.…"   6 days ago          Up 6 days (healthy)                                                 registryctl
051736f00111        goharbor/harbor-db:v2.0.0            "/docker-entrypoint.…"   6 days ago          Up 6 days (healthy)   5432/tcp                                      harbor-db
b10b982a225a        goharbor/harbor-portal:v2.0.0        "nginx -g 'daemon of…"   6 days ago          Up 6 days (healthy)   8080/tcp                                      harbor-portal
3a0d7ec954c5        goharbor/harbor-log:v2.0.0           "/bin/sh -c /usr/loc…"   6 days ago          Up 6 days (healthy)   127.0.0.1:1514->10514/tcp                     harbor-log
[root@VM_0_13_centos kubernetes-elasticsearch]# nsenter --target 6128
[root@VM_0_13_centos kubernetes-elasticsearch]# docker inspect -f {{.State.Pid}} 1421f7bbc523
6128
[root@VM_0_13_centos kubernetes-elasticsearch]# nsenter -n --target 6128
```

进入之后可以执行对应的ip address，ping，telnet，ss，tcpdump命令等

```
[root@VM_0_13_centos kubernetes-elasticsearch]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
31887: eth0@if31888: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:1f:00:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.31.0.10/16 brd 172.31.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@VM_0_13_centos kubernetes-elasticsearch]# tcpdump -i eth0 tcp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
```

### 在pod中通过busybox容器来修改系统参数失败

现在的版本中集群中如果节点使用TKE订制镜像，无法修改pod的内核参数，如果需要支持内核参数的修改，可以采用官方的centos和ubuntu镜像。


# TKE上搭建EFK日志采集系统

本篇文章介绍了如何在TKE集群中搭建EFK日志采集系统来采集集群日志。

## 创建新的namespace

创建一个新的namespace为logging，该命名空间来部署EFK

![upload-image](/files/-MN2J8CAeWZTEQhr7pDU)

## 创建ES的svc

```
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: logging
  labels:
    app: elasticsearch
spec:
  selector:
    app: elasticsearch
  clusterIP: None
  ports:
    - port: 9200
      name: rest
    - port: 9300
      name: inter-node
```

![upload-image](/files/-MN2J8CBXFB92iKlgYQj)

## 创建ES的StatefulSet资源

### 创建StorageClass

需要创建一个SC，用来后续ES创建时候动态挂载PVC，在腾讯云中，如果挂载了卷，又起了多个pod，可以采用自动动态挂载PVC

![upload-image](/files/-MN2J8CCcY0QITxjCnHH)

```
volumeClaimTemplates:
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: es-data-db
      resources:
        requests:
          storage: 50Gi
```

在资源中通过volumeClaimTemplates自动常见PV来挂载PVC

### 创建StatefulSet

```
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: logging
spec:
  serviceName: elasticsearch
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:6.4.3
        resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
        ports:
        - containerPort: 9200
          name: rest
          protocol: TCP
        - containerPort: 9300
          name: inter-node
          protocol: TCP
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        env:
          - name: cluster.name
            value: k8s-logs
          - name: node.name
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: discovery.zen.ping.unicast.hosts
            value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
          - name: discovery.zen.minimum_master_nodes
            value: "2"
          - name: ES_JAVA_OPTS
            value: "-Xms512m -Xmx512m"
      initContainers:
      - name: fix-permissions
        image: busybox
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
      - name: increase-vm-max-map
        image: busybox
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      - name: increase-fd-ulimit
        image: busybox
        command: ["sh", "-c", "ulimit -n 65536"]
        securityContext:
          privileged: true
  volumeClaimTemplates:
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: cbs
      resources:
        requests:
          storage: 10Gi
```

![upload-image](/files/-MN2J8CEIIfyCkrxnnyw)

## 创建kibana服务

```
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: logging
  labels:
    app: kibana
spec:
  ports:
  - port: 5601
  type: NodePort
  selector:
    app: kibana

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: logging
  labels:
    app: kibana
spec:
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: docker.elastic.co/kibana/kibana-oss:6.4.3
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_URL
            value: http://elasticsearch:9200
        ports:
        - containerPort: 5601
```

![upload-image](/files/-MN2J8CF_D1g-bcOQG77)

![upload-image](/files/-MN2J8CG88WwGczqqYLz)

通过[http://任意节点ip:31020访问kibana界面](http://xn--ip-dh3cr99d42rrmy/:31020访问kibana界面)

![upload-image](/files/-MN2J8CHite2VVVkAwzS)

## 部署Fluentd

### 添加fluentd的配置文件

```
kind: ConfigMap
apiVersion: v1
metadata:
  name: fluentd-config
  namespace: logging
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  system.conf: |-
    <system>
      root_dir /tmp/fluentd-buffers/
    </system>
  containers.input.conf: |-
    <source>
      @id fluentd-containers.log
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/es-containers.log.pos
      time_format %Y-%m-%dT%H:%M:%S.%NZ
      localtime
      tag raw.kubernetes.*
      format json
      read_from_head true
    </source>
    # Detect exceptions in the log output and forward them as one log entry.
    <match raw.kubernetes.**>
      @id raw.kubernetes
      @type detect_exceptions
      remove_tag_prefix raw
      message log
      stream stream
      multiline_flush_interval 5
      max_bytes 500000
      max_lines 1000
    </match>
  system.input.conf: |-
    # Logs from systemd-journal for interesting services.
    <source>
      @id journald-docker
      @type systemd
      filters [{ "_SYSTEMD_UNIT": "docker.service" }]
      <storage>
        @type local
        persistent true
      </storage>
      read_from_head true
      tag docker
    </source>
    <source>
      @id journald-kubelet
      @type systemd
      filters [{ "_SYSTEMD_UNIT": "kubelet.service" }]
      <storage>
        @type local
        persistent true
      </storage>
      read_from_head true
      tag kubelet
    </source>
  forward.input.conf: |-
    # Takes the messages sent over TCP
    <source>
      @type forward
    </source>
  output.conf: |-
    # Enriches records with Kubernetes metadata
    <filter kubernetes.**>
      @type kubernetes_metadata
    </filter>
    <match **>
      @id elasticsearch
      @type elasticsearch
      @log_level info
      include_tag_key true
      host elasticsearch
      port 9200
      logstash_format true
      request_timeout    30s
      <buffer>
        @type file
        path /var/log/fluentd-buffers/kubernetes.system.buffer
        flush_mode interval
        retry_type exponential_backoff
        flush_thread_count 2
        flush_interval 5s
        retry_forever
        retry_max_interval 30
        chunk_limit_size 2M
        queue_limit_length 8
        overflow_action block
      </buffer>
    </match>
```

![upload-image](/files/-MN2J8CI8SpwLRUNtW_z)

### 部署fluentd

fluentdt需要收集所有节点的日志，所以通过deamonset的方式部署

```
apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
  name: fluentd-es
  namespace: logging
  labels:
    k8s-app: fluentd-es
    version: v2.0.4
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  selector:
    matchLabels:
      k8s-app: fluentd-es
      version: v2.0.4
  template:
    metadata:
      labels:
        k8s-app: fluentd-es
        kubernetes.io/cluster-service: "true"
        version: v2.0.4
      # This annotation ensures that fluentd does not get evicted if the node
      # supports critical pod annotation based priority scheme.
      # Note that this does not guarantee admission on the nodes (#40573).
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      serviceAccountName: fluentd-es
      containers:
      - name: fluentd-es
        image: cnych/fluentd-elasticsearch:v2.0.4
        env:
        - name: FLUENTD_ARGS
          value: --no-supervisor -q
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /data/docker/containers
          readOnly: true
        - name: config-volume
          mountPath: /etc/fluent/config.d
      #nodeSelector:
      #  beta.kubernetes.io/fluentd-ds-ready: "true"
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /data/docker/containers
      - name: config-volume
        configMap:
          name: fluentd-config
```

![upload-image](/files/-MN2J8CKbwj5vWPHIRQK)


# TKE上搭建prometheus

该文章介绍了如何在k8s集群中搭建prometheus来监控集群并通过AlertManager发送告警。

## 新建namespace部署prometheus

![upload-image](/files/-MN2J9FjKMaawHyUt4IM)

## 部署prometheus

### 创建pvc挂载卷挂载prometheus数据

![upload-image](/files/-MN2J9Fm2aV_xly-OQ2j)

![upload-image](/files/-MN2J9FngWRWTuPx7zsf)

### 通过configmap配置prometheus配置文件

![upload-image](/files/-MN2J9FoV8Q1-jehVwI8)

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: kube-ops
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      scrape_timeout: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']
```

### 创建prometheus的pod资源

![upload-image](/files/-MN2J9FpL9qcc1dlaypj)

```
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: prometheus
  namespace: kube-ops
  labels:
    app: prometheus
spec:
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      containers:
      - image: prom/prometheus:v2.4.3
        name: prometheus
        command:
        - "/bin/prometheus"
        args:
        - "--config.file=/etc/prometheus/prometheus.yml"
        - "--storage.tsdb.path=/prometheus"
        - "--storage.tsdb.retention=24h"
        - "--web.enable-admin-api"  # 控制对admin HTTP API的访问，其中包括删除时间序列等功能
        - "--web.enable-lifecycle"  # 支持热更新，直接执行localhost:9090/-/reload立即生效
        ports:
        - containerPort: 9090
          protocol: TCP
          name: http
        volumeMounts:
        - mountPath: "/prometheus"
          subPath: prometheus
          name: data
        - mountPath: "/etc/prometheus"
          name: config-volume
        resources:
          requests:
            cpu: 100m
            memory: 512Mi
          limits:
            cpu: 100m
            memory: 512Mi
      securityContext:
        runAsUser: 0
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: prometheus
      - configMap:
          name: prometheus-config
        name: config-volume
```

### 创建prometheus的svc

![upload-image](/files/-MN2J9FrrbgTahAeOkYe)

```
apiVersion: v1
kind: Service
metadata:
  name: prometheus
  namespace: kube-ops
  labels:
    app: prometheus
spec:
  selector:
    app: prometheus
  type: NodePort
  ports:
    - name: web
      port: 9090
      targetPort: http
```

### 页面访问prometheus

![upload-image](/files/-MN2J9Fso9ftvjkcdJTD)

我们就可以通过[http://任意节点IP:31160访问](http://xn--ip-dh3cr99d42rrmy/:31160访问) prometheus 的 webui 服务了。

![upload-image](/files/-MN2J9FteVr7ZYivYped)

### 监控集群节点

可以通过node\_exporter来获取，顾名思义，node\_exporter 就是抓取用于采集服务器节点的各种运行指标，目前 node\_exporter 支持几乎所有常见的监控点，比如 conntrack，cpu，diskstats，filesystem，loadavg，meminfo，netstat等

![upload-image](https://github.com/nieweixing/gitbook/tree/5b9a6d508599b5d01e513a34e5511386cfaa9dd4/assets/images/blog/prometheus/9.png)

```
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: kube-ops
  labels:
    name: node-exporter
spec:
  template:
    metadata:
      labels:
        name: node-exporter
    spec:
      hostPID: true
      hostIPC: true
      hostNetwork: true
      containers:
      - name: node-exporter
        image: prom/node-exporter:v0.16.0
        ports:
        - containerPort: 9100
        resources:
          requests:
            cpu: 0.15
        securityContext:
          privileged: true
        args:
        - --path.procfs
        - /host/proc
        - --path.sysfs
        - /host/sys
        - --collector.filesystem.ignored-mount-points
        - '"^/(sys|proc|dev|host|etc)($|/)"'
        volumeMounts:
        - name: dev
          mountPath: /host/dev
        - name: proc
          mountPath: /host/proc
        - name: sys
          mountPath: /host/sys
        - name: rootfs
          mountPath: /rootfs
      tolerations:
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule"
      volumes:
        - name: proc
          hostPath:
            path: /proc
        - name: dev
          hostPath:
            path: /dev
        - name: sys
          hostPath:
            path: /sys
        - name: rootfs
          hostPath:
            path: /
```

我们上面是指定了hostNetwork=true，所以在每个节点上就会绑定一个端口 9100，我们可以通过这个端口去获取到监控指标数据，不需要创建service

让 Prometheus 也能够获取到当前集群中的所有节点信息的话，我们就需要利用 Node 的服务发现模式，同样的prometheus.yml 文件中配置如下的 job 任务即可

prometheus 去发现 Node 模式的服务的时候，访问的端口默认是10250，而现在该端口下面已经没有了/metrics指标数据了，现在 kubelet 只读的数据接口统一通过10255端口进行暴露了，所以我们应该去替换掉这里的端口，但是我们是要替换成10255端口吗？不是的，因为我们是要去配置上面通过node-exporter抓取到的节点指标数据，而我们上面是不是指定了hostNetwork=true，所以在每个节点上就会绑定一个端口9100，所以我们应该将这里的10250替换成9100，但是应该怎样替换呢？

这里我们就需要使用到 Prometheus 提供的relabel*configs中的replace能力了，relabel 可以在 Prometheus 采集数据之前，通过Target 实例的 Metadata 信息，动态重新写入 Label 的值。除此之外，我们还能根据 Target 实例的 Metadata 信息选择是否采集或者忽略该 Target 实例。比如我们这里就可以去匹配\_address*这个 Label 标签，然后替换掉其中的端口

最终添加如下job即可

```
   - job_name: 'kubernetes-nodes'
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:9100'
        target_label: __address__
        action: replace
```

由于 kubelet 也自带了一些监控指标数据，就上面我们提到的10255端口，所以我们这里也把 kubelet 的监控任务也一并配置上

```
  - job_name: 'kubernetes-kubelet'
      kubernetes_sd_configs:
      - role: node
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
```

![upload-image](/files/-MN2J9G-lYsW0GlhVBIx)

curl一下接口，使配置项生效，去prometheus 的 dashboard 中查看 Targets 是否能够正常抓取数据

![upload-image](/files/-MN2J9G1pMdU2Yb09gVP)

## 容器监控

容器监控我们自然会想到cAdvisor，我们前面也说过cAdvisor已经内置在了 kubelet 组件之中，所以我们不需要单独去安装，cAdvisor的数据路径为/api/v1/nodes//proxy/metrics，同样我们这里使用 node 的服务发现模式，因为每一个节点下面都有 kubelet，自然都有cAdvisor采集到的数据指标，在prometheus.yaml中配置一下即可

```
    - job_name: 'kubernetes-cadvisor'
      kubernetes_sd_configs:
      - role: node
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - target_label: __address__
        replacement: kubernetes.default.svc:443
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        target_label: __metrics_path__
        replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
```

![upload-image](/files/-MN2J9G6_Rh9_cA__Mzq)

reload使配置生效

![upload-image](/files/-MN2J9G7SpiPR1-3y3uE)

界面查看cadvisor的target是否生效

## Apiservice的监控

集群的 apiserver 在集群内部的 Service 地址，要自动发现 Service 类型的服务，我们就需要用到 role 为 Endpoints 的 kubernetes\_sd\_configs，我们可以在 ConfigMap 对象中添加上一个 Endpoints 类型的服务的监控任务 由于 kubernetes 这个服务对应的端口是443，需要使用 https 协议，所以这里我们需要使用 https 的协议，对应的就需要将对应的 ca 证书配置上，最终配置如下

```
    - job_name: 'kubernetes-apiservers'
      kubernetes_sd_configs:
      - role: endpoints
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
        action: keep
        regex: default;kubernetes;https
```

![upload-image](/files/-MN2J9G8dF0NrFozls1L)

reload使prometheus.yml配置生效

![upload-image](/files/-MN2J9G9K0Hw5vooE71_)

## Service的监控

配置一个任务用来专门发现普通类型的 Service

```
    - job_name: 'kubernetes-service-endpoints'
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
        action: replace
        target_label: __scheme__
        regex: (https?)
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_service_name]
        action: replace
        target_label: kubernetes_name
```

想自动发现集群中的 Service，就需要我们在 Service 的annotation区域添加prometheus.io/scrape=true的声明 我们修改下redis的service，生效后可以在target中查看到

```
metadata:
  annotations:
    prometheus.io/port: "9121"
    prometheus.io/scrape: "true"
```

![upload-image](/files/-MN2J9GAIqubzRjKVAgV)

## 部署kube-state-metrics

kube-state-metrice主要是负责监控pod、DaemonSet、Deployment、Job、CronJob 等各种资源对象的状态

部署kube-state-metrice的pod应用

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kube-state-metrics
  namespace: kube-ops
  labels:
    k8s-app: kube-state-metrics
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    version: v1.3.0
spec:
  selector:
    matchLabels:
      k8s-app: kube-state-metrics
      version: v1.3.0
  replicas: 1
  template:
    metadata:
      labels:
        k8s-app: kube-state-metrics
        version: v1.3.0
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: kube-state-metrics
      containers:
      - name: kube-state-metrics
        image: zhaocheng172/kube-state-metrics:v1.3.0
        ports:
        - name: http-metrics
          containerPort: 8080
        - name: telemetry
          containerPort: 8081
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          timeoutSeconds: 5
      - name: addon-resizer
        image: zhaocheng172/addon-resizer:1.8.3
        resources:
          limits:
            cpu: 100m
            memory: 30Mi
          requests:
            cpu: 100m
            memory: 30Mi
        env:
          - name: MY_POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: MY_POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        volumeMounts:
          - name: config-volume
            mountPath: /etc/config
        command:
          - /pod_nanny
          - --config-dir=/etc/config
          - --container=kube-state-metrics
          - --cpu=100m
          - --extra-cpu=1m
          - --memory=100Mi
          - --extra-memory=2Mi
          - --threshold=5
          - --deployment=kube-state-metrics
      volumes:
        - name: config-volume
          configMap:
            name: kube-state-metrics-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-state-metrics-config
  namespace: kube-ops
  labels:
    k8s-app: kube-state-metrics
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
data:
  NannyConfiguration: |-
    apiVersion: nannyconfig/v1alpha1
    kind: NannyConfiguration
```

部署kube-state-metrice的service

```
apiVersion: v1
kind: Service
metadata:
  name: kube-state-metrics
  namespace: kube-ops
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "kube-state-metrics"
  annotations:
    prometheus.io/scrape: 'true'
spec:
  ports:
  - name: http-metrics
    port: 8080
    targetPort: http-metrics
    protocol: TCP
  - name: telemetry
    port: 8081
    targetPort: telemetry
    protocol: TCP
  selector:
    k8s-app: kube-state-metrics
```

kube-state-metrics-service.yaml 对 Service 的定义包含prometheus.io/scrape: 'true'这样的一个annotation，因此 kube-state-metrics 的 endpoint 可以被 Prometheus 自动服务发现

![upload-image](/files/-MN2J9GDUO1c3jddu-1Z)

## 部署granafa

### 新建pvc做数据持久化

![upload-image](/files/-MN2J9GEXxnbejjSupas)

### 创建grafana的svc

```
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: kube-ops
  labels:
    app: grafana
spec:
  type: NodePort
  ports:
    - port: 3000
  selector:
    app: grafana
```

### 创建gafana的deployment

```
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: grafana
  namespace: kube-ops
  labels:
    app: grafana
spec:
  revisionHistoryLimit: 10
  template:
    metadata:
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:5.3.4
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
          name: grafana
        env:
        - name: GF_SECURITY_ADMIN_USER
          value: admin
        - name: GF_SECURITY_ADMIN_PASSWORD
          value: admin321
        readinessProbe:
          failureThreshold: 10
          httpGet:
            path: /api/health
            port: 3000
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 30
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /api/health
            port: 3000
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: 100m
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 256Mi
        volumeMounts:
        - mountPath: /var/lib/grafana
          subPath: grafana
          name: storage
      securityContext:
        fsGroup: 472
        runAsUser: 472
      volumes:
      - name: storage
        persistentVolumeClaim:
          claimName: grafana
```

### 访问grafana界面

![upload-image](/files/-MN2J9GG9t45vIU0GnAF)

现在我们就可以在浏览器中使用http\://<任意节点IP:31838>来访问 grafana 这个服务了：

![upload-image](/files/-MN2J9GHF6q5RCEubFZB)

登录账号密码从deployment的配置文件查看

### 配置数据源

![upload-image](/files/-MN2J9GIT4NUMqHOAUuG)

![upload-image](/files/-MN2J9GJGoq0xkryAgM2)

### 添加Dashboard

![upload-image](/files/-MN2J9GKriM-lfmbpnF3)

我们这里可以使用Kubernetes cluster monitoring (via Prometheus)(dashboard id 为162)

![upload-image](/files/-MN2J9GLmsaQ3xHAL63t)

![upload-image](/files/-MN2J9GMN1-nL6ubjeue)

## AlertManager配置告警

### 配置告警配置文件

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: alertmanager
  namespace: monitor
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: EnsureExists
data:
  config.yml: |
    global:
      resolve_timeout: 5m
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: 'nwx*****@163.com'
      smtp_auth_username: 'nwx****@163.com'
      smtp_auth_password: '*******'
      smtp_require_tls: false

    templates:
    - '/etc/alertmanager-templates/*.tmpl'


    route:
      group_by: ['alertname']
      group_interval: 1m
      group_wait: 10s
      receiver: default-receiver
      repeat_interval: 1m

    receivers:
    - name: default-receiver
      email_configs:
      - to: "niewx****@163.com"
        html: '{ template "emai.html" . }' # 模板
        headers: { Subject: " { .CommonLabels.instance } { CommonAnnotations.summary }" } #标题
```

### 配置告警发送模板文件

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: alertmanager-templates
  namespace: monitor
data:
  default.tmpl: |
    { define "emai.html" }
    { range .Alerts }
     <pre>
    实例: { .Labels.instance }
    信息: { .Annotations.summary }
    详情: { .Annotations.description }
    时间: { .StartsAt.Format "2006-01-02 15:04:05" }
    </pre>
    { end }
    { end }


    { define "__alertmanager" }AlertManager{ end }
    { define "__subject" }[{ .Status | toUpper }{ if eq .Status "firing" }:{ .Alerts.Firing | len }{ end }] { .GroupLabels.SortedPairs.Values | join " " } { if gt (len .CommonLabels) (len .GroupLabels) }({ with .CommonLabels.Remove .GroupLabels.Names }{ .Values | join " " }{ end }){ end }{ end }
    { define "__single_message_title" }{ range .Alerts.Firing }{ .Labels.alertname } @ { .Annotations.identifier }{ end }{ range .Alerts.Resolved }{ .Labels.alertname } @ { .Annotations.identifier }{ end }{ end }
    { define "custom_title" }[{ .Status | toUpper }{ if eq .Status "firing" }:{ .Alerts.Firing | len }{ end }] { if or (and (eq (len .Alerts.Firing) 1) (eq (len .Alerts.Resolved) 0)) (and (eq (len .Alerts.Firing) 0) (eq (len .Alerts.Resolved) 1)) }{ template "__single_message_title" . }{ end }{ end }
    { define "custom_slack_message" }
    { if or (and (eq (len .Alerts.Firing) 1) (eq (len .Alerts.Resolved) 0)) (and (eq (len .Alerts.Firing) 0) (eq (len .Alerts.Resolved) 1)) }
    { range .Alerts.Firing }
    *报警类型: * { .Labels.service }
    *报警描述: * { .Annotations.description }
    { end }
    { range .Alerts.Resolved }
    *报警恢复: * { .Annotations.resolved }{ end }
    { else }
    { if gt (len .Alerts.Firing) 0 }
    { range .Alerts.Firing }
    *报警类型: * { .Labels.service }
    *报警描述: * { .Annotations.description }
    { end }{ end }
    { if gt (len .Alerts.Resolved) 0 }
    { range .Alerts.Resolved }
    *报警类型: * { .Labels.service }
    *报警恢复: * { .Annotations.resolved }
    { end }{ end }
    { end }
    { end }


    { define "slack.default.title" }{ template "__subject" . }{ end }
    { define "slack.default.username" }{ template "__alertmanager" . }{ end }
    { define "slack.default.fallback" }{ template "slack.default.title" . } | { template "slack.default.titlelink" . }{ end }
    { define "slack.default.pretext" }{ end }
    { define "slack.default.titlelink" }{ template "__alertmanagerURL" . }{ end }
    { define "slack.default.iconemoji" }{ end }
    { define "slack.default.iconurl" }{ end }
    { define "slack.default.text" }{ end }
    { define "slack.default.footer" }{ end }

    { define "wechat.html" }
    { range .Alerts }
    ========start==========
    告警程序: prometheus_alert
    告警级别: { .Labels.severity }
    告警类型: { .Labels.alertname }
    故障主机: { .Labels.instance }
    告警主题: { .Annotations.summary }
    告警详情: { .Annotations.description }
    触发时间: { .StartsAt.Format "2019-01-01 01:01:01" }
    ========end==========
    { end }
    { end }
```

### 配置alertmanager容器

我们可以直接在之前的 Prometheus 的 Pod 中添加这个容器，对应的 YAML 资源声明如下：

```
      containers:
      - args:
        - --config.file=/etc/alertmanager/config.yml
        - --storage.path=/alertmanager/data
        image: prom/alertmanager:v0.15.3
        imagePullPolicy: IfNotPresent
        name: alertmanager
        ports:
        - containerPort: 9093
          name: http
          protocol: TCP
        resources:
          limits:
            cpu: 100m
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 256Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /etc/alertmanager
          name: alertcfg
        - mountPath: /etc/alertmanager-templates
          name: templates-volume
```

### prometheus中配置alermanager

Prometheus 中配置下 AlertManager 的地址，让 Prometheus 能够访问到 AlertManager

```
alerting:
  alertmanagers:
    - static_configs:
      - targets: ["localhost:9093"]
```

### 设置告警规则

直接在prometheus的配置文件中配置告警规则

```
rules.yml: |2-

        groups:
        - name: test-rule
          rules:
          - alert: NodeFilesystemUsage
            expr: (node_filesystem_size_bytes{device="rootfs"} - node_filesystem_free_bytes{device="rootfs"}) / node_filesystem_size_bytes{device="rootfs"} * 100 > 20
            for: 2m
            labels:
              team: node
            annotations:
              summary: "{$labels.instance}: High Filesystem usage detected"
              description: "{$labels.instance}: Filesystem usage is above 80% (current value is: { $value }"
          - alert: NodeMemoryUsage
            expr: (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes+node_memory_Buffers_bytes+node_memory_Cached_bytes )) / node_memory_MemTotal_bytes * 100 > 20
            for: 2m
            labels:
              team: node
            annotations:
              summary: "{$labels.instance}: High Memory usage detected"
              description: "{$labels.instance}: Memory usage is above 80% (current value is: { $value }"
          - alert: NodeCPUUsage
            expr: (100 - (avg by (instance) (irate(node_cpu{job="kubernetes-node-exporter",mode="idle"}[5m])) * 100)) > 20
            for: 2m
            labels:
              team: node
            annotations:
              summary: "{$labels.instance}: High CPU usage detected"
              description: "{$labels.instance}: CPU usage is above 80% (current value is: { $value }"
```

### 查看告警

![upload-image](/files/-MN2J9GNtCWrvWTSL_qp)

![upload-image](/files/-MN2J9GOlbVAwZhJhcw7)

在这个页面中我们可以进行一些操作，比如过滤、分组等等，里面还有两个新的概念：Inhibition(抑制)和 Silences(静默)。

* Inhibition：如果某些其他警报已经触发了，则对于某些警报，Inhibition 是一个抑制通知的概念。例如：一个警报已经触发，它正在通知整个集群是不可达的时，Alertmanager 则可以配置成关心这个集群的其他警报无效。这可以防止与实际问题无关的数百或数千个触发警报的通知，Inhibition 需要通过上面的配置文件进行配置。
* Silences：静默是一个非常简单的方法，可以在给定时间内简单地忽略所有警报。Silences 基于 matchers配置，类似路由树。来到的警告将会被检查，判断它们是否和活跃的 Silences 相等或者正则表达式匹配。如果匹配成功，则不会将这些警报发送给接收者。

由于全局配置中我们配置的repeat\_interval: 5m，所以正常来说，上面的测试报警如果一直满足报警条件(CPU使用率大于20%)的话，那么每5分钟我们就可以收到一条报警邮件。

![upload-image](/files/-MN2J9GP8osK3uei6tLr)

## 配置ingress通过域名访问

![upload-image](/files/-MN2J9GQtuQUH9KM6p2A)

将域名解析到对应的vip上

![upload-image](/files/-MN2J9GRqPsuouxaW2GM)

配置好ingress的转发规则

![upload-image](/files/-MN2J9GSKLCAWfLh6vmK)

通过域名来访问


# TKE升级node节点版本

该文章介绍了在TKE中如何给集群节点升级版本。

## TKE节点升级方式

### 驱逐节点pod升级节点方式

![upload-image](/files/-MN2J9Bmenin8nE44LOO)

找到对应的节点，点击驱逐，驱逐完毕后，点击集群信息升级

![upload-image](/files/-MN2J9Bow3T7tNZcUbzl)

![upload-image](/files/-MN2J9BpyPEdZ3d3rLWl)

![upload-image](/files/-MN2J9Bq5gXP5cKxSOth)

![upload-image](/files/-MN2J9Bry74eImqQZAq2)

信息配置完成后点击完成，等待10分钟即可升级完成。

### 将节点对应的pod副本设置大于1

![upload-image](/files/-MN2J9BscioB0sJCdBo8)

![upload-image](/files/-MN2J9BtE_aNeuWQ4Z_B)

先将对应节点上的pod副本都设置大于2，因为升级时候会销毁pod，可以会导致服务不可用，设置pod副本为多个，可以保证服务不会中断。设置完之后，再执行上述升级节点步骤即可。


# TKE上部署treafik2

腾讯云上有默认的提供的ingress服务，如果你不想用提供的，想用最新的treafik来暴露服务通过域名访问也是可以的,下面我们来部署操作下。

## 创建LB负载到集群中

![upload-image](/files/-MN2J7urxwllPK6Ai6Eb)

![upload-image](/files/-MN2J7us0gYmSpTfoLSY)

![upload-image](/files/-MN2J7ut8X6P10HZy2dd)

网络类型选择公网，域名需要解析到公网ip

网络选择私有网络，集群所在的vpc

![upload-image](/files/-MN2J7uurE4C8u5TZUmE)

在你购买的域名中解析到该VIP上，我这边是解析了\*.tx.niewx.ciub

![upload-image](/files/-MN2J7uv8oXVszmjZzDx)

添加监听器

![upload-image](/files/-MN2J7uwHZiFRDeOyCTp)

![upload-image](/files/-MN2J7uxZ1ka2XyHnYH9)

![upload-image](/files/-MN2J7uy6u4WIXd2lcf0)

![upload-image](/files/-MN2J7uz231419N86UQ0)

![upload-image](/files/-MN2J7v-Qiae1JNtTGZo)

绑定后端服务器，我们这里绑定的端口为30183,这个端口是k8s集群暴露treafik2的服务端口

## 创建treafik命名空间来部署服务

![upload-image](/files/-MN2J7v0BFgzCAWVflnF)

![upload-image](/files/-MN2J7v17tfBYZiIT-zh)

## 部署treafik服务

以下部署，如果在TKE的控制台无法部署yaml，可以通过kubectl来部署对应的yaml文件

### 首先自定义资源类型

```
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced
```

### 配置rbac权限

```
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: treafik
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - traefik.containo.us
    resources:
      - ingressroutes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - traefik.containo.us
    resources:
      - ingressroutetcps
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - traefik.containo.us
    resources:
      - tlsoptions
    verbs:
      - get
      - list
      - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
  name: traefik-ingress-controller
  namespace: treafik
```

### 部署treafik

```
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: traefik
  namespace: treafik
  labels:
    k8s-app: traefik-ingress-lb
spec:
  selector:
    matchLabels:
      k8s-app: traefik-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
      - image: traefik:v2.0
        name: traefik-ingress-lb
        ports:
        - name: web
          containerPort: 80
          hostPort: 80
        - name: websecure
          containerPort: 443
          hostPort: 443
        - name: admin
          containerPort: 8080
        args:
        - --entrypoints.web.Address=:80
        - --entrypoints.websecure.Address=:443
        - --api.insecure=true
        - --providers.kubernetescrd
        - --api
        - --api.dashboard=true
        - --accesslog

---

kind: Service
apiVersion: v1
metadata:
  name: traefik
  namespace: treafik
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - protocol: TCP
      port: 8080
      name: admin
  type: NodePort
```

### 配置域名规则

```
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-webui
  namespace: treafik
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`traefik2.tx.niewx.club`)
    kind: Rule
    services:
    - name: traefik
      port: 8080

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: prometheus-webui
  namespace: kube-ops
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`prometheus.tx.niewx.club`)
    kind: Rule
    services:
    - name: prometheus
      port: 9090

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: grafana-webui
  namespace: kube-ops
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`grafana.tx.niewx.club`)
    kind: Rule
    services:
    - name: grafana
      port: 3000
```

这里我们暴露我们之前已经部署好的服务。

### 添加treafik服务端口

因为之前的svc只暴露了8080,我们需要暴露treafik的80和443端口，这样lb才能负载到对应的服务上

![upload-image](/files/-MN2J7v28cCF5Y21Ye1V)

我们直接在treafik的svc上添加2条映射即可，也可以在最开始的yaml中直接设置好。

## 通过域名访问集群服务

![upload-image](/files/-MN2J7v3Y3Zt6G_uLPzP)

![upload-image](/files/-MN2J7v414IF4jA6eKJg)

## 设置treafik的basic auth认证

有的时候我们的服务自身没有设置鉴权，任何人可以直接登录界面，这样是不安全的，treafik2提供的中间来解决这个问题，我们一般访问treafik界面是不需要密码的，下面我们给treafik来设置一个访问账号密码

### 采用htpasswd创建文件

```
htpasswd -bc auth admin admin
```

### 创建secret

```
kubectl create secret generic nginx-basic-auth --from-file=auth -n treafik
```

### 定义Basic Auth中间件

```
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: secured
  namespace: treafik
spec:
  chain:
    middlewares:
    - name: auth-users

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: auth-users
  namespace: treafik
spec:
  basicAuth:
    secret: nginx-basic-auth # 兼容 K8S secrets 对象
```

### Ingress中应用中间件

```
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-webui
  namespace: treafik
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`traefik2.tx.niewx.club`)
    kind: Rule
    services:
    - name: traefik
      port: 8080
    middlewares:
      - name: secured
```

### 通过域名访问需要登录才行

![upload-image](/files/-MN2J7v52alxTGY1bp2j)

![upload-image](/files/-MN2J7v6Y1YtO3bHZ2Ei)


# k8s之dns解析

本篇文章主要介绍了k8s中dns如何解析和怎么使用dns服务。

## coredns的原理和介绍

TKE集群中使用的DNS解析是采用coreDNS，Kubernetes1.11和更高版本中，CoreDNS位于GA并且默认情况下与kubeadm一起安装。

![upload-image](/files/-MN2J87xgFuIY0Skxsph)

我们的每个容器下dns解析配置文件，都是通过kubelet来给容器进行配置。

![upload-image](/files/-MN2J87yEHus6txD5cRE)

kubelet 使用 --cluster-dns =  标志将 DNS 传递到每个容器。

DNS 名称也需要域。 您可在kubelet中使用 --cluster-domain =  标志配置本地域

## coredns的配置文件

```
apiVersion: v1
data:
  Corefile: |2-
        .:53 {
            errors
            health
            kubernetes cluster.local. in-addr.arpa ip6.arpa {
                pods insecure
                upstream
                fallthrough in-addr.arpa ip6.arpa
            }
            prometheus :9153
            proxy . /etc/resolv.conf
            cache 30
            reload
            loadbalance
        }
kind: ConfigMap
metadata:
  creationTimestamp: "2020-06-02T03:16:59Z"
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: coredns
  namespace: kube-system
  resourceVersion: "8573074315"
  selfLink: /api/v1/namespaces/kube-system/configmaps/coredns
  uid: 859ce588-a47f-11ea-8fe7-0a5ffefb2e9f
```

* error：错误记录到 stdout。
* health：CoreDNS 的健康报告给 <http://localhost:8080/health。>
* kubernetes：CoreDNS 将基于 Kubernetes 的服务和 Pod 的 IP 答复 DNS 查询。 您可以在 此处.

  **提供 pods insecure 选项是为了与 kube-dns 向前兼容。 您可以使用 pods verified 选项，该选项仅在相同名称空间中存在具有匹配 IP 的 pod 时才返回 A 记录。 如果您不使用 Pod 记录，则可以使用 pods disabled 选项**

'Upstream' 用来解析指向外部主机的服务（外部服务）。

* prometheus：CoreDNS的度量标准以Prometheus格式在 <http://localhost:9153/metrics> 上提供。
* proxy: 不在 Kubernetes 集群域内的任何查询都将转发到预定义的解析器 (/etc/resolv.conf).
* cache：这将启用前端缓存。
* loop：检测到简单的转发循环，如果发现死循环，则中止 CoreDNS 进程。
* reload：允许自动重新加载已更改的 Corefile。 编辑 ConfigMap 配置后，请等待两分钟，以使更改生效。

  loadbalance：这是一个轮询 DNS 负载均衡器，它在应答中随机分配 A，AAAA 和 MX 记录的顺序。

## 使用 CoreDN 配置存根域和上游域名服务器

CoreDNS 能够使用 proxy plugin. 配置存根域和上游域名服务器。

**注意** ：k8s在1.18版本后，配置上游dns服务器采用的字段不在是proxy，而是采用的forword字段。

**示例**

如果集群操作员的 Consul 域服务器位于 10.150.0.1，并且所有 Consul 名称都带有后缀.consul.local。 要在 CoreDNS 中对其进行配置，集群管理员可以在 CoreDNS 的 ConfigMap 中创建加入以下字段。

```
consul.local:53 {
        errors
        cache 30
        proxy . 10.150.0.1
    }
```

要显式强制所有非集群 DNS 查找通过特定的域名服务器（位于172.16.0.1），请将 proxy 和 forward 指向域名服务器，而不是 /etc/resolv.conf。

```
proxy .  172.16.0.1
```

最终的 ConfigMap 以及默认的 Corefile 配置如下所示：

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           upstream 172.16.0.1
           fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        proxy . 172.16.0.1
        cache 30
        loop
        reload
        loadbalance
    }
    consul.local:53 {
        errors
        cache 30
        proxy . 10.150.0.1
    }
```

CoreDNS 不仅仅提供 kube-dns 的功能。 为 kube-dns 创建的 ConfigMap 支持 StubDomains 和 upstreamNameservers 转换为 CoreDNS 中的 proxy 插件。 同样，kube-dns 中的 Federations 插件会转换为 CoreDNS 中的 federation 插件。

**示例**

用于 kubedns 的此示例 ConfigMap 描述了 federations, stubdomains and upstreamnameservers：

```
apiVersion: v1
data:
  federations: |
    {"foo" : "foo.feddomain.com"}
  stubDomains: |
    {"abc.com" : ["1.2.3.4"], "my.cluster.local" : ["2.3.4.5"]}
  upstreamNameservers: |
    ["8.8.8.8", "8.8.4.4"]
kind: ConfigMap
```

CoreDNS 中的等效配置将创建一个 Corefile：

* For federations:

```
federation cluster.local {
           foo foo.feddomain.com
        }
```

* For stubDomains:

```
abc.com:53 {
        errors
        cache 30
        proxy . 1.2.3.4
    }
    my.cluster.local:53 {
        errors
        cache 30
        proxy . 2.3.4.5
    }
```

带有默认插件的完整 Corefile：

```
.:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           upstream  8.8.8.8 8.8.4.4
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
        }
        federation cluster.local {
           foo foo.feddomain.com
        }
        prometheus :9153
        proxy .  8.8.8.8 8.8.4.4
        cache 30
    }
    abc.com:53 {
        errors
        cache 30
        proxy . 1.2.3.4
    }
    my.cluster.local:53 {
        errors
        cache 30
        proxy . 2.3.4.5
    }
```

## pod中coredns策略选择

在 kubernetes 中还提供了 dnsPolicy 决定 Pod 内预设 DNS 配置策略：

* None                                       无任何策略
* Default                                    默认
* ClusterFirst                             集群 DNS 优先
* ClusterFirstWithHostNet         集群 DNS 优先，并伴随着使用宿主机网络

### 无策略 (None)

清除 Pod 预设 DNS 配置，当 dnsPolicy 设置成为这个值之后， kubernetes 不会为 Pod 预先加载任何逻辑用于判定得到 DNS 的配置。因此若将 dnsPolicy 设置为 None , 为了避免 Pod 里面没有 DNS 配置，最好通过 dnsConfig 来描述自定义的 DNS 参数。如下所示：

```
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: base/java
    command:
      - "java -jar /opt/app.jar"
    imagePullPolicy: IfNotPresent
    name: demo
  restartPolicy: Always
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 172.xxx.xxx.201
    searches:
      - ns1.svc.cluster.local
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0
```

通过上述配置创建 Pod 之后，执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目，如下：

```
nameserver 172.xxx.xxx.201
search ns1.svc.cluster.local my.dns.search.suffix
options ndots:2 edns0
```

### 默认预设 (Default)

Pod 里面的 DNS 配置继承了宿主机上的 DNS 配置。即，该 Pod 的 DNS 配置与宿主机完全一致。

```
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: base/java
    command:
      - "java -jar /opt/app.jar"
    imagePullPolicy: IfNotPresent
    name: demo
  restartPolicy: Always
  dnsPolicy: Default
```

通过 cat /etc/resolv.conf 可查看到宿主机上的配置如下：

```
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 172.xxx.xxx.201
nameserver 114.114.114.114
```

通过上述配置创建 Pod 之后，执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目，如下：

```
nameserver 172.xxx.xxx.201
nameserver 114.114.114.114
```

### 集群优先 (ClusterFirst)

与 Default 相反，会预先使用 kube-dns (或 CoreDNS ) 的信息当预设置参数写入到该 Pod 内的DNS配置。

```
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: base/java
    command:
      - "java -jar /opt/app.jar"
    imagePullPolicy: IfNotPresent
    name: demo
  restartPolicy: Always
  dnsPolicy: ClusterFirst
```

通过上述配置创建 Pod 之后，执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目，如下：

```
nameserver 10.20.0.2
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
```

**注** 如设置了 hostNetwork = true 时，ClusterFirst 会被强制转化为 Default 。如下：

```
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: base/java
    command:
      - "java -jar /opt/app.jar"
    imagePullPolicy: IfNotPresent
    name: demo
  hostNetwork: true
  restartPolicy: Always
  dnsPolicy: ClusterFirst
```

通过上述配置创建 Pod 之后，执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目，如下：

```
nameserver 172.xxx.xxx.201
nameserver 114.114.114.114
```

**注** 设置 hostNetwork = true 之后，会让 Pod 与该节点公用相同的网络空间(网卡/路由等)

### 宿主机与 Kubernetes 共存 ( ClusterFirstWithHostNet )

同时使用 hostNetwork 与 kube-dns 作为 Pod 预设 DNS 配置。

```
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: base/java
    command:
      - "java -jar /opt/app.jar"
    imagePullPolicy: IfNotPresent
    name: demo
  hostNetwork: true
  restartPolicy: Always
  dnsPolicy: ClusterFirstWithHostNet
```

通过上述配置创建 Pod 之后，执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目，如下：

```
nameserver 10.20.0.2
search default.svc.k8s.local. svc.k8s.local. k8s.local.
options ndots:5
```

### yaml文件中添加nameserver和search域

```
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: base/java
    command:
      - "java -jar /opt/app.jar"
    imagePullPolicy: IfNotPresent
    name: demo
  restartPolicy: Always
  dnsConfig:
    nameservers:
      - 172.xxx.xxx.201
    searches:
      - ns1.svc.cluster.local
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0
```

通过上述配置创建 Pod 之后，执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目，如下：

```
nameserver 10.20.0.2
nameserver 172.xxx.xxx.201
search default.svc.cluster.local svc.cluster.local cluster.local ns1.svc.cluster.local my.dns.search.suffix
options ndots:2 edns0
```


# TKE网络模式之vpc-cni

本章介绍了如何在TKE上使用vpc-cni的网络模式。

## vpc-cni网络模式介绍

一般我们选择vpc-cni的网络模式分为2中情况，一种是创建集群的时候就选择vpc-cni模式，还有一种就是创建时候选择的GlobalRouter模式，后面才开启的vpc-cni模式，无论哪种情况开启，启用vpc-cni模式都需要满足如下要求

* 仅支持 k8s 1.10、1.12、1.14和1.16集群。
* 集群需要开启 cni 支持。
* 当前 VPC-CNI 模式仅支持单一子网，因此该模式下的 Pod 不可跨可用区调度。
* 当前 VPC-CNI 模式的子网不能与其他云上资源共用（如云服务器、负载均衡等）。
* 和子网处于相同可用区的节点才支持创建 VPC-CNI 模式的 Pod，请提前规划 VPC-CNI 模式子网。
* 您需要指定单节点下 VPC-CNI 模式的 Pod 数量上限，创建后不可修改。建议集群中节点配置相同。

## 创建集群时候选择vpc-cni模式

![upload-image](/files/-MN2J9HUEUXvC1pUBHAW)

创建集群的时候选择，vpc-cni模式，建议开启固定ip的功能，如果某些业务需要设置ip为固定的，这样可以通过固定ip设置，固定ip的模式默认只在vpc-cni网络模式下的StatefulSet类型工作负载才会生效。

集群网络模式设置成vpc-cni后，集群会开启tke-eni的服务，来操作弹性网卡来pod进行ip分配，ip的从容器网络中获取

![upload-image](/files/-MN2J9HVEp7yF2xdrqRt)

同时每一个节点会添加一个弹性网卡eth1，对应的pod ip通过新增的eth1分配副ip得到，创建的时候选择vpc-cni模式，所有的pod服务ip都是在容器的子网中，Pod 和 Node 的控制面和数据面完全在同一网络层面。

## 后续开启vpc-cni模式

开启的方式如下，必须要提前规划好一个子网，子网要求满足上面的要求，选择好你配置子网就可以开启了

![upload-image](/files/-MN2J9HWsfb2si8XLzpg)

![upload-image](/files/-MN2J9HXR1PYZS6FaiNi)

同样的开启vcp-cni的网络模式后，集群会开启tke-eni的服务，来操作弹性网卡来pod进行ip分配，并且会为每一个节点分配一个弹性网卡，采用vpc-cni的pod服务ip都是在容器的子网中获取，Pod 和 Node 的控制面和数据面完全在同一网络层面。

![upload-image](/files/-MN2J9HY3vswA-11fnCr)

后续开启的vpc-cni模式只能适用于StatefulSet类型的工作负载，其他的工作类型暂不支持从vpc-cni的容器子网中分配ip网络，其他的工作负载类型还是走GlobalRouter模式，pod ip的分配还是从之前的网络中获取，设置StatefulSet的网络模式为vpc-cni如下

![upload-image](/files/-MN2J9HZi1Y5FXOICush)

![upload-image](/files/-MN2J9H_UV1Lew4BI0f6)

![upload-image](/files/-MN2J9Ha7fHrEau30C-S)

如果你需要开启固定ip，则开启固定ip的选择。


# harbor搭建企业docker私有镜像仓库

本篇文章介绍了如何搭建企业级私有镜像仓库harbor及harbor仓库的使用。

## 搭建harbor仓库

### 安装docker和docker-compose

```
# curl -fsSL https://get.docker.com/ | sh
# systemctl start docker
# systemctl enable docker
# curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
```

### 下载harbor安装包

```
# wget https://github.com/goharbor/harbor/releases/download/v2.0.0/harbor-offline-installer-v2.0.0.tgz
# tar -zxvf harbor-offline-installer-v2.0.0.tgz
# mv harbor /opt/
# cd /opt/harbor
# cp harbor.yml.tmpl harbor.yml
```

### 配置https方式访问证书

1. 生成根证书(存放到目录/etc/docker/certs.d/reg.niewx.club)

   ```
   $ mkdir -p /etc/docker/certs.d/reg.niewx.club && cd /etc/docker/certs.d/reg.niewx.club
   ```
2. 创建自己的CA证书（不使用第三方权威机构的CA来认证，自己充当CA的角色

   ```
   $ openssl genrsa -out ca.key 2048
   ```
3. 生成自签名证书（使用已有私钥ca.key自行签发根证书）

   ```
   $ openssl req -x509 -new -nodes -key ca.key -days 10000 -out ca.crt -subj "/CN=Harbor-ca"
   ```
4. 生成服务器端私钥和CSR签名请求

   ```
   $ openssl req -newkey rsa:4096 -nodes -sha256 -keyout server.key -out server.csr
   ```
5. 签发服务器证书

   ```
   echo subjectAltName = IP:49.235.179.157 > extfile.cnf
   openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extfile extfile.cnf -out server.crt
   ```
6. 最终生成的证书如下

   ```
   [root@VM_0_13_centos reg.niewx.club]# ls
   ca.crt ca.key ca.srl extfile.cnf server.crt server.csr server.key
   ```

### 修改harbor配置项

```
[root@VM_0_13_centos harbor]# cat harbor.yml
# Configuration file of Harbor
# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: 1.1.1.1
# http related config
#http:
 # port for http, default is 80. If https enabled, this port will redirect to https port
# port: 80
# https related config
https:
 # https port for harbor, default is 443
 port: 443
 # The path of cert and key files for nginx
 certificate: /etc/docker/certs.d/reg.niewx.club/server.crt
 private_key: /etc/docker/certs.d/reg.niewx.club/server.key
# # Uncomment following will enable tls communication between all harbor components
# internal_tls:
# # set enabled to true means internal tls is enabled
# enabled: true
# # put your cert and key files on dir
# dir: /etc/harbor/tls/internal
# Uncomment external_url if you want to enable external proxy
# And when it enabled the hostname will no longer used
# external_url: https://reg.mydomain.com:8433
# The initial password of Harbor admin
# It only works in first time to install harbor
# Remember Change the admin password from UI after launching Harbor.
harbor_admin_password: 123456
```

主要需要修改上面标记的选项。

### 启动harbor

```
# cd /opt/harbor
# ./ prepare
# ./install.sh --with-clair (启动扫描器)
```

![upload-image](/files/-MN2J8OEatTyan1mqsty)

启动日志显示上面则启动成功

如果修改了配置项需要重新启动harbor则重新执行以下命令即可

```
# cd /opt/harbor
# ./ prepare
# ./install.sh --with-clair (启动扫描器)
```

## haobor仓库的使用

### harbor的登录和创建项目

默认账号为admin，密码为你之前修改配置密码

![upload-image](/files/-MN2J8OFPkBaKlwnB3HR)

项目管理，里面会有一个默认的公开项目library，所有人可以上传下载镜像

![upload-image](/files/-MN2J8OG1k8uRacjFJzb)

点击新建项目，输入项目名称，设置存储容量和是否公开

![upload-image](/files/-MN2J8OHhKIpt1EIYS-o)

查看项目的镜像仓库，也可以查看推送命令推送镜像

![upload-image](/files/-MN2J8OIsMrEZ6XaQGlZ)

### 客户端推送镜像

首先需要配置docker认真地址

```
[root@node1 ~]# cat /etc/docker/daemon.json
{
 "insecure-registries": ["https://1.1.1.1"],
 "registry-mirrors": ["https://yywkvob3.mirror.aliyuncs.com"],
 "exec-opts": ["native.cgroupdriver=systemd"]
}
# systemctl daemon-reload && systemctl restart docker
# docker login 1.1.1.1 -u admin -p *****
# docker tag busybox:latest 1.1.1.1/library/busybox:latest
# docker push 1.1.1.1/library/busybox:latest
```

### harbor中角色权限说明

| 角色    | 权限说明                      |
| ----- | ------------------------- |
| 访客    | 对于指定项目拥有只读权限              |
| 开发人员  | 对于指定项目拥有读写权限              |
| 维护人员  | 对于指定项目拥有读写权限，创建 Webhooks  |
| 项目管理员 | 除了读写权限，同时拥有用户管理/镜像扫描等管理权限 |


# Traefik1.7.17的部署使用

本篇文章介绍了在k8s上treafik1.7的搭建和使用。

因为我这里是作为kubernetes服务的暴露，因此你得有一个kubernetes集群

集群准备好了，需要下面的配置文件

## 部署rbac文件

rbac文件让ingress获取对应命名空间的权限

```
[root@master traefik]# cat ingress-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ingress
  namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: ingress
subjects:
  - kind: ServiceAccount
    name: ingress
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
```

## 部署traefik应用

这里部署应用中包含了https服务，因此需要在对应的节点上生成证书进行认证

首先openssl命令生成 CA 证书

```
$ openssl req -newkey rsa:2048 -nodes -keyout tls.key -x509 -days 365 -out tls.crt
```

现在我们有了证书，我们可以使用 kubectl 创建一个 secret 对象来存储上面的证书

```
$ kubectl create secret generic traefik-cert --from-file=tls.crt --from-file=tls.key -n kube-system
```

现在我们来配置 Traefik，让其支持 https，新建traefik.toml文件

```
[root@master https]# cat traefik.toml
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
  [[entryPoints.https.tls.certificates]]
  CertFile = "/ssl/tls.crt"
  KeyFile = "/ssl/tls.key"

然后通过cm挂载进pod里面，让pod能够访问该配置文件
```

然后通过cm挂载进pod里面，让pod能够访问该配置文件

```
$ kubectl create configmap traefik-conf --from-file=traefik.toml -n kube-system
```

最后部署我们的对应的traefik应用

```
[root@master traefik]# cat traefik.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: traefik-ingress-lb
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      terminationGracePeriodSeconds: 60
      hostNetwork: true
      restartPolicy: Always
      serviceAccountName: ingress
      containers:
      - image: traefik:v1.7.17
        name: traefik-ingress-lb
        volumeMounts:
        - mountPath: "/ssl"
          name: "ssl"
        - mountPath: "/config"
          name: "config"
        resources:
          limits:
            cpu: 200m
            memory: 30Mi
          requests:
            cpu: 100m
            memory: 20Mi
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        - name: https
          containerPort: 443
          hostPort: 443
        - name: admin
          containerPort: 8580
          hostPort: 8580
        args:
        - --web
        - --web.address=:8580
        - --kubernetes
        - --configfile=/config/traefik.toml
      volumes:
      - name: ssl
        secret:
          secretName: traefik-cert
      - name: config
        configMap:
          name: traefik-conf
```

## 部署traefik的service

```
[root@master traefik]# cat traefik-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
  - name: web
    port: 80
    targetPort: 8580
  - protocol: TCP
    port: 443
    name: https
  type: NodePort
```

## 给trarfik部署一个路由

```
[root@master traefik]# cat ingress-route.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  rules:
  - host: traefik.k8s.niewx.club #配置ui的域名
    http:
      paths:
      - path: /
        backend:
          serviceName: traefik-web-ui
          servicePort: web
```

## ingress 中 path 的用法

部署nginx测试服务测试ingress 中 path 的用法

```
[root@master traefik]# cat test.yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: svc1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: svc1
    spec:
      containers:
      - name: svc1
        image: cnych/example-web-service
        env:
        - name: APP_SVC
          value: svc1
        ports:
        - containerPort: 8080
          protocol: TCP

---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: svc2
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: svc2
    spec:
      containers:
      - name: svc2
        image: cnych/example-web-service
        env:
        - name: APP_SVC
          value: svc2
        ports:
        - containerPort: 8080
          protocol: TCP

---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: svc3
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: svc3
    spec:
      containers:
      - name: svc3
        image: cnych/example-web-service
        env:
        - name: APP_SVC
          value: svc3
        ports:
        - containerPort: 8080
          protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: svc1
  name: svc1
spec:
  type: ClusterIP
  ports:
  - port: 8080
    name: http
  selector:
    app: svc1
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: svc2
  name: svc2
spec:
  type: ClusterIP
  ports:
  - port: 8080
    name: http
  selector:
    app: svc2
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: svc3
  name: svc3
spec:
  type: ClusterIP
  ports:
  - port: 8080
    name: http
  selector:
    app: svc3
```

设置不同的同一个路由不同的路由访问对应的nginx服务

```
[root@master traefik]# cat test-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik-test
spec:
  rules:
  - host: test.k8s.niewx.club #配置ui的域名
    http:
      paths:
      - path: /s1
        backend:
          serviceName: svc1
          servicePort: 8080
      - path: /s2
        backend:
          serviceName: svc2
          servicePort: 8080
      - path: /
        backend:
          serviceName: svc3
          servicePort: 8080
```

## 部署结果

![upload-image](/files/-MN2J8NdWbjEZwnKFwww)

## 基于traefik的Basic auth认证

首先采用htpasswd创建文件

```
htpasswd -bc auth admin admin
```

基于上面的htpasswd创建secret(注意命名空间)

```
kubectl create secret generic nginx-basic-auth --from-file=auth -n kube-system
```

treafik引用对应的secret进行认证（注意如下）

* Secret文件必须与Ingress规则在同一命名空间。
* 目前只支持basic authentication。
* Realm不可配置，默认使用traefik。
* Secret必须只包含一个文件。

引用secret的yaml配置

```
[root@master traefik]# cat test-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik-test
  namespace: kube-system
  annotations:
    kubernetes.io/ingress.class: traefik
    # 认证类型
    ingress.kubernetes.io/auth-type: basic
    # 包含 user/password 的 Secret 名称
    ingress.kubernetes.io/auth-secret: nginx-basic-auth
    # 当认证的时候显示一个合适的上下文信息
    #ingress.kubernetes.io/auth-realm: 'Authentication Required - admin'
spec:
  rules:
  - host: test.k8s.niewx.club #配置ui的域名
    http:
      paths:
      - path: /s1
        backend:
          serviceName: svc1
          servicePort: 8080
      - path: /s2
        backend:
          serviceName: svc2
          servicePort: 8080
      - path: /
        backend:
          serviceName: svc3
          servicePort: 8080
```

## 解析域名到k8s集群中

一般暴露服务到外部都是提供域名访问，我们这边的集群节点通过lb来负载均衡，将域名解析到对应的lb上，后端监听的服务为treafik的80端口即可，这样treafik可以使用你所绑定解析的域名


# TKE中configMap的使用

本篇文章主要介绍了如何在tke集群中挂载configMap到pod中。

一般我们使用configMap主要用途分为2种，一种是挂载configmap中的配置文件进容器里，一种是引用configMap中的键值对作为容器的环境变量。

我们这里测试之前创建了3个测试文件，测试镜像为nginx最新镜像

## 测试文件

### nginx.conf

在配置文件中加入了一行测试注释 #test line，方便我们后续进行验证

```
apiVersion: v1
data:
  nginx.conf: |2-
    user  nginx;
    worker_processes  1;
    # test line
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    events {
        worker_connections  1024;
    }
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';

        access_log  /var/log/nginx/access.log  main;

        sendfile        on;
        #tcp_nopush     on;

        keepalive_timeout  65;

        #gzip  on;

        include /etc/nginx/conf.d/*.conf;
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2020-06-02T09:19:45Z"
  name: nginx-conf
  namespace: test
  resourceVersion: "10479370942"
  selfLink: /api/v1/namespaces/test/configmaps/nginx-conf
  uid: 32ca67ac-a4b2-11ea-9c35-e28957d7d0b3
```

### nginx-env

nginx-env用来测试环境变量的引用

```
apiVersion: v1
data:
  RUNTIME: nginx-test
  TZ: Asia/Shanghai
kind: ConfigMap
metadata:
  creationTimestamp: "2020-06-02T09:31:26Z"
  name: nginx-env
  namespace: test
  resourceVersion: "8584098360"
  selfLink: /api/v1/namespaces/test/configmaps/nginx-env
  uid: d4e69d1a-a4b3-11ea-9c35-e28957d7d0b3
```

### test.txt

```
apiVersion: v1
data:
  test.txt: '"\tmap $http_upgrade $connection_upgrade {\n\t    default upgrade;\n\t    ''''
    close;\n\t}"'
kind: ConfigMap
metadata:
  creationTimestamp: "2020-07-13T10:33:50Z"
  name: test
  namespace: test
  resourceVersion: "10478782044"
  selfLink: /api/v1/namespaces/test/configmaps/test
  uid: 95a40fc5-7e5b-4af1-892d-b3ed3d6ebee9
```

## 如何挂载配置文件到容器内

我们一般挂载文件到容器内分为多种情况：

* 替换容器内已经存在的某个配置文件
* 替换容器内不存在的配置文件
* 替换容器内的某个目录下所有文件

![upload-image](/files/-MN2J7TlYne4z_dPgtk4)

对应的yaml文件如下

```
        volumeMounts:
        - mountPath: /etc/nginx/test.txt
          name: conf
        - mountPath: /etc/nginx/nginx.conf
          name: nginx
          subPath: nginx.conf
        - mountPath: /etc/nginx/conf.d/
          name: conf
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          defaultMode: 420
          name: test
        name: conf
      - configMap:
          defaultMode: 420
          name: nginx-conf
        name: nginx
```

## 如何引用configMap的值作为环境变量

![upload-image](/files/-MN2J7TmWo-KXKTn734B)

yaml文件如下

```
spec:
      containers:
      - env:
        - name: RUNTIME
          valueFrom:
            configMapKeyRef:
              key: RUNTIME
              name: nginx-env
              optional: false
        - name: TZ
          valueFrom:
            configMapKeyRef:
              key: TZ
              name: nginx-env
              optional: false
```


# TKE集群中deployment使用vpc-cni模式

TKE集群中的网络模式分为Global Router和vpc-cni这2种，有的集群是创建的时候使用Global Router的网络模式，但是后续开启了vpc-cni的网络模式，因为集群是可以同时兼容2种网络模式。

在使用vpc-cni的网络模式大家会有一个疑惑，就是为什么我开启了vpc-cni模式后，创建的工作负载中的pod ip没有在我配置的子网中，没有和vpc在一个网段。

其实并不是开启了vpc-cni模式后，创建新的pod或者重建pod就会选择vpc-cni模式，除非你在创建集群的时候就选择的vpc-cni模式，那样创建出来的所有pod分配的ip都会和vpc在一个网段。其实在tke集群中有一个参数来控制你的pod是否选择vpc-cni模式，下面我们来说说如何使用这个参数，不同类型的工作负载如何选择vpc-cni模式。

## StatefulSet如何选择vpc-cni模式

一般我们创建StatefulSet的时候是可以在控制台选择是否选择vpc-cni模式，使用方法如下，在创建StatefulSet时候选择高级选项，点击是否选择vpc-cni模式即可，StatefulSet还可以选择是否配置固定ip模式

![upload-image](/files/-MN2J7BUW4SrJ6fNxiwl)

![upload-image](/files/-MN2J7BVyDuqhB3E2hn-)

![upload-image](/files/-MN2J7BWA70HSCeLL4ns)

## deployement如何选择vpc-cni模式

我们在创建deployment的时候在控制台是没有对应的选项来选择是否采用vpc-cni模式的，但是并不是说我们无法创建deployment的pod选择vpc-cni模式。我们可以在yaml中添加参数 tke.cloud.tencent.com/networks: tke-route-eni 即可，下面我们来实验一下

首先我们创建一个不配置这个参数的pod，我们在控制台是没有vpc-cni选择项的

![upload-image](/files/-MN2J7BX2HwDW9HrXOZs)

我们创建完之后发现pod和节点不在一个网段，对应的yaml中没有tke.cloud.tencent.com/networks: tke-route-eni这个字段

![upload-image](/files/-MN2J7BYj3Ct_PYDP1Du)

![upload-image](/files/-MN2J7BZvbHWXOyhNKmi)

下面我们修改yaml，加上这个字段再更新pod，我们发现pod和节点处在同一个网段中，

![upload-image](/files/-MN2J7B_rNmMp_Cmxfl4)

![upload-image](/files/-MN2J7Bao8bE9g67InS0)

所以，如果我们需要在deployment中使用vpc-cni模式只需要在yaml中加上tke.cloud.tencent.com/networks: tke-route-eni这个字段更新pod即可。


# TKE中使用lb直连获取客户端真实IP

我们在使用TKE的过程中会遇到一个这样的场景，就是我在服务端想获取到有哪些客户端在访问我，并且获取到客户端的真实ip。但是在k8s集群中经过多次的网络的转发，一般是无法获取到客户端真实ip。

为了满足这个常见TKE这边提供了lb直连pod的方式来获取客户端真实的ip，其实tke中能够实现这个方案的主要还是基于在vpc-cni的网络模式下实现的，因为vpc-cni模式可以使pod处于和node节点，vpc同一个网络下，而lb也是在vpc的网络中，因此这边lb就可以直接将请求转发到pod上，下面我们来说一下如何在tke中使用这种模式。

这边还是分为2种类型的工作负载来进行实践操作。

## Deployment使用lb直连

一般我们创建deploy类型的pod，关联创建svc的时候是无法选择直连lb的类型的，因为这边直连需要pod选择vpc-cni网络模式才可以，我们这边先手动创建一个正常的deployement，再去手动修改svc和pod的网络模式即可

我们这里在控制台创建了一个pod

![upload-image](/files/-MN2J8wjJJWe_vGuDD5p)

我们可以测试一下非直连的pod，通过10.168.1.5这个机器上发起访问，发现pod日志并没有对应的客户端ip信息

![upload-image](/files/-MN2J8wlTWuWRo10cxpE)

![upload-image](/files/-MN2J8wmhJzdE0FEiGp1)

下面修改对应的svc类型为直连模式，勾选这个采用负载均衡直连pod的模式

![upload-image](/files/-MN2J8wnh5c5EcSaNkJL)

修改下对应的pod为vpc-cni网络模式，在pod中加上参数

```
annotations:
        tke.cloud.tencent.com/networks: tke-route-eni
```

![upload-image](/files/-MN2J8wohOjd8cgtjP8m)

![upload-image](/files/-MN2J8wpCO-acJQdz_Ap)

现在pod的网络模式已经是vpc-cni了，并且svc也是直连了，下面我们来访问下，看日志能否看到client的ip

![upload-image](/files/-MN2J8wqXQwP4XqyuFeS)

![upload-image](/files/-MN2J8wrJjuyDn9X71o9)

经过测试是可以获取到客户端的ip的。

## StatefulSet使用lb直连

StatefulSet因为支持在界面创建vpc-cni的网路模式，所以我们只需要在控制台配置就行。

我们不选择vpc-cni模式是没有lb直连选项的

![upload-image](/files/-MN2J8wsXiHQ79POBQNU)

勾选了vpc-cni模式才会出现lb直连选项，所以我们需要在创建的时候选择vpc-cni，并选择lb直连

![upload-image](/files/-MN2J8wt9eZrwLItYMVB)

下面我们来测试下创建好的sts的直连nginx服务

![upload-image](/files/-MN2J8wuBo5wSvZI6RQY)

![upload-image](/files/-MN2J8wv6kuaxtYuwW0B)

![upload-image](/files/-MN2J8wwZ0584ZNaNm_f)

经过测试，这这边创建好的sts类型的nginx的pod也可以获取到客户端的真实ip


# TKE上搭建集群Dashboard

如果需要将TKE的信息展示给多个部门的人查看，但是又不想让他们通过控制台查看，这边可以搭建一个dashborad用来展示。

## 申请证书

因为dashborad需要https的访问，这边需要提供下证书，这个证书可以是自建从，也可以从腾讯云上申请一个免费的1年证书

![upload-image](/files/-MN2J7HFY1-9t9JlJupo)

![upload-image](/files/-MN2J7HGLcdPV4uwpfCG)

购买成功后，需要审核，审核通过可以下载对应的证书和rsa key

![upload-image](/files/-MN2J7HHVyE-gp327D9K)

## 创建命名空间来部署dashboard

```
# kubectl  create namespace kubernetes-dashboard
```

## 引用申请的证书创建secret

将证书上传到linux机器上$HOME/certs目录，并分别改名为tls.key和tls.crt

```
# mkdir $HOME/certs
# kubectl create secret generic kubernetes-dashboard-certs --from-file=$HOME/certs -n kubernetes-dashboard
```

## 创建deployement

首先拉取yaml文件，需要修改下yaml文件中的部分配置，再apply这个yaml文件

```
# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml
```

```
$ vim recommended.yaml

# 把创建 kubernetes-dashboard-certs Secret 注释掉，前面已通过命令创建

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

# 添加ssl证书路径，关闭自动更新证书，添加多长时间登出

      containers:
      - args:
        #- --auto-generate-certificates
        - --tls-cert-file=/tls.crt
        - --tls-key-file=/tls.key
        - --token-ttl=3600 #这个是登陆token的过期时间，如果不想重复输入token,可以设置长点
```

```
# kubectl  apply -f recommended.yaml

[root@VM_1_4_centos ~]# kubectl  get pods -n kubernetes-dashboard
NAME                                         READY   STATUS    RESTARTS   AGE
dashboard-metrics-scraper-555dc8bbb4-c8wtd   1/1     Running   0          24h
kubernetes-dashboard-755c66fc9f-p4tjd        1/1     Running   0          135m
```

## 创建登陆用户

```
# vim create-admin.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard

---

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

创建账户

```
# kubectl apply -f create-admin.yaml
```

## 获取登陆token

```
# kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')
```

## 修改访问的svc为lb类型提供公网访问

Dashborad会创建2个svc，kubernetes-dashboard是用来页面访问的

![upload-image](/files/-MN2J7HI0kMUcYY_LTcB)

## 浏览器输入公网ip用https访问

输入<https://vip> 后会让你输入token，将第6步获取的token输入，就可以进行查看

![upload-image](/files/-MN2J7HJG8Vh9lCOi4ns)


# TKE中在节点上获取容器资源配置

本章讲述了如何获取容器的资源配置

## 容器的实现原理

从本质上，容器其实就是一种沙盒技术。就好像把应用隔离在一个盒子内，使其运行。因为有了盒子边界的存在，应用于应用之间不会相互干扰。并且像集装箱一样，拿来就走，随处运行。其实这就是 PaaS 的理想状态。

实现容器的核心，就是要生成限制应用运行时的边界。我们知道，编译后的可执行代码加上数据，叫做程序。而把程序运行起来后，就变成了进程，也就是所谓的应用。如果能在应用启动时，给其加上一个边界，这样不就能实现期待的沙盒吗？

在 Linux 中，实现容器的边界，主要有两种技术 Cgroups 和 Namespace. Cgroups 用于对运行的容器进行资源的限制，Namespace 则会将容器隔离起来，实现边界。

容器的限制：Cgroups

通过 Namespace 技术，我们实现了容器和容器间，容器与宿主机之间的隔离。但这还不够，想象这样一种场景，宿主机上运行着两个容器。虽然在容器间相互隔离，但以宿主机的视角来看的话，其实两个容器就是两个特殊的进程，而进程之间自然存在着竞争关系，自然就可以将系统的资源吃光。当然，我们不能允许这么做的。

这里可以查看cpu，内存，我们拿查看内存举例，/proc/meminfo是了解Linux系统内存使用状况的主要接口，那么我们如何查看容器的这个接口文件获取容器的内存数据来进行统计。

首先获取容器的pid

```
# docker inspect -f {{.State.Pid}} b930cd9c4ba9
6298
```

找到容器的cgroup文件，并获取cgroup文件

```
# cd /proc/6298
[root@VM_1_4_centos 6298]# cat cgroup
12:blkio:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
11:cpuset:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
10:cpu,cpuacct:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
9:devices:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
8:hugetlb:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
7:freezer:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
6:net_cls:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
5:rdma:/
4:perf_event:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
3:memory:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
2:pids:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
1:name=systemd:/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0/b930cd9c4ba969a1366da5c79fbce8a0a6690649d0238d9f5fc34f8269fc43b5
```

去sys目录下获取/proc/meminfo

```
cd /sys/fs/cgroup/memory/kubepods/burstable/pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd
```

查看对应的文件获取容器的内存信息

```
[root@VM_1_4_centos pod17b4aaff-dd14-4ba1-a735-5e6a7725fbd0]# cat memory.meminfo
MemTotal:         102400 kB
MemFree:           78776 kB
Buffers:               0 kB
Cached:                0 kB
SwapCached:            0 kB
Active:                0 kB
Inactive:              0 kB
Active(anon):          0 kB
Inactive(anon):        0 kB
Active(file):          0 kB
Inactive(file):        0 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:             0 kB
Mapped:                0 kB
Shmem:                 0 kB
Slab:                  0 kB
SReclaimable:          0 kB
SUnreclaim:            0 kB
KernelStack:           0 kB
PageTables:            0 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:           0 kB
Committed_AS:          0 kB
VmallocTotal:          0 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
```


# TKE挂载COS桶容器目录权限如何设置

本篇文章介绍了在tke集群中挂载cos桶如何配置目录权限问题

## 问题背景

用户在tke的集群中创建工作负载并把某一个对应的/data目录挂载到cos桶的根目录，在镜像构建的时候有把/data目录设置权限为755，但是运行容器后成功挂载/data/目录到cos桶的根目录，但是发现用非root账号确无法访问/data下面的文件，这边镜像的启动用户是非root用户，查看容器内/data目录权限变成了700，为什么这边设置的目录权限是755，挂载到COS后就变成了700权限呢？

## 排查思路

这边首先进行了简单的自测，启动2个nginx工作负载，一个负载将目录/etc/nginx/conf.d挂载到cos桶上，一个正常运行不挂载，然后发现确实挂载cos后，默认会把目录权限变成700。

![upload-image](/files/-MN2J83xbSTPiLerxAX3)

这边咨询的cos的同事，他们说有一个参数是用来控制目录权限的，如果挂载时候不加这个参数就默认变成700，加了这个参数才不会。现在问题已经大致明确了，就是这个参数没有设置的原因，那么这个参数该怎么使用呢？

```
-oallow_other
如果要允许其他用户访问挂载文件夹，可以在运行 COSFS 的时候指定该参数。
```

## 配置-oallow\_other参数

其实在使用cos桶进行挂载的时候在pv创建界面是可以进行参数设置的，但是由于我们习惯在控制直接创建pvc关联pv，然后pv会自动创建导致很多人没有去关注这个cos的参数项配置。

如果你想配置-oallow\_other这个参数，可以通过2种方式进行配置，一种是编写yaml文件来创建pv,pvc，这种方式可以参考这个指导 <https://github.com/TencentCloud/kubernetes-csi-tencentcloud/blob/master/docs/README_COSFS.md>

另外一种方式就是通过控制台来配置参数，具体操作如下

### 控制台创建pv，配置参数

在挂载选项填入-oallow\_other这个参数，想填写多个参数空格分隔，cos提供的参数配置选项可以参考<https://cloud.tencent.com/document/product/436/6883#.E5.B8.B8.E7.94.A8.E6.8C.82.E8.BD.BD.E9.80.89.E9.A1.B9>

![upload-image](/files/-MN2J83ywhxZRYJdXJ96)

### 创建pvc关联创建的pv

新建pvc关联创建好的pv

![upload-image](/files/-MN2J83zBL6ZUH-FoObN)

### 在工作负载中挂载创建好的pvc

创建nginx的工作负载挂载创建好的pvc

![upload-image](/files/-MN2J84-4d5h8VcEStxw)

### 验证对应的目录权限是否正确

进入容器中查看/etc/nginx/conf.d的目录不再是700，创建一个test文件，也挂载到了cos桶中

![upload-image](/files/-MN2J840q9pH-Yv__vFP)

![upload-image](/files/-MN2J8412ZU9u1TGR4bg)

### 上传一个文件到cos桶看容器中是否可以访问

上传一个1.txt文件到cos桶，从容器中看文件权限是777，可以进行正常访问

![upload-image](/files/-MN2J842HKbKOUkBZoyZ)

![upload-image](/files/-MN2J843-mpkOhNGH4V1)


# TKE中挂载文件到CFS子目录

本次我们来讲如何在TKE容器中多个pod挂载文件到文件服务器CFS不同的子目录

## 首先创建好CFS文件服务器

登录CFS控制台，创建一个文件系统

![upload-image](/files/-MN2J9Ql422J-8NTibgl)

## 创建CFS子目录

如何创建CFS的子目录呢，这里我们找一个可以访问cfs内网ip的服务器，先将cfs的根目录下挂载到/root/cfs，然后在/root/nfs下创建2个子目录tke和tke-1，注意创建完目录后记得解挂/root/nfs这个目录，对应的子目录在文件系统中已经创建好了。

```
[root@VM-1-5-centos ~]# mkdir nfs
[root@VM-1-5-centos nfs]# sudo mount -t nfs -o vers=4.0 1.1.1.1:/ /root/nfs
[root@VM-1-5-centos nfs]# mkdir tke
[root@VM-1-5-centos nfs]# mkdir tke-1
[root@VM-1-5-centos nfs]# cd
[root@VM-1-5-centos ~]# umount /root/nfs
```

corresponding condition of pod readiness gate "platform.tkex/InPlace-Update-Ready" does not exist., the status of pod readiness gate "cloud.tencent.com/load-balancer-backendgroup-ready" is not "True", but False

## TKE中创建StorageClass

登录tke，在集群中新建sc

![upload-image](/files/-MN2J9QmvHGAYS74rHSh)

## 创建PV

这里我们分别为tke和tke-1创建一个pv

![upload-image](/files/-MN2J9Qn72cT-AVxD1B2)

![upload-image](/files/-MN2J9QqraBHeHdknEOs)

## 创建PVC

我们创建2个pvc关联上一步创建的pv

![upload-image](/files/-MN2J9Qrm4bPhM4tIgPb)

![upload-image](/files/-MN2J9QsmbS6Y-_jCYfu)

## 挂载PVC

我们创建2个测试的nginx镜像pod来挂载2个pvc

![upload-image](/files/-MN2J9QtrqexV9Zj7reN)

![upload-image](/files/-MN2J9QuTBZhnm5i4bQV)

## 验证

查看pod启动成功，挂载成功，在CFS中查看也能看到对应的2条挂载信息

![upload-image](/files/-MN2J9QvgzlHeQV9UROi)

![upload-image](/files/-MN2J9QwbZVbzGSaWys7)


# TKE上部署metrics-server

kubectl top 是基础命令，但是需要部署配套的组件才能获取到监控值

* 1.8以下：部署 heapter
* 1.8以上：部署 metric-server

下面我们来在TKE上配置下1.8版本以上的metric-server

## 获取metric-server的部署yaml

登录能执行kubectl命令的客户端机器，执行下面命令下载

```
# git clone https://github.com/kubernetes-incubator/metrics-server
# cd metrics-server/manifests/base
[root@VM_0_13_centos base]# ll
total 24
-rw-r--r-- 1 root root  298 Jul  8 11:57 apiservice.yaml
-rw-r--r-- 1 root root 1386 Aug 13 10:35 deployment.yaml
-rw-r--r-- 1 root root  158 Jul  8 11:57 kustomization.yaml
-rw-r--r-- 1 root root  239 Jul  8 11:57 pdb.yaml
-rw-r--r-- 1 root root 1714 Jul  8 11:57 rbac.yaml
-rw-r--r-- 1 root root  297 Jul  8 11:57 service.yaml
```

## 修改yaml文件参数

修改对应的metrics-server-deployment.yaml文件，需要改镜像源，国外的镜像需要科学上网下载，还需要添加如下参数

command: - /metrics-server - --kubelet-preferred-address-types=InternalIP - --kubelet-insecure-tls

不添加参数报错，metrics-server 启动提示no metrics known for pod?

首先需要知道的是metrics-server默认会使用hostname 来进行通讯。

如果没有进行相应配置的话，那么通过hostname是无法正常通讯的。

所以使用默认命令行启动，由于无法正常通过hostname通信就会产生错误， 从而提示no metrics known for pod

实际上metrics-server不只支持通过hostname进行通讯，还支持使用IP来进行通讯，只不过需要显式指定命令行参数：

\--kubelet-preferred-address-types=InternalIP

其中InternalIP可以修改为以下值:

InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP (具体含义这里就不展开了，感兴趣的可以自行了解。)

但是呢，事情到这里还没有结束，当你加上命令行参数后，会发现出现另一个错误x509: cannot validate certificate。

这是由于证书验证不通过导致的，所以我们需要让metrics-server忽略掉证书错误。而忽略证书错误也是有命令行支持的，我们添加如下命令行参数就可以解决了：

\--kubelet-insecure-tls

修改后的yaml如下：

```
[root@VM_0_13_centos base]# cat deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
  labels:
    k8s-app: metrics-server
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    metadata:
      name: metrics-server
      labels:
        k8s-app: metrics-server
    spec:
      serviceAccountName: metrics-server
      volumes:
      # mount in tmp so we can safely use from-scratch images and/or read-only containers
      - name: tmp-dir
        emptyDir: {}
      containers:
      - name: metrics-server
        image: ccr.ccs.tencentyun.com/mirrors/metrics-server-amd64:v0.3.1
        imagePullPolicy: IfNotPresent
        command:
          - /metrics-server
          - --kubelet-preferred-address-types=InternalIP
          - --kubelet-insecure-tls
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
        ports:
        - name: main-port
          containerPort: 4443
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /healthz
            port: main-port
            scheme: HTTPS
        securityContext:
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
        volumeMounts:
        - name: tmp-dir
          mountPath: /tmp
      nodeSelector:
        kubernetes.io/os: linux
```

## 执行apply部署对应的yaml文件

我们在对应的部署目录文件中发现有一个kustomization.yaml，这个文件的作用可以先了解下kustomize这个项目。

kustomize是sig-cli的一个子项目，它的设计目的是给kubernetes的用户提供一种可以重复使用同一套配置的声明式应用管理，从而在配置工作中用户只需要管理和维护kubernetes的API对象，而不需要学习或安装其它的配置管理工具，也不需要通过复制粘贴来得到新的环境的配置。

我们这里不做kustomize的部署，所以后续不需要applykustomization.yaml这个yaml。

我们这里直接部署目录下的所有yaml文件，有一个报错不用管，是因为我们没安装kustomize。

```
[root@VM_0_13_centos base]# kubectl apply -f .
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io unchanged
deployment.apps/metrics-server configured
poddisruptionbudget.policy/metrics-server unchanged
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader unchanged
serviceaccount/metrics-server unchanged
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader unchanged
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator unchanged
clusterrole.rbac.authorization.k8s.io/system:metrics-server unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server unchanged
service/metrics-server unchanged
error: unable to recognize "kustomization.yaml": no matches for kind "Kustomization" in version "kustomize.config.k8s.io/v1beta1"
```

## 验证和执行命令查看pod和node的内存及cpu指标

这边metrics-server运行正常后，执行命令是可以正常查看node和pod的性能指标的

```
[root@VM_0_13_centos base]# kubectl get pod -n kube-system | grep metrics-server
metrics-server-6b59b7fc98-wkvmz         1/1     Running   0          136m

[root@VM_0_13_centos base]# kubectl top nodes
NAME            CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
10.168.1.4      432m         5%     7360Mi          52%
10.168.1.5      1342m        34%    5489Mi          82%
10.168.100.22   109m         2%     4401Mi          62%
[root@VM_0_13_centos base]# kubectl top node 10.168.1.4
NAME         CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
10.168.1.4   432m         5%     7360Mi          52%
[root@VM_0_13_centos base]# kubectl top pod -n kube-system
NAME                                    CPU(cores)   MEMORY(bytes)
ccs-log-collector-7h4qv                 8m           182Mi
ccs-log-collector-jgh88                 4m           499Mi
ccs-log-collector-t4gsc                 3m           187Mi
cls-provisioner-5c5dcfb9fc-bsjrb        1m           9Mi
coredns-7f47d46d54-257rj                2m           13Mi
coredns-7f47d46d54-h6czg                3m           23Mi
csi-attacher-cfsplugin-0                2m           14Mi
csi-coslauncher-mc488                   1m           30Mi
csi-coslauncher-nxjsb                   1m           60Mi
csi-coslauncher-wfs7m                   1m           11Mi
csi-cosplugin-external-runner-0         2m           14Mi
csi-cosplugin-s8xf6                     1m           6Mi
csi-cosplugin-vzdfn                     1m           7Mi
csi-cosplugin-xpkbp                     1m           33Mi
csi-nodeplugin-cfsplugin-j467w          1m           24Mi
csi-nodeplugin-cfsplugin-jpctb          1m           12Mi
csi-nodeplugin-cfsplugin-r2kjc          1m           10Mi
csi-provisioner-cfsplugin-0             2m           16Mi
ip-masq-agent-cvrsl                     1m           10Mi
ip-masq-agent-hv2p6                     1m           6Mi
ip-masq-agent-zfmg5                     1m           8Mi
kube-proxy-hzzhb                        14m          74Mi
kube-proxy-lrh8l                        21m          45Mi
kube-proxy-pjbmm                        15m          64Mi
kubernetes-dashboard-654cb6dc56-pql6z   1m           16Mi
l7-lb-controller-5b49444bc9-wbt8j       2m           28Mi
metrics-server-6b59b7fc98-wkvmz         1m           18Mi
swift-566d576-2rtsz                     1m           25Mi
tiller-deploy-698956c985-7njgz          1m           15Mi
tke-bridge-agent-c86n7                  2m           14Mi
tke-bridge-agent-hstm7                  2m           10Mi
tke-bridge-agent-sl5j5                  1m           9Mi
tke-cni-agent-fllwd                     0m           4Mi
tke-cni-agent-js9ls                     0m           18Mi
tke-cni-agent-q7vb8                     0m           13Mi
tke-eni-agent-694nz                     1m           28Mi
tke-eni-agent-sskl9                     1m           8Mi
tke-eni-agent-vkt92                     1m           14Mi
tke-eni-ipamd-b866c887f-bgq5z           3m           20Mi
tke-log-agent-6jt2l                     2m           42Mi
tke-log-agent-jwpwq                     994m         96Mi
tke-log-agent-vmndp                     4m           112Mi
tke-persistent-event-5d6fc9bccd-nrsqp   1m           82Mi
```


# TKE之初识容器探测器

kubelet 使用存活探测器来知道什么时候要重启容器。例如，存活探测器可以捕捉到死锁（应用程序在运行，但是无法继续执行后面的步骤）。这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。

kubelet 使用就绪探测器可以知道容器什么时候准备好了并可以开始接受请求流量， 当一个 Pod 内的所有容器都准备好了，才能把这个 Pod 看作就绪了。这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。在 Pod 还没有准备好的时候，会从 Service 的负载均衡器中被剔除的。

kubelet 使用启动探测器可以知道应用程序容器什么时候启动了。如果配置了这类探测器，就可以控制容器在启动成功后再进行存活性和就绪检查，确保这些存活、就绪探测器不会影响应用程序的启动。这可以用于对慢启动容器进行存活性检测，避免它们在启动运行之前就被杀掉。

## 就绪探针readinessProbe

用于判断容器是否启动完成，即容器的Ready是否为True，可以接收请求，如果ReadinessProbe探测失败，则容器的Ready将为False，控制器将此Pod的Endpoint从对应的service的Endpoint列表中移除，从此不再将任何请求调度此Pod上，直到下次探测成功。通过使用Readiness探针，Kubernetes能够等待应用程序完全启动，然后才允许服务将流量发送到新副本。

比如使用tomcat的应用程序来说，并不是简单地说tomcat启动成功就可以对外提供服务的，还需要等待spring容器初始化，数据库连接没连上等等。对于spring boot应用，默认的actuator带有/health接口，可以用来进行启动成功的判断

### 探测方式

* exec：通过执行命令来检查服务是否正常，针对复杂检测或无HTTP接口的服务，命令返回值为0则表示容器健康。
* httpGet：通过发送http请求检查服务是否正常，返回200-399状态码则表明容器健康。
* tcpSocket：通过容器的IP和Port执行TCP检查，如果能够建立TCP连接，则表明容器健康。

### 探测参数

* initialDelaySeconds：容器启动后要等待多少秒后存活和就绪探测器才被初始化，默认是 0 秒，最小值是 0。
* periodSeconds：执行探测的时间间隔（单位是秒）。默认是 10 秒。最小值是 1。
* timeoutSeconds：探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
* successThreshold：探测器在失败后，被视为成功的最小连续成功数。默认值是 1。存活探测的这个值必须是 1。最小值是 1。
* failureThreshold：当探测失败时，Kubernetes 的重试次数。存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。

HTTP 探测器可以在 httpGet 上配置额外的字段：

* host：连接使用的主机名，默认是 Pod 的 IP。也可以在 HTTP 头中设置 “Host” 来代替。
* scheme ：用于设置连接主机的方式（HTTP 还是 HTTPS）。默认是 HTTP。
* path：访问 HTTP 服务的路径。
* httpHeaders：请求中自定义的 HTTP 头。HTTP 头字段允许重复。
* port：访问容器的端口号或者端口名。如果数字必须在 1 ～ 65535 之间。

### TKE中实践

一般我们在TKE中单独配置readinessProbe，如果这边连续探测多少次都失败，pod是不会重启的，只是不会接受请求的。我们创建一个只设置就绪探针的pod，并探测81端口，看pod会怎么样。

![upload-image](/files/-MN2J9exrlHiXG1616xb)

![upload-image](/files/-MN2J9eyvIl9rLwZsBGn)

我们查看事件发现探测了13次失败了，pod是不会重启的，这边会一直探测直到服务启动成功。

![upload-image](/files/-MN2J9ezBeaCAeDxQtM-)

![upload-image](/files/-MN2J9f-gAkX-1CszLNo)

## 存活探针livenessProbe

用于判断容器是否存活，即Pod是否为running状态，如果LivenessProbe探针探测到容器不健康，则kubelet将kill掉容器，并根据容器的重启策略是否重启。如果一个容器不包含LivenessProbe探针，则Kubelet认为容器的LivenessProbe探针的返回值永远成功。

有时应用程序可能因为某些原因（后端服务故障等）导致暂时无法对外提供服务，但应用软件没有终止，导致K8S无法隔离有故障的pod，调用者可能会访问到有故障的pod，导致业务不稳定。K8S提供livenessProbe来检测应用程序是否正常运行，并且对相应状况进行相应的补救措施。

重启策略：指示容器是否正在运行。如果存活探测失败，则 kubelet 会杀死容器，并且容器将受到其 重启策略 的影响。如果容器不提供存活探针，则默认状态为 Success

### 探测方式

* exec：通过执行命令来检查服务是否正常，针对复杂检测或无HTTP接口的服务，命令返回值为0则表示容器健康。
* httpGet：通过发送http请求检查服务是否正常，返回200-399状态码则表明容器健康。
* tcpSocket：通过容器的IP和Port执行TCP检查，如果能够建立TCP连接，则表明容器健康。

### 探测参数

* initialDelaySeconds：容器启动后要等待多少秒后存活和就绪探测器才被初始化，默认是 0 秒，最小值是 0。
* periodSeconds：执行探测的时间间隔（单位是秒）。默认是 10 秒。最小值是 1。
* timeoutSeconds：探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
* successThreshold：探测器在失败后，被视为成功的最小连续成功数。默认值是 1。存活探测的这个值必须是 1。最小值是 1。
* failureThreshold：当探测失败时，Kubernetes 的重试次数。存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。

HTTP 探测器可以在 httpGet 上配置额外的字段：

* host：连接使用的主机名，默认是 Pod 的 IP。也可以在 HTTP 头中设置 “Host” 来代替。
* scheme ：用于设置连接主机的方式（HTTP 还是 HTTPS）。默认是 HTTP。
* path：访问 HTTP 服务的路径。
* httpHeaders：请求中自定义的 HTTP 头。HTTP 头字段允许重复。
* port：访问容器的端口号或者端口名。如果数字必须在 1 ～ 65535 之间。

### TKE中实践

这里存活探针不一样，加入连续探测多次失败会根据你设置的重启策略来看是否让pod重启，这里我们配置一个单独的存活探针的pod。也是nignx服务，并探测81端口。配置的重启策略是always，下面我们看看pod会怎么样。

![upload-image](/files/-MN2J9fACDhmfZjvKlo9)

![upload-image](/files/-MN2J9fBwUiJ6izBSPoJ)

这边我们从事件看出，如果联系探测3次失败就会重启pod。

![upload-image](/files/-MN2J9fCt_iIzhF6Z2S_)

## 启动探针startupProbe

startupProbe是在k8s v1.16加入了alpha版，有时候，会有一些现有的应用程序在启动时需要较多的初始化时间。要不影响对引起探测死锁的快速响应，这种情况下，设置存活探测参数是要技巧的。技巧就是使用一个命令来设置启动探测，针对HTTP 或者 TCP 检测，可以通过设置 failureThreshold \* periodSeconds参数来保证有足够长的时间应对糟糕情况下的启动时间。

```
ports:
- name: liveness-port
  containerPort: 8080
  hostPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 1
  periodSeconds: 10

startupProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 30
  periodSeconds: 10
```

有了启动探测，应用程序将会有最多 5 分钟(30 \* 10 = 300s) 的时间来完成它的启动。 一旦启动探测成功一次，存活探测任务就会接管对容器的探测，对容器死锁可以快速响应。 如果启动探测一直没有成功，容器会在 300 秒后被杀死，并且根据restartPolicy来设置 Pod 状态。

### 探测方式

* exec：通过执行命令来检查服务是否正常，针对复杂检测或无HTTP接口的服务，命令返回值为0则表示容器健康。
* httpGet：通过发送http请求检查服务是否正常，返回200-399状态码则表明容器健康。
* tcpSocket：通过容器的IP和Port执行TCP检查，如果能够建立TCP连接，则表明容器健康。

### 探测参数

* initialDelaySeconds：容器启动后要等待多少秒后存活和就绪探测器才被初始化，默认是 0 秒，最小值是 0。
* periodSeconds：执行探测的时间间隔（单位是秒）。默认是 10 秒。最小值是 1。
* timeoutSeconds：探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
* successThreshold：探测器在失败后，被视为成功的最小连续成功数。默认值是 1。存活探测的这个值必须是 1。最小值是 1。
* failureThreshold：当探测失败时，Kubernetes 的重试次数。存活探测情况下的放弃就意味着重新启动容器。就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。

HTTP 探测器可以在 httpGet 上配置额外的字段：

* host：连接使用的主机名，默认是 Pod 的 IP。也可以在 HTTP 头中设置 “Host” 来代替。
* scheme ：用于设置连接主机的方式（HTTP 还是 HTTPS）。默认是 HTTP。
* path：访问 HTTP 服务的路径。
* httpHeaders：请求中自定义的 HTTP 头。HTTP 头字段允许重复。
* port：访问容器的端口号或者端口名。如果数字必须在 1 ～ 65535 之间。

## 参考文档

<https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>


# 自制CA证书设置ssl证书

本章介绍了如何自制CA证书签发ssl证书来配置https服务

## 安装openssl工具

```
# yum install openssl
# yum install openssl-devel
# openssl version -a
```

## 生成ca证书

### 生成 CA 私钥

```
# openssl genrsa -out ca.key 1024
```

### 生成请求文件

```
openssl req -new -key ca.key -out ca.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=devops/OU=devops/CN=nwx_qdlg@163.com"
```

**注意**:这里的 Organization Name (eg, company) \[Internet Widgits Pty Ltd]: 后面生成客户端和服务器端证书的时候也需要填写，O和OU不要写成一样的！！！

### 生成CA证书

```
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
```

## 生成服务端证书并CA签发

### 生成服务端私钥

```
openssl genrsa -out server.key 1024
```

### 生成服务端公钥

```
openssl rsa -in server.key -pubout -out server.pem
```

### 生成服务端向CA申请签名的CSR

```
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=serverdevops/OU=serverdevops/CN=nwx_qdlg@163.com"
```

### 生成服务端带有CA签名的证书

```
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
```

## 生成客户端证书并CA签发

### 生成客户端私钥

```
openssl genrsa -out client.key 1024
```

### 生成客户端公钥

```
openssl rsa -in client.key -pubout -out client.pem
```

### 生成客户端向CA申请签名的CSR

```
openssl req -new -key client.key -out client.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=clientdevops/OU=clientdevops/CN=nwx_qdlg@163.com"
```

### 生成客户端带有CA签名的证书

```
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt
```

## 使用证书在nginx进行https的配置

将服务端或者客户端生成的私钥和CA签名证书拷贝到对应的服务部署机器上进行部署

例如：

配置nginx 我们拿到CA签发的这个证书后，需要将证书配置在nginx中。 首先，我们将server.crt和server.key拷贝到nginx的配置文件所在的目录 其次，在nginx的配置中添加如下配置：

```
server {
        listen       443 ssl;
        server_name  你的域名;
        charset utf-8;

        ssl on;
        ssl_certificate      server.crt;
        ssl_certificate_key  server.key;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }
```


# TKE上动态部署jenkins-slaves

本章我们来讲一下如何在TKE上搭建jenkins这个CI/CD工具，这边我们在进行构建的时候可以自动生成一个slave pod来进行走流水线，流水线跑完之后会自动销毁。

## 部署jenkins的rbac权限

```
[root@VM_0_13_centos jenkins]# cat rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins2
  namespace: jenkins

---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins2
rules:
  - apiGroups: ["extensions", "apps"]
    resources: ["deployments"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create","delete","get","list","patch","update","watch"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create","delete","get","list","patch","update","watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get","list","watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: jenkins2
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: jenkins2
subjects:
  - kind: ServiceAccount
    name: jenkins2
    namespace: jenkins
```

## 创建pvc进行jenkins的数据持久化

这边通过cbs卷挂载jenkins的数据

![upload-image](/files/-MN2J7igf1ogCWQFMpGx)

## 部署jenkins的应用

```
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins2
  namespace: kube-ops
spec:
  template:
    metadata:
      labels:
        app: jenkins2
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: jenkins2
      containers:
      - name: jenkins
        image: jenkins/jenkins:lts
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources:
          limits:
            cpu: 1000m
            memory: 1Gi
          requests:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        volumeMounts:
        - name: jenkinshome
          subPath: jenkins2
          mountPath: /var/jenkins_home
      securityContext:
        fsGroup: 1000
      volumes:
      - name: jenkinshome
        persistentVolumeClaim:
          claimName: opspvc

---
apiVersion: v1
kind: Service
metadata:
  name: jenkins2
  namespace: kube-ops
  labels:
    app: jenkins2
spec:
  selector:
    app: jenkins2
  type: NodePort
  ports:
  - name: web
    port: 8080
    targetPort: web
    nodePort: 30002
  - name: agent
    port: 50000
    targetPort: agent
```

## 采用treafik暴露域名给jenkins

```
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: jenkins
  namespace: jenkins
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`jenkins.tx.niewx.club`)
    kind: Rule
    services:
    - name: jenkins2
      port: 8080
```

## 访问初始化jenkins

查看登录的初始密码，然后安装好推荐的插件，安装好之后配置账号，直接登录即可。

```
cat /var/lib/jenkins/secrets/initialAdminPassword
```

![upload-image](/files/-MN2J7ihEZyvURI0Duri)

## 安装Kubernetes-plugin 插件

我这边已经安装好了，可以选择可选插件输入Kubernetes搜索找到插件进行安装

![upload-image](/files/-MN2J7iikufxc0WejqVJ)

## 连接TKE集群

在“系统配置”面板最下方，选择“云”模块下的【新增一个云】>【Kubernetes】。如下图所示

对应的证书文件和token以及api地址可以从TKE集群中节点上的下面文件获取

集群证书获取

```
[root@VM_1_4_centos ~]# cat /etc/kubernetes/cluster-ca.crt
-----BEGIN CERTIFICATE-----
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIwMDYwMjAzMTYzNVoXDTMwMDUzMTAzMTYzNVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANmh
Z/tm6Ej66vNgkcRk4zirSGpoLpHaofq/q9eOE8zLr+QfzwNlDImeNfpX/OG/atz2
bl6DY4kAt7CsHUVs60leCIs1PZHY212vevlL/nWceLHqVeskynwT4xwJKI48DnwM
UgABoXTqm3S2k02CRJcm0Ucx1pw+E0l0Cy/hJcEwa8kFaqsskHqL1jHhRGB4ENGO
6ocjEzZstZdjv1Ab1WoOmHhvvI3mT4Gqb1BSd3BFqlu0id1wJvIXuood0bYDTXSD
KHHKZFOnrxwFFAg3ZxmrFDkfqfmILV0dcA4KKW/1q0g7+XepM2gZrrbwMAjVaOpH
vPUEaLn/kJ1LSUNz09UCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKUMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAC6jddY1Y5ofHZkthZu8Hj2Op36q
kY9pv1fpexcPXtcRuxgw6+mQjEzKraEDoDYdX/DkHkuYVulWl6Sqv+P9WRY+hLzH
/K+GHh4uuuIlL+C6Uk1nV0clrznv+CqXKY9AZ8sY+x6l9d3hIxaDxwKWDfzJe2sU
zMJZsbxyy4wIkwlG5+1lSD3YYRo1TooYGeFELD0OdGMrH9XRP0E/6Q5XdRBecH54
JA6Tj4vXI2SgOX4MWR47FFI7agBgATiOLLYGVcRqzf6rK/QFbOnT1R1JAR4=
-----END CERTIFICATE-----
```

apiserver地址和token

```
[root@VM_1_4_centos ~]# cat .kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJNDQWJD....
    server: https://169.xx.xx.xx:60002
  name: local
contexts:
- context:
    cluster: local
    user: admin
  name: master
current-context: master
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: MUrM6Aj4qwgKKjwF....
```

将上述获取的填入集群配置

![upload-image](/files/-MN2J7ijIKgEE1SlwLoO)

其他配置如下图所示，我们jenkins的master采用的hosts模式，所以这边直接用节点的8080端口

![upload-image](/files/-MN2J7ikMMCojZduGbgd)

slave 的 pod的配置如下

![upload-image](/files/-MN2J7ilOp3VeQIE_vJp)

![upload-image](/files/-MN2J7imJrTYXT8QCARw)

## 创建测试项目进行构建

![upload-image](/files/-MN2J7inwbhyrgNWbIbS)

![upload-image](/files/-MN2J7ioLnowQv3ZwoSW)

```
echo "测试 Kubernetes 动态生成 jenkins slave"
echo "==============docker in docker==========="
docker info

echo "=============kubectl============="
kubectl get pods
```

## 执行任务构建

执行构建会自动生成一个slave pod，执行任务结束后会自动删除掉

![upload-image](/files/-MN2J7ip1TA9X8H_7AmM)

```
[root@VM_1_4_centos ~]# kubectl get pod -n jenkins
NAME                        READY   STATUS    RESTARTS   AGE
jenkins2-574678b486-8r4qz   1/1     Running   0          2d5h
jnlp-7zv3k                  1/1     Running   0          14s
```

![upload-image](/files/-MN2J7iqt09BT7-uNwhe)

![upload-image](/files/-MN2J7irReo82oqyx8Fe)

## 参考文档

<https://www.qikqiak.com/post/kubernetes-jenkins1/>

<https://cloud.tencent.com/document/product/457/41396>


# Kubernetes之RBAC权限管理

本篇文章主要介绍了Kubernetes中RBAC权限控制使用。

## RBAC简介

![upload-image](/files/-MN2J8KJzYAv6DXccbln)

* RBAC（Role-Based Access Control）

基于角色的权限控制。通过角色关联用户、角色关联权限的方式间接赋予用户权限。 在 Kubernetes 中，RBAC 是通过 rbac.authorization.k8s.io API Group 实现的，即允许集群管理员通过 Kubernetes API 动态配置策略。

![upload-image](/files/-MN2J8KLR2Qt-gR9FcwG)

* Role 用于定义某个命名空间的角色的权限。
* ClusterRole 用于定义整个集群的角色的权限。
* RoleBinding 将角色中定义的权限赋予一个或者一组用户，针对命名空间执行授权。
* ClusterRoleBinding 将角色中定义的权限赋予一个或者一组用户，针对集群范围内的命名空间执行授权。

在 RBAC API 中，一个角色包含一组相关权限的规则。权限是纯粹累加的（不存在拒绝某操作的规则）。 角色可以用 Role 来定义到某个命名空间上， 或者用 ClusterRole 来定义到整个集群作用域。

## Role

### 对某个类型资源的权限分配

一个Role只可以用来对某一命名空间中的资源赋予访问权限。 下面的Role示例定义到名称为 "default" 的命名空间，可以用来授予对该命名空间中的 Pods 的读取权限：

```
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] # "" 指定核心 API 组
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
```

### 获取某个资源的子资源

"pods" 是有命名空间的资源，而 "log" 是 pods 的子资源。在 RBAC 角色中， 使用"/"分隔资源和子资源。允许一个主体要同时读取 pods 和 pod logs

```
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list"]
```

### 配置某一个具体资源的权限

可以通过resourceNames列表按名称引用资源。 在指定时，可以将请求类型限制资源的单个实例。限制只可以 "get" 和 "update" 的单一configmap，你可以这么写：

```
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: configmap-updater
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-configmap"]
  verbs: ["update", "get"]
```

### Role常用资源权限配置示例

在以下示例中，我们仅截取展示了 rules 对应部分， 允许读取在核心 API 组下的 Pods: rules:

```
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
```

允许读/写在 "extensions" 和 "apps" API 组中的 "deployments" 资源：

```
rules:
- apiGroups: ["extensions", "apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
```

允许读取 "pods" 和读/写 "jobs" :

```
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["batch", "extensions"]
  resources: ["jobs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
```

允许读取名称为 "my-config"的 ConfigMap （需要通过 RoleBinding 绑定带某名字空间中特定的 ConfigMap）：

```
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-config"]
  verbs: ["get"]
```

允许读取在核心组中的 "nodes" 资源（因为 Node 是集群范围的，所以需要 ClusterRole 绑定到 ClusterRoleBinding 才生效）

```
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
```

允许在非资源端点 "/healthz" 和其子路径上发起 "GET" 和 "POST" 请求（必须在 ClusterRole 绑定 ClusterRoleBinding 才生效）

```
rules:
- nonResourceURLs: ["/healthz", "/healthz/*"] # '*' 在 nonResourceURL 中的意思是后缀全局匹配。
  verbs: ["get", "post"]
```

## RoleBinding

### RoleBinding绑定Role

角色绑定（RoleBinding）是将角色中定义的权限赋予一个或者一组用户。 它包含若干主体（用户，组和服务账户）的列表和对这些主体所获得的角色的引用。 可以使用 RoleBinding 在指定的命名空间中执行授权， 或者在集群范围的命名空间使用 ClusterRoleBinding 来执行授权。

一个 RoleBinding 可以引用同一的命名空间中的 Role 。 下面的例子 RoleBinding 将 "pod-reader" 角色授予在 "default" 命名空间中的用户 "jane"； 这样，用户 "jane" 就具有了读取 "default" 命名空间中 pods 的权限。

roleRef 里的内容决定了实际创建绑定的方法。kind 可以是 Role 或 ClusterRole， name 将引用你要指定的 Role 或 ClusterRole 的名称。在下面的例子中，角色绑定使用 roleRef 将用户 "jane" 绑定到前文创建的角色 Role，其名称是 pod-reader。

```
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "jane" 能够读取 "default" 命名空间中的 Pods
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: jane # Name is case sensitive
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role #this must be Role or ClusterRole
  name: pod-reader # 这里的名称必须与你想要绑定的 Role 或 ClusterRole 名称一致
  apiGroup: rbac.authorization.k8s.io
```

### RoleBinding绑定ClusterRole

RoleBinding 也可以引用 ClusterRole，对 ClusterRole 所定义的、位于 RoleBinding 命名空间内的资源授权。 这可以允许管理者在 整个集群中定义一组通用的角色，然后在多个命名空间中重用它们。

例如下面的例子，RoleBinding 指定的是 ClusterRole， "dave" （主体，区分大小写）将只可以读取在"development" 命名空间（ RoleBinding 的命名空间）中的"secrets"。

```
apiVersion: rbac.authorization.k8s.io/v1
# 这个角色绑定允许 "dave" 用户在 "development" 命名空间中有读取 secrets 的权限。 
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: development # 这里只授予 "development" 命名空间的权限。
subjects:
- kind: User
  name: dave # 名称区分大小写
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
```

## ClusterRole

### CluserRole示例

ClusterRole 可以授予的权限和 Role 相同， 但是因为 ClusterRole 属于集群范围，所以它也可以授予以下访问权限：

* 集群范围资源 （比如 nodes）
* 非资源端点（比如 "/healthz"）
* 跨命名空间访问的有名字空间作用域的资源（如 Pods），比如运行命令kubectl get pods --all-namespaces 时需要此能力

下面的 ClusterRole 示例可用来对某特定命名空间下的 Secrets 的读取操作授权， 或者跨所有命名空间执行授权（取决于它是如何绑定的）:

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # 此处的 "namespace" 被省略掉是因为 ClusterRoles 是没有命名空间的。
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]
```

### aggregationRule聚合ClusteRole

从 1.9 开始，集群角色（ClusterRole）可以通过使用aggregationRule的方式并组合其他 ClusterRoles 来创建。 聚合集群角色的权限是由控制器管理的，方法是通过过滤与标签选择器匹配的 ClusterRules，并将其中的权限进行组合

下面是通过创建一个新的、标签同样为rbac.example.com/aggregate-to-monitoring: true的 ClusterRole，新的规则可被添加到 "monitoring" 集群角色中。

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 具体规则由控制器管理器自动填写。
```

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-endpoints
  labels:
    rbac.example.com/aggregate-to-monitoring: "true"
# 这些规则将被添加到 "monitoring" 角色中。
rules:
- apiGroups: [""]
  resources: ["services", "endpoints", "pods"]
  verbs: ["get", "list", "watch"]
```

## ClusterRoleBinding

### ClusterRoleBinding绑定某个组

ClusterRoleBinding可用来在集群级别或对所有命名空间执行授权。 下面的例子允许 "manager" 组中的任何用户读取任意命名空间中 "secrets"。

```
apiVersion: rbac.authorization.k8s.io/v1
# 这个集群角色绑定允许 "manager" 组中的任何用户读取任意命名空间中 "secrets"。
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager # 名称区分大小写
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
```

## Kubernetes中的用户和组

RoleBinding 或者 ClusterRoleBinding 需要绑定角色到 主体。 主体可以是组，用户或者服务账户。

用户是由字符串表示，它们可以是普通的用户名，像 "alice"，或者是 邮件格式 "<bob@example.com>"，或者是数字ID。由 Kubernetes 管理员配置身份认证模块 需要的格式。RBAC 鉴权系统不对格式作任何要求，但是前缀 system: 是 Kubernetes 系统保留的， 所以管理员要确保配置的用户名不能出现上述前缀格式。

用户组信息是 Kubernetes 现在提供的一种身份验证模块，与用户一样，对组的字符串没有格式要求， 只是不能使用保留的前缀 system: 。

K8S中有两种用户(User)

* 服务账号(ServiceAccount)
* 普通用户(User)

ServiceAccount是由K8S管理的，而User通常是在外部管理.

### serviceaccount绑定

1. 为特定应用的服务账户授予角色（最佳实践）

这要求应用在其 pod 规范中指定 serviceAccountName ， 并额外创建服务账号（包括通过 API、应用程序清单、kubectl create serviceaccount 等）。 例如，在命名空间 "my-namespace" 中授予服务账号 "my-sa" 只读权限：

```
kubectl create rolebinding my-sa-view \
  --clusterrole=view \
  --serviceaccount=my-namespace:my-sa \
  --namespace=my-namespace
```

1. 将角色授予某命名空间中的 ”default” 服务账号

如果一个应用没有指定 serviceAccountName，那么它将使用 "default" 服务账号。

**说明**： 不指定 serviceAccountName 的话， "default" 服务账号的权限会授予给命名空间中所有未指定 serviceAccountName 的 Pods。

![upload-image](/files/-MN2J8KORyuSMbzBpG-m)

例如，在命名空间 "my-namespace" 中授予服务账号 "default" 只读权限：

```
kubectl create rolebinding default-view \
  --clusterrole=view \
  --serviceaccount=my-namespace:default \
  --namespace=my-namespace
```

1. 许多附加组件 add-ons 目前在 kube-system 命名空间以 "default" 服务账号运行。 要允许这些附加组件以超级用户权限运行，需要将集群的 cluster-admin 权限授予 kube-system 命名空间中的 "default" 服务账号。

**说明**： 启用这一配置意味着在 kube-system 命名空间中包含以超级用户账号来访问 API 的 Secrets。

```
kubectl create clusterrolebinding add-on-cluster-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:default
```

1. 将角色授予命名空间中所有的服务账号

如果你想要在命名空间中所有的应用都具有某角色，无论它们使用的什么服务账号， 你可以将角色授予该命名空间的服务账号组。

例如，在命名空间 "my-namespace" 中的只读权限授予该命名空间中的所有服务账号：

```
kubectl create rolebinding serviceaccounts-view \
  --clusterrole=view \
  --group=system:serviceaccounts:my-namespace \
  --namespace=my-namespace
```

1. 对集群范围内的所有服务账户授予一个受限角色（不鼓励）

   如果你不想管理每一个命名空间的权限，你可以向所有的服务账号授予集群范围的角色。

   例如，为集群范围的所有服务账号授予跨所有命名空间的只读权限：

```
kubectl create clusterrolebinding serviceaccounts-view \
  --clusterrole=view \
 --group=system:serviceaccounts
```

1. 授予超级用户访问权限给集群范围内的所有服务帐户（强烈不鼓励）

   如果你不关心如何区分权限，你可以将超级用户访问权限授予所有服务账号。

   警告：这将允许所有能够读取 Secrets 和创建 Pods 的用户访问超级用户的私密信息。

```
kubectl create clusterrolebinding serviceaccounts-cluster-admin \
  --clusterrole=cluster-admin \
  --group=system:serviceaccounts
```

### 绑定用户和组示例

下面的示例只是展示 RoleBinding 中 subjects 的部分。

用户的名称为 "<alice@example.com>":

```
subjects:
- kind: User
  name: "alice@example.com"
  apiGroup: rbac.authorization.k8s.io
```

组的名称为 "frontend-admins":

```
subjects:
- kind: Group
  name: "frontend-admins"
  apiGroup: rbac.authorization.k8s.io
```

服务账号在 kube-system 命名空间中:

```
subjects:
- kind: ServiceAccount
  name: default
  namespace: kube-system
```

在名称为 "qa" 命名空间中所有的服务账号:

```
subjects:
- kind: Group
  name: system:serviceaccounts:qa
  apiGroup: rbac.authorization.k8s.io
```

所有的服务账号:

```
subjects:
- kind: Group
  name: system:serviceaccounts
  apiGroup: rbac.authorization.k8s.io
```

所有认证过的用户 （版本 1.5+）:

```
subjects:
- kind: Group
  name: system:authenticated
  apiGroup: rbac.authorization.k8s.io
```

所有未认证的用户 （版本 1.5+）:

```
subjects:
- kind: Group
  name: system:unauthenticated
  apiGroup: rbac.authorization.k8s.io
```

所有用户 （版本 1.5+）:

```
subjects:
- kind: Group
  name: system:authenticated
  apiGroup: rbac.authorization.k8s.io
- kind: Group
  name: system:unauthenticated
  apiGroup: rbac.authorization.k8s.io
```

## k8s系统默认的Roles和RoleBindings

API servers创建一组默认为 ClusterRole 和 ClusterRoleBinding 的对象。 其中许多是以 system: 为前缀的，它表示资源是基础设施 "owned" 的。对于这些资源的修改可能导致集群功能失效。 例如，system:node 是集群角色，它是定义 kubelets 相关的权限，如果这个角色被修改，它将导致 kubelets 无法正常工作。

所有默认的 ClusterRole 和 ClusterRoleBinding 对象都会被标记为 kubernetes.io/bootstrapping=rbac-defaults

![upload-image](/files/-MN2J8KQm_GGvRnXGUiZ)

### 默认ClusterRole自动更新

在每次启动时，API Server 都会更新默认 ClusterRole 所缺少的各种权限，并更新默认 ClusterRoleBinding 所缺少的各个角色绑定主体。 这种自动更新机制允许集群去修复一些特殊的修改。 由于权限和角色绑定主体在新的 Kubernetes 版本中可能发生变化，所以这样的话也能够保证角色和角色绑定始终保持是最新的。

如果要禁止此功能,请将默认ClusterRole以及ClusterRoleBinding的rbac.authorization.kubernetes.io/autoupdate设置成false。

![upload-image](/files/-MN2J8KRmp-5QGptlB9e)

**注意**，缺乏默认权限和角色绑定主体可能会导致非功能性集群问题。

自动更新功能在 Kubernetes 版本1.6+ 的 RBAC 认证是默认开启的。

### Discovery Roles

无论是经过身份验证的还是未经过身份验证的用户，默认角色的用户读取API被认为是安全的，可以公开访问（包括CustomResourceDefinitions）， 如果要禁用匿名未经过身份验证的用户访问，请在 API server 中添加 --anonymous-auth=false 的配置选项。

通过运行命令 kubectl 可以查看这些角色的配置信息:

```
kubectl get clusterroles system:discovery -o yaml
```

**注意**：不建议编辑这个角色，因为更改将在 API server 重启时自动更新时覆盖（见上文）

| 默认 ClusterRole            | 默认 ClusterRoleBinding                           | 描述                                                                                        |
| ------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| system:basic-user         | system:authenticated 组                          | 允许用户以只读的方式去访问他们自己的基本信息。在1.14版本之前，这个角色在默认情况下也绑定在 `system:unauthenticated` 上。               |
| system:discovery          | system:authenticated 组                          | 允许以只读方式访问 API 发现端点，这些端点用来发现和协商 API 级别。在1.14版本之前，这个角色在默认情况下绑定在 `system:unauthenticated` 上。 |
| system:public-info-viewer | system:authenticated 和 system:unauthenticated 组 | 允许对集群的非敏感信息进行只读访问，它是在1.14版本中引入的。                                                          |

### 面向用户的角色

一些默认的角色不是前缀 system: 开头的。这些是面向用户的角色。它们包括 super-user 角色（cluster-admin）， 使用 ClusterRoleBindings （cluster-status）在集群范围内授予角色， 以及使用 RoleBindings （admin, edit, view）在特定命名空间中授予的角色。

在 1.9 开始，面向用户的角色使用ClusterRole Aggregation允许管理员在包含这些角色上的 自定义资源上添加规则。如果想要添加 "admin" "edit" 或者 "view" ，需要先创建使用以下一个或多个的 ClusterRole 的标签：

```
metadata:
  labels:
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
    rbac.authorization.k8s.io/aggregate-to-view: "true"
```

| 默认 ClusterRole | 默认 ClusterRoleBinding | 描述                                                                                                                                           |
| -------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| cluster-admin  | system:masters 组      | 允许超级用户在平台上的任何资源的所有操作。 当在 ClusterRoleBinding 中使用时，可以授权对集群中以及所有命名空间中的全部资源进行完全控制。 当在 RoleBinding 中使用时，可以授权控制 RoleBinding 所在命名空间中的所有资源，包括命名空间本身。 |
| admin          | 无                     | 允许管理员访问权限，旨在使用 RoleBinding 在命名空间内执行授权。 如果在 RoleBinding 中使用，则可授予对命名空间中的大多数资源的读/写权限， 包括创建角色和绑定角色（RoleBinding）的能力。 但是它不允许对资源配额或者命名空间本身进行写操作。    |
| edit           | 无                     | 允许对命名空间的大多数对象进行读/写操作。 它不允许查看或者修改角色（Roles）或者角色绑定（RoleBindings）。                                                                               |
| view           | 无                     | 允许对命名空间的大多数对象有只读权限。 它不允许查看角色（Roles）或角色绑定（RoleBindings）。 它不允许查看 Secrets，因为这类操作属于越权。                                                           |

### 核心组件角色

| 默认 ClusterRole                 | 默认 ClusterRoleBinding             | 描述                                                                                                                                                                                                                                                                                    |
| ------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| system:kube-scheduler          | system:kube-scheduler 用户          | 允许访问 kube-scheduler 组件所需要的资源。                                                                                                                                                                                                                                                         |
| system:volume-scheduler        | system:kube-scheduler 用户          | 允许访问 kube-scheduler 组件所需要的的卷资源。                                                                                                                                                                                                                                                       |
| system:kube-controller-manager | system:kube-controller-manager 用户 | 允许访问 kube-controller-manager 组件所需要的资源。 各个控制环所需要的权限包含在 controller roles 之中。                                                                                                                                                                                                            |
| system:node                    | 在版本1.8之后无                         | 允许访问 kubelet 组件所需要的资源，它包括读取所有的 Secrets 和对所有 Pod 状态对象的写操作。从版本 1.7 开始，推荐使用 Node authorizer 和 NodeRestriction 准入插件 来代替这个角色，它允许基于 kubelet 上调度执行的 Pods 来授权对 kubelet API 的访问。 在版本 1.7 之前，这个角色会自动绑定到 system:nodes 组。 在版本 1.7中，如果未启用Node 鉴权模式，这个角色将自动绑定到 system:nodes 组 在版本 1.8+ 之后，不再自动创建绑定。 |
| system:node-proxier            | system:kube-proxy 用户              | 允许访问 kube-proxy 组件所需要的资源。                                                                                                                                                                                                                                                             |

### 其他组件角色

| 默认 ClusterRole                       | 默认 ClusterRoleBinding          | 描述                                            |
| ------------------------------------ | ------------------------------ | --------------------------------------------- |
| system:auth-delegator                | 无                              | 允许代理身份认证和鉴权， 它通常用在插件式 API 服务器上，以实现统一的身份认证和鉴权。 |
| system:heapster                      | 无                              | 为 Heapster 组件定义的角色。                           |
| system:kube-aggregator               | 无                              | 为 kube-aggregator 组件定义的角色。                    |
| system:kube-dns                      | 在kube-system命名空间中的kube-dns服务账号 | 为 kube-dns 组件定义的角色。                           |
| system:kubelet-api-admin             | 无                              | 允许完全访问 kubelet API 。                          |
| system:node-bootstrapper             | 无                              | 允许访问执行 Kubelet TLS 启动引导 所需要的资源。               |
| system:node-problem-detector         | 无                              | 为 node-problem-detector 组件定义的角色。              |
| system:persistent-volume-provisioner | 无                              | 允许访问大部分的 动态卷驱动 所需要的资源。                        |

### 控制器角色

Kubernetes 控制器管理器 运行核心控制环。 当使用 --use-service-account-credentials 参数时, 每个控制环使用一个单独的服务账号启动。 每个控制环都有相应的、前缀为 system:controller: 的角色。 如果控制管理器启动时未设置 --use-service-account-credentials， 它使用自己的身份信息来运行所有的控制环，该身份必须被授予所有相关的角色。 这些角色包括:

![upload-image](/files/-MN2J8KhLAeER98apJx8)

```
system:controller:attachdetach-controller
system:controller:certificate-controller
system:controller:clusterrole-aggregation-controller
system:controller:cronjob-controller
system:controller:daemon-set-controller
system:controller:deployment-controller
system:controller:disruption-controller
system:controller:endpoint-controller
system:controller:expand-controller
system:controller:generic-garbage-collector
system:controller:horizontal-pod-autoscaler
system:controller:job-controller
system:controller:namespace-controller
system:controller:node-controller
system:controller:persistent-volume-binder
system:controller:pod-garbage-collector
system:controller:pv-protection-controller
system:controller:pvc-protection-controller
system:controller:replicaset-controller
system:controller:replication-controller
system:controller:resourcequota-controller
system:controller:root-ca-cert-publisher
system:controller:route-controller
system:controller:service-account-controller
system:controller:service-controller
system:controller:statefulset-controller
system:controller:ttl-controller
```

### 初始化第一个角色和角色绑定

1. 授予他们一个角色，允许他们根据需要创建/更新RoleBinding或者ClusterRoleBinding对象。
2. 授予他们绑定特定角色所需的权限: *隐式地，通过给他们授予角色中包含的权限。* 显式地，通过允许他们对特定角色（或集群角色）执行bind操作的权限

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: role-grantor
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["rolebindings"]
  verbs: ["create"]
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["clusterroles"]
  verbs: ["bind"]
  resourceNames: ["admin","edit","view"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: role-grantor-binding
  namespace: user-1-namespace
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: role-grantor
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user-1
```

当初始化第一个角色和角色绑定时，需要为初始用户授予他们尚未拥有的权限。 对初始角色和角色绑定进行初始化时需要：

* 使用用户组为 system:masters 的凭据，该用户组由默认绑定关联到 cluster-admin 这个超级用户角色。
* 如果你的 API server 启动时启用了不安全端口（使用--insecure-port）, 你也可以通过该端口调用 API ，这样操作会绕过身份验证或鉴权。

## RBAC常用的命令

### kubectl create role

创建 Role 对象，定义在某命名空间中的权限。例如:

* 创建名称为 "pod-reader" 的 Role 对象，允许用户对 pods 执行 "get"、"watch" 和 "list" 操作：

```
kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
```

* 创建名称为 "pod-reader" 的 Role 对象并指定 resourceNames：

```
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
```

* 创建名为 "foo" 的 Role 对象并指定 apiGroups:

```
kubectl create role foo --verb=get,list,watch --resource=replicasets.apps
```

* 创建名为 "foo" 的 Role 对象并指定子资源权限:

```
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status
```

* 创建名为 "my-component-lease-holder" 的 Role 对象，使其具有对特定名称资源执行 get/update 的权限：

```
kubectl create role my-component-lease-holder --verb=get,list,watch,update --resource=lease --resource-name=my-component
```

### kubectl create clusterrole

创建 ClusterRole 对象。例如：

* 创建名称为 "pod-reader" 的 ClusterRole 对象，允许用户对 pods 对象执行 "get"、"watch" 和 "list" 操作：

```
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
```

* 创建名为 "pod-reader" 的 ClusterRole 对象并指定资源名称：

```
kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
```

* 创建名为 "foo" 的 ClusterRole 对象并指定 apiGroups：

```
kubectl create clusterrole foo --verb=get,list,watch --resource=replicasets.apps
```

* 创建名为 "foo" 的ClusterRole 对象并指定子资源:

```
kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
```

* 创建名为 "foo" 的 ClusterRole 对象并指定非资源路径：

```
kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*
```

* 创建名为 "monitoring" 的 ClusterRole 对象并指定聚合规则：

```
kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"
```

### kubectl create rolebinding

在特定的命名空间中对 Role 或 ClusterRole 授权。例如：

* 在命名空间 "acme" 中，将名为 admin 的 ClusterRole 中的权限授予名称 "bob" 的用户:

```
kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=bob --namespace=acme
```

* 在命名空间 "acme"中，将名为 view 的 ClusterRole 中的权限授予该命名空间 "acme" 中名为 "myapp" 的服务账号：

```
kubectl create rolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp --namespace=acme
```

* 在命名空间 "acme" 中，将名为 view 的 ClusterRole 对象中的权限授予命名空间 "myappnamespace" 中名称为 "myapp" 的服务账号：

```
kubectl create rolebinding myappnamespace-myapp-view-binding --clusterrole=view --serviceaccount=myappnamespace:myapp --namespace=acme
```

### kubectl create clusterrolebinding

在整个集群、包括所有的命名空间中对 ClusterRole 授权。例如：

* 在整个集群范围，将名为 cluster-admin 的 ClusterRole 中定义的权限授予名为 "root" 用户：

```
kubectl create clusterrolebinding root-cluster-admin-binding --clusterrole=cluster-admin --user=root
```

* 在整个集群范围，将名为 system:node-proxier 的 ClusterRole 的权限授予名为 "system:kube-proxy" 的用户：

```
kubectl create clusterrolebinding kube-proxy-binding --clusterrole=system:node-proxier --user=system:kube-proxy
```

* 在整个集群范围，将名为 view 的 ClusterRole 对象中定义的权限授予 "acme" 命名空间中名为 "myapp" 的服务账号：

```
kubectl create clusterrolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp
```

### kubectl auth reconcile

使用清单文件来创建或者更新 rbac.authorization.k8s.io/v1 API 对象。

尚不存在的对象会被创建，如果对应的命名空间也不存在，必要的话也会被创建。 已经存在的角色会被更新，使之包含输入对象中所给的权限。如果指定了 --remove-extra-permissions，可以删除其余权限。

已经存在的绑定也会被更新，使之包含输入对象中所给的主体。如果指定了 --remove-extra-permissions，则可以删除其余主体。

例如:

* 测试应用 RBAC 对象的清单文件，显示将要进行的更改：

```
kubectl auth reconcile -f my-rbac-rules.yaml --dry-run
```

* 应用 RBAC 对象的清单文件， 保留角色中的其余权限和绑定中的其他主体：

```
kubectl auth reconcile -f my-rbac-rules.yaml
```

* 应用 RBAC 对象的清单文件, 删除角色中的其他权限和绑定中的其他主体：

```
kubectl auth reconcile -f my-rbac-rules.yaml --remove-extra-subjects --remove-extra-permissions
```

查看 CLI 帮助获取详细的用法。

## RBAC使用注意事项

### 角色和角色绑定的更新

你不能修改绑定对象所引用的 Role 或 ClusterRole 。 试图改变绑定对象的 roleRef 将导致验证错误。想要 改变现有绑定对象中 roleRef 字段的内容，必须删除并 重新创建绑定对象。这种限制有两个主要原因：

* 关于不同角色的绑定是完全不一样的。更改 roleRef 需要删除/重建绑定，确保要赋予绑定的完整主体列表是新 的角色（而不是只是启用修改 roleRef 在不验证所有现有 主体的情况下的，应该授予新角色对应的权限）。
* 使得 roleRef 不可以改变现有绑定主体用户的 update 权限， 这样可以让它们能够管理主体列表，而不能更改授予这些主体相关 的角色。

命令kubectl auth reconcile可以创建或者更新包含 RBAC 对象的清单文件， 并且在必要的情况下删除和重新创建绑定对象，以改变所引用的角色

## RBAC在TKE中的应用

### 简介

TKE 提供了对接 Kubernetes RBAC 的授权模式，便于对子账号进行细粒度的访问权限控制。该授权模式下，可通过容器服务控制台及 kubectl 两种方式进行集群内资源访问。如下图所示：

![upload-image](/files/-MN2J8Kn9MWbLdJ7lLrm)

### TKE Kubernetes 对象级别权限控制方案

Kubernetes APIServer 支持丰富多样的认证策略，例如 x509 证书、bearer token、basic auth。其中，仅 bearer token 单个认证策略支持指定 known-token csv 文件的 beaer token、serviceaccount token、OIDC token、webhook token server 等多种 token 认证方式。

TKE 分析了实现复杂性及多种场景等因素，选择使用 x509 证书认证方式。其优势如下：

* 用户理解成本低。
* 对于存量集群无需进行复杂变更 。
* 按照 User 及 Group 进行划分，后续扩展性好。

TKE 基于 x509 证书认证实现了以下功能：

* 每个子账号单独具备客户端证书，用于访问 Kubernetes APIServer。
* 当子账号在控制台访问 Kubernetes 资源时，后台默认使用该子账号的客户端证书去访问用户 Kubernetes APIServer。
* 支持子账号更新独有的客户端证书，防止凭证泄露。
* 支持主账号或使用集群 tke:admin 权限的账号进行查看、更新其他子账号的证书。

![upload-image](/files/-MN2J8KoGL-l2EaElNam)

### 存量集群授权模式升级操作

对于存量的集群，如果开启RBAC模式，那么会给腾讯云下的所有账号创建一个clusterrolebinding，并且会绑定tke:admin这个clusterole，所以如果你是存量集群，如果需要回收某些子账号的权限，那么需要自己手动操作收回权限，否则默认所有子账号都有admin权限

**升级授权模式**

若使用旧授权模式的集群需要升级时，请参考以下操作步骤进行升级：

1. 登录容器服务控制台，选择左侧导航栏中的【集群】。
2. 在“集群管理”页面中，选择需升级的集群 ID。
3. 在集群详情页面中，选择左侧【授权管理】>【ClusterRole】。
4. 在 “ClusterRole” 管理页面中，单击【RBAC策略生成器】。
5. 在弹出的“切换权限管理模式”窗口中，单击【切换权限管理模式】即可进行授权模式升级。如下图所示：

![upload-image](/files/-MN2J8KqS-njw7T6FY11)

为确保新旧模式的兼容性，升级过程中会进行如下操作：

1. 创建默认预设管理员 ClusterRole：tke:admin。
2. 拉取子账号列表。
3. 为每个子账号生成可用于 Kubernetes APIServer 认证的 x509 客户端证书。
4. 为每个子账号都绑定 tke:admin 角色（确保和存量功能兼容）。
5. 升级完毕。

回收子账号权限

集群授权模式升级完毕后，集群管理员（通常为主账号管理员或创建集群的运维人员）可按需对具有该集群权限的子账号进行权限回收操作，步骤如下：

1. 选择集群【授权管理】下的菜单项，在对应的管理页面中单击【RBAC策略生成器】。
2. 在“管理权限”页面的“选择子账号”步骤中，勾选需回收权限的子账号并单击【下一步】。如下图所示：

![upload-image](/files/-MN2J8Kshr1OfM5qh4U9)

* 在“集群RBAC”步骤中，设置权限。例如，“权限设置”选择为命名空间 “default” 下的“只读用户”。如下图所示：

![upload-image](/files/-MN2J8KtTWZR2vAKYt1L)

单击【完成】即可完成回收操作。

**确认子账号权限**

当完成子账号回收操作后，您可通过以下步骤进行确认：

1. 选择左侧的【授权管理】>【ClusterRoleBinding】，进入 “ClusterRoleBinding” 管理页面。
2. 选择被回收权限的子账号名称，进入 YAML 文档页面。子账号默认为 tke:admin 权限，回收对应权限后，可在 YAML 文件中查看变更。如下图所示：

![upload-image](/files/-MN2J8Ku-jCJqbc2zNC1)

### 新建集群身份权限预设

容器服务控制台提供授权管理页，默认主账号及集群创建者具备管理员权限。可对其他拥有该集群 DescribeCluster Action 权限的子账号进行权限管理。如下图所示：

![upload-image](/files/-MN2J8KvsKU-f587weuL)

ClusterRole 说明

* 所有命名空间维度：
  * 管理员（tke:admin）：对所有命名空间下资源的读写权限，具备集群节点、存储卷、命名空间、配额的读写权限，可配置子账号的读写权限。
  * 运维人员（tke:ops）：对所有命名空间下控制台可见资源的读写权限，具备集群节点、存储卷、命名空间、配额的读写权限。
  * 开发人员（tke:dev）：对所有命名空间下控制台可见资源的读写权限。
  * 受限人员（tke:ro）：对所有命名空间下控制台可见资源的只读权限。
  * 自定义：用户自定义 ClusterRole。
* 指定命名空间维度：
  * 开发人员（tke:ns:dev）： 对所选命名空间下控制台可见资源的读写权限， 需要选择指定命名空间。
  * 只读用户（tke:ns:ro）：对所选命名空间下控制台可见资源的只读权限， 需要选择指定命名空间。
* 所有预设的 ClusterRole 都将带有固定 label：cloud.tencent.com/tke-rbac-generated: "true"。
* 所有预设的 ClusterRoleBinding 都带有固定的 annotations：cloud.tencent.com/tke-account-nickname: yournickname 及 label：cloud.tencent.com/tke-account: "yourUIN"。

操作步骤

获取凭证

容器服务默认会为每个子账号创建独立的凭证，用户只需访问集群详情页或调用云 API 接口 DescribeClusterKubeconfig，即可获取当前使用账号的凭证信息 Kubeconfig 文件。通过控制台获取步骤如下：

* 登录容器服务控制台，选择左侧导航栏中的【集群】。
* 在“集群管理”页面中，选择需目标集群 ID。
* 在集群详情页面中，选择左侧的【基本信息】即可在“集群APIServer信息”模块中查看并下载 Kubeconfig 文件。如下图所示：

![upload-image](/files/-MN2J8KwgQbG1-6a_fLg)

凭证管理

集群管理员可以访问凭证管理页，进行查看并更新所有账号下集群的凭证。详情请参见 更新子账号的 TKE 集群访问凭证。

授权

**说明**：请联系集群管理员（主账号、集群创建者或拥有 admin role 的用户）进行授权。

1. 在“集群管理”页面中，选择目标集群 ID。
2. 在集群详情页面中，选择左侧【授权管理】>【ClusterRoleBinding】。
3. 在 “ClusterRoleBinding” 管理页面中，单击【RBAC策略生成器】。如下图所示：

![upload-image](/files/-MN2J8Kx5oObqQ7Ee9jg)

1. 在“管理权限”页面的“选择子账号”步骤中，勾选需授权的子账号并单击【下一步】。
2. 在“集群RBAC设置”步骤中，按照以下指引进项权限设置：
   * Namespace列表：按需指定权限生效的 Namespace 范围。
   * 权限：请参考界面中的“权限说明”，按需设置权限。说明：您还可以单击【添加权限】，继续进行权限自定义设置。

鉴权

登录子账号，确认该账号已获得所授权限，则表示授权成功。

### 自定义策略授权

* Role：命名空间维度

```
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: testRole
  namespaces: default
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - create
  - delete
  - deletecollection
  - get
  - list
  - patch
  - update
  - watch
```

* ClusterRole：集群维度

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: testClusterRole
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - create
  - delete
  - deletecollection
  - get
  - list
  - patch
  - update
  - watch
```

操作步骤

**说明**：该步骤以为子账号绑定自定义 ClusterRole 为例，与绑定 Role 的步骤基本一致，您可结合实际需求进行操作。

1. 登录容器服务控制台，选择左侧导航栏中的【集群】。
2. 在“集群管理”页面中，选择需升级的集群 ID。
3. 在集群详情页面中，选择左侧【授权管理】>【ClusterRole】。如下图所示：

![upload-image](/files/-MN2J8Kz3730z-06EhPQ)

1. 在 “ClusterRole” 管理页面中，选择右上角的【YAML创建资源】。
2. 在编辑界面输入自定义策略的 YAML 内容，单击【完成】即可创建 ClusterRole。该步骤以 ClusterRole：集群维度 YAML 为例，创建完成后，可在 “ClusterRole” 管理页面中查看自定义权限 “testClusterRole”。
3. 在 “ClusterRoleBinding” 管理页面中，单击【RBAC策略生成器】。
4. 在“管理权限”页面的“选择子账号”步骤中，勾选需授权的子账号并单击【下一步】。如下图所示：

![upload-image](/files/-MN2J8L-Kzl6FS1c6BrN)

1. 进入“集群RBAC设置”界面，按照以下指引进项权限设置。如下图所示：

![upload-image](/files/-MN2J8L0VfbFm8xztrVB)

* Namespace列表：按需指定权限生效的 Namespace 范围。
* 权限：选择“自定义”，并单击【选择自定义权限】。按需在自定义权限列表中进行权限选择，本文以选择已创建的自定义权限 “testClusterRole” 为例。说明： 您还可以单击【添加权限】，继续进行权限自定义设置。
* 单击【完成】即可完成授权操作。

### 更新及获取子账号访问凭证

腾讯云容器服务 TKE 基于 x509 证书认证实现了以下功能：

* 每个子账号均单独具备客户端证书，用于访问 Kubernetes APIServer。
* 在 TKE 新授权模式下，不同子账号在获取集群访问凭证时，即访问集群基本信息页面或调用云 API 接口 DescribeClusterKubeconfig 时，将会获取到子账户独有的 x509 客户端证书，该证书是使用每个集群的自签名 CA 进行签发的。
* 当子账号在控制台访问 Kubernetes 资源时，后台默认使用该子账号的客户端证书去访问用户 Kubernetes APIServer。
* 支持子账号更新独有的客户端证书，防止凭证泄露。
* 支持主账号或使用集群 tke:admin 权限的账号进行查看、更新其他子账号的证书。

操作步骤

* 登录容器服务控制台，选择左侧导航栏中的【集群】。
* 在“集群管理”页面中，选择需目标集群 ID。
* 在集群详情页面中，选择左侧的【基本信息】，在“集群APIServer信息”模块中单击【Kubeconfig权限管理】。
* 在弹出的 “Kubeconfig权限管理” 窗口中，按需勾选认证账号并单击【更新】即可。如下图所示：

![upload-image](/files/-MN2J8L1PJ98eoJ96hIa)

## 参考文档

<https://blog.csdn.net/ywq935/article/details/84840935>

<https://kubernetes.io/zh/docs/reference/access-authn-authz/rbac/>


# Kubernetes之调度篇

我们在日常使用k8s的过程中总会存在一些特殊的调度情景：

* 让master节点上不部署业务的pod
* 让mysql调度到高IO的节点上
* 让coredns的服务均匀的散布在每个节点
* 让内服服务调度在一个节点上，减少访问延迟

这边肯定会有其他场景也会有对pod的调度有特殊要求，这边只是列举了其中几个情况，对于上述遇到的情况我们需要怎么处理，其实k8s给我们提供了丰富的调度策略来满足我们的需求。下面我们来一一说下这些调度策略。

## nodeSelector

你可以约束一个Pod只能在特定的Node(s)上运行，或者优先运行在特定的节点上。有几种方法可以实现这点，推荐的方法都是用标签选择器来进行选择。通常这样的约束不是必须的，因为调度器将自动进行合理的放置（比如，将 pod 分散到节点上，而不是将 pod 放置在可用资源不足的节点上等等），但在某些情况下，你可以需要更多控制 pod 停靠的节点，例如，确保 pod 最终落在连接了 SSD 的机器上，或者将来自两个不同的服务且有大量通信的 pod 放置在同一个可用区

nodeSelector是节点选择约束的最简单推荐形式。nodeSelector是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行，该节点的标签中必须包含这里的每个键值对（它也可以具有其他标签）。最常见的用法的是一对键值对。

### 添加标签到节点

执行 kubectl get nodes 命令获取集群的节点名称。 选择一个你要增加标签的节点，然后执行命令将标签添加到你所选择的节点上。

```
kubectl label nodes <node-name> <label-key>=<label-value>
```

例如，如果你的节点名称为 'kubernetes-foo-node-1.c.a-robinson.internal' 并且想要的标签是 'disktype=ssd'，则可以执行命令。

```
kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd
```

你可以通过重新运行 kubectl get nodes --show-labels，查看节点当前具有了所指定的标签来验证它是否有效。 你也可以使用 命令查看指定节点的标签完整列表

```
[root@VM_0_13_centos prometheus]# kubectl get nodes --show-labels | grep ssd
10.168.1.4      Ready    <none>   48d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-4qz8yvgq,disktype=ssd,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.1.4,kubernetes.io/os=linux
[root@VM_0_13_centos prometheus]# kubectl describe node 10.168.1.4
Name:               10.168.1.4
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=QCLOUD
                    beta.kubernetes.io/os=linux
                    cloud.tencent.com/node-instance-id=ins-4qz8yvgq
                    disktype=ssd
                    failure-domain.beta.kubernetes.io/region=gz
                    failure-domain.beta.kubernetes.io/zone=100003
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=10.168.1.4
                    kubernetes.io/os=linux
```

### 添加 nodeSelector 字段到 Pod 配置中

选择任何一个你想运行的 Pod 的配置文件，并且在其中添加一个 nodeSelector 部分。 例如，如果下面是我的 pod 配置

```
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd
```

执行上述yaml可以发现pod已经到到对应的ssd节点了

![upload-image](/files/-MN2J93zV1TqmFNUOy5x)

### k8s默认给节点打的标签

```
kubernetes.io/hostname
failure-domain.beta.kubernetes.io/zone
failure-domain.beta.kubernetes.io/region
topology.kubernetes.io/zone
topology.kubernetes.io/region
beta.kubernetes.io/instance-type
node.kubernetes.io/instance-type
kubernetes.io/os
kubernetes.io/arch
```

## 节点的亲和性和反亲和性

nodeSelector 提供了一种非常简单的方法来将 pod 约束到具有特定标签的节点上。亲和/反亲和功能极大地扩展了你可以表达约束的类型。关键的增强点是

* 语言更具表现力（不仅仅是“完全匹配的 AND”）
* 你可以发现规则是“软”/“偏好”，而不是硬性要求，因此，如果调度器无法满足该要求，仍然调度该 pod
* 你可以使用节点上（或其他拓扑域中）的 pod 的标签来约束，而不是使用节点本身的标签，来允许哪些 pod 可以或者不可以被放置在一起。

亲和功能包含两种类型的亲和，即“节点亲和”和“pod 间亲和/反亲和”。节点亲和就像现有的 nodeSelector（但具有上面列出的前两个好处），然而 pod 间亲和/反亲和约束 pod 标签而不是节点标签（在上面列出的第三项中描述，除了具有上面列出的第一和第二属性）

亲和性调度可以分成软策略和硬策略两种方式:

* 软策略就是如果你没有满足调度要求的节点的话，pod 就会忽略这条规则，继续完成调度过程，说白了就是满足条件最好了，没有的话也无所谓了的策略
* 硬策略就比较强硬了，如果没有满足条件的节点的话，就不断重试直到满足条件为止，简单说就是你必须满足我的要求，不然我就不干的策略。

对于亲和性和反亲和性都有这两种规则可以设置： preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution，前面的就是软策略，后面的就是硬策略。

目前有两种类型的节点亲和，分别为 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution。你可以视它们为“硬”和“软”，意思是，前者指定了将 pod 调度到一个节点上必须满足的规则（就像 nodeSelector 但使用更具表现力的语法），后者指定调度器将尝试执行但不能保证的偏好。名称的“IgnoredDuringExecution”部分意味着，类似于 nodeSelector 的工作原理，如果节点的标签在运行时发生变更，从而不再满足 pod 上的亲和规则，那么 pod 将仍然继续在该节点上运行。

```
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0
```

此节点亲和规则表示，pod 只能放置在具有标签键为 kubernetes.io/e2e-az-name 且 标签值为 e2e-az1 或 e2e-az2 的节点上。另外，在满足这些标准的节点中，具有标签键为 another-node-label-key 且标签值为 another-node-label-value 的节点应该优先使用。这里的匹配逻辑是 label 的值在某个列表中，现在Kubernetes提供的操作符有下面的几种：

* In：label 的值在某个列表中
* NotIn：label 的值不在某个列表中
* Gt：label 的值大于某个值
* Lt：label 的值小于某个值
* Exists：某个 label 存在
* DoesNotExist：某个 label 不存在

如果nodeSelectorTerms下面有多个选项的话，满足任何一个条件就可以了；如果matchExpressions有多个选项的话，则必须同时满足这些条件才能正常调度 POD。

preferredDuringSchedulingIgnoredDuringExecution 中的 weight 字段值的范围是 1-100。对于每个符合所有调度要求（资源请求，RequiredDuringScheduling 亲和表达式等）的节点，调度器将遍历该字段的元素来计算总和，并且如果节点匹配对应的MatchExpressions，则添加“权重”到总和。然后将这个评分与该节点的其他优先级函数的评分进行组合。总分最高的节点是最优选

## pod的亲和性和反亲和性

pod 间亲和与反亲和使你可以基于已经在节点上运行的 pod 的标签来约束 pod 可以调度到的节点，而不是基于节点上的标签。规则的格式为“如果 X 节点上已经运行了一个或多个 满足规则 Y 的pod，则这个 pod 应该（或者在非亲和的情况下不应该）运行在 X 节点”。Y 表示一个具有可选的关联命令空间列表的 LabelSelector；与节点不同，因为 pod 是命名空间限定的（因此 pod 上的标签也是命名空间限定的），因此作用于 pod 标签的标签选择器必须指定选择器应用在哪个命名空间。从概念上讲，X 是一个拓扑域，如节点，机架，云供应商地区，云供应商区域等。你可以使用 topologyKey 来表示它，topologyKey 是节点标签的键以便系统用来表示这样的拓扑域。这里拓扑域可以从默认的标签中选择

pod 亲和性主要解决 pod 可以和哪些 pod 部署在同一个拓扑域中的问题（其中拓扑域用主机标签实现，可以是单个主机，也可以是多个主机组成的 cluster、zone 等等），而 pod 反亲和性主要是解决 pod 不能和哪些 pod 部署在同一个拓扑域中的问题，它们都是处理的 pod 与 pod 之间的关系，比如一个 pod 在一个节点上了，那么我这个也得在这个节点，或者你这个 pod 在节点上了，那么我就不想和你待在同一个节点上。

由于我们这里只有一个集群，并没有区域或者机房的概念，所以我们这里直接使用主机名来作为拓扑域，把 pod 创建在同一个主机上面。

```
[root@VM_0_13_centos prometheus]# kubectl get nodes --show-labels
NAME            STATUS   ROLES    AGE   VERSION          LABELS
10.168.1.4      Ready    <none>   48d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-4qz8yvgq,disktype=ssd,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.1.4,kubernetes.io/os=linux
10.168.1.5      Ready    <none>   17d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-ft3pcr72,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.1.5,kubernetes.io/os=linux
10.168.100.22   Ready    <none>   18d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-iomm694e,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.100.22,kubernetes.io/os=linux
```

与节点亲和一样，当前有两种类型的 pod 亲和与反亲和，即 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution，分表表示“硬性”与“软性”要求。请参阅前面节点亲和部分中的描述。requiredDuringSchedulingIgnoredDuringExecution 亲和的一个示例是“将服务 A 和服务 B 的 pod 放置在同一区域，因为它们之间进行大量交流”，而 preferredDuringSchedulingIgnoredDuringExecution 反亲和的示例将是“将此服务的 pod 跨区域分布”（硬性要求是说不通的，因为你可能拥有的 pod 数多于区域数）。

Pod 间亲和通过 PodSpec 中 affinity 字段下的 podAffinity 字段进行指定。而 pod 间反亲和通过 PodSpec 中 affinity 字段下的 podAntiAffinity 字段进行指定

```
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: failure-domain.beta.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0
```

### podAffinity

下面我来实践下pod的亲和性，下面这个例子中的 pod 需要调度到某个指定的主机上，至少有一个节点上运行了这样的 pod：这个 pod 有一个app=redis的 label。

```
[root@VM_0_13_centos ~]# kubectl get pod --show-labels -o wide | grep app=redis
redis-866f79579-2qdfp                  1/1     Running   0          16d   172.16.3.79    10.168.1.4      <none>           <none>            app=redis,pod-template-hash=866f79579
```

测试pod亲和性的yaml文件

```
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - redis
            topologyKey: kubernetes.io/hostname
```

我们看到这个 pod 运行在了10.168.1.4的节点上面，所以按照上面的亲和性来说，上面我们部署的3个 pod 副本也应该运行在 10.168.1.4节点上：

```
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep affinity
affinity-547c75bd94-hkx47              1/1     Running   0          19s   172.16.3.91    10.168.1.4      <none>           <none>
affinity-547c75bd94-tg22d              1/1     Running   0          19s   172.16.3.89    10.168.1.4      <none>           <none>
affinity-547c75bd94-vfm7f              1/1     Running   0          19s   172.16.3.90    10.168.1.4      <none>           <none>
```

下面我们把redis的标签改下，然后重建下deloyment看下，看下会发生什么情况

```
[root@VM_0_13_centos ~]# kubectl get pod --show-labels -o wide | grep app=redis
redis-5b4495ddb4-szjtz                 1/1     Running   0          18s     172.16.3.92    10.168.1.4      <none>           <none>            k8s-app=redis,pod-template-hash=5b4495ddb4,qcloud-app=redis
```

```
[root@VM_0_13_centos ~]# kubectl get pod 
NAME                                   READY   STATUS    RESTARTS   AGE
affinity-547c75bd94-8jcb8              0/1     Pending   0          11s
affinity-547c75bd94-9wv9d              0/1     Pending   0          10s
affinity-547c75bd94-bq8qh              0/1     Pending   0          10s
```

我们可以看到处于Pending状态了，这是因为现在没有一个节点上面拥有busybox-pod这个 label 的 pod，而上面我们的调度使用的是硬策略，所以就没办法进行调度了

我们这个地方使用的是kubernetes.io/hostname这个拓扑域，意思就是我们当前调度的 pod 要和目标的 pod 处于同一个主机上面，因为要处于同一个拓扑域下面，为了说明这个问题，我们把拓扑域改成beta.kubernetes.io/os，同样的我们当前调度的 pod 要和目标的 pod 处于同一个拓扑域中，目标的 pod 是不是拥有beta.kubernetes.io/os=linux的标签，而我们这里3个节点都有这样的标签，这也就意味着我们3个节点都在同一个拓扑域中，所以我们这里的 pod 可能会被调度到任何一个节点：

```
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - redis
            topologyKey: beta.kubernetes.io/os
```

```
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep affinity
affinity-87649f558-6p72d               1/1     Running   0          3m22s   172.16.2.160   10.168.1.5      <none>           <none>
affinity-87649f558-fpcxg               1/1     Running   0          3m22s   172.16.3.94    10.168.1.4      <none>           <none>
affinity-87649f558-h8scw               1/1     Running   0          2m22s   172.16.2.126   10.168.100.22   <none>           <none>
```

### podAntiAffinity

这就是 pod 亲和性的用法，而 pod 反亲和性则是反着来的，比如一个节点上运行了某个 pod，那么我们的 pod 则希望被调度到其他节点上去，同样我们把上面的 podAffinity 直接改成 podAntiAffinity，(pod-antiaffinity-demo.yaml)

```
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - redis
            topologyKey: kubernetes.io/hostname
```

这里的意思就是如果一个节点上面有一个k8s-app=redis这样的 pod 的话，那么我们的 pod 就别调度到这个节点上面来，上面我们k8s-app=redis在10.168.1.4这个节点上，正常来说我们这里的 pod 不会出现在 10.168.1.4节点上

```
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep redis
redis-5b4495ddb4-szjtz                 1/1     Running   0          23m     172.16.3.92    10.168.1.4      <none>           <none>
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep affinity
affinity-6868874dbc-5cc95              1/1     Running   0          2m26s   172.16.2.68    10.168.100.22   <none>           <none>
affinity-6868874dbc-g6vb2              1/1     Running   0          2m28s   172.16.2.162   10.168.1.5      <none>           <none>
affinity-6868874dbc-zbpt4              1/1     Running   0          2m30s   172.16.2.67    10.168.100.22   <none>           <none>
```

## 污点和容忍

污点和容忍度（Toleration）相互配合，可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点，这表示对于那些不能容忍这些污点的 Pod，是不会被该节点接受的

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

比如我们的Master 节点保留给 Kubernetes 系统组件使用，或者把一组具有特殊资源预留给某些 pod，则污点就很有用了，pod 不会再被调度到 taint 标记过的节点。我们使用kubeadm搭建的集群默认就给 master 节点添加了一个污点标记，所以我们看到我们平时的 pod 都没有被调度到 master 上

```
[tke@VM_0_13_centos ~]$ kubectl describe node 192.168.100.11
Name:               192.168.100.11
Roles:              master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=QCLOUD
                    beta.kubernetes.io/os=linux
                    cloud.tencent.com/node-instance-id=ins-1zeb156t
                    failure-domain.beta.kubernetes.io/region=cd
                    failure-domain.beta.kubernetes.io/zone=160001
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=192.168.100.11
                    kubernetes.io/node-role-etcd=true
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/master=true
Annotations:        node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Wed, 17 Jun 2020 10:51:00 +0800
Taints:             node-role.kubernetes.io/master:NoSchedule
```

我们可以使用上面的命令查看 master 节点的信息，其中有一条关于 Taints 的信息：node-role.kubernetes.io/master:NoSchedule，就表示给 master 节点打了一个污点的标记，其中影响的参数是NoSchedule，表示 pod 不会被调度到标记为 taints 的节点，除了 NoSchedule 外，还有另外两个选项：

* PreferNoSchedule：NoSchedule 的软策略版本，表示尽量不调度到污点节点上去
* NoExecute：该选项意味着一旦 Taint 生效，如该节点内正在运行的 pod 没有对应 Tolerate 设置，会直接被逐出

### 节点添加和去除污点

您可以使用命令 kubectl taint 给节点增加一个污点。比如，

```
kubectl taint nodes node1 key=value:NoSchedule
```

给节点 node1 增加一个污点，它的键名是 key，键值是 value，效果是 NoSchedule。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1 这个节点。

```
kubectl taint nodes node1 key:NoSchedule-
```

若要移除上述命令所添加的污点，你可以执行：

```
kubectl taint nodes node1 key:NoSchedule-
```

### pod容忍污点

您可以在 PodSpec 中定义 Pod 的容忍度。 下面两个容忍度均与上面例子中使用 kubectl taint 命令创建的污点相匹配， 因此如果一个 Pod 拥有其中的任何一个容忍度都能够被分配到 node1 ：

```
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"
这里是一个使用了容忍度的 Pod：pods/pod-with-toleration.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"
```

对于 tolerations 属性的写法，其中的 key、value、effect 与 Node 的 Taint 设置需保持一致， 还有以下几点说明：

1. 如果 operator 的值是 Exists，则 value 属性可省略
2. 如果 operator 的值是 Equal，则表示其 key 与 value 之间的关系是 equal(等于)
3. 如果不指定 operator 属性，则默认值为 Equal

另外，还有两个特殊值：

1. 空的 key 如果再配合 Exists 就能匹配所有的 key 与 value，也是是能容忍所有 node 的所有 Taints
2. 空的 effect 匹配所有的 effect

### 多个污点的pod容忍策略

您可以给一个节点添加多个污点，也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器：从一个节点的所有污点开始遍历， 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点，特别是以下情况：

* 如果未被过滤的污点中存在至少一个 effect 值为 NoSchedule 的污点， 则 Kubernetes 不会将 Pod 分配到该节点。
* 如果未被过滤的污点中不存在 effect 值为 NoSchedule 的污点， 但是存在 effect 值为 PreferNoSchedule 的污点， 则 Kubernetes 会 尝试 将 Pod 分配到该节点。
* 如果未被过滤的污点中存在至少一个 effect 值为 NoExecute 的污点， 则 Kubernetes 不会将 Pod 分配到该节点（如果 Pod 还未在节点上运行）， 或者将 Pod 从该节点驱逐（如果 Pod 已经在节点上运行）。

### NoExecute类型pod继续运行时间

通常情况下，如果给一个节点添加了一个 effect 值为 NoExecute 的污点， 则任何不能忍受这个污点的 Pod 都会马上被驱逐， 任何可以忍受这个污点的 Pod 都不会被驱逐。 但是，如果 Pod 存在一个 effect 值为 NoExecute 的容忍度指定了可选属性 tolerationSeconds 的值，则表示在给节点添加了上述污点之后， Pod 还能继续在节点上运行的时间。例如，

```
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600
```

这表示如果这个 Pod 正在运行，同时一个匹配的污点被添加到其所在的节点， 那么 Pod 还将继续在节点上运行 3600 秒，然后被驱逐。 如果在此之前上述污点被删除了，则 Pod 不会被驱逐。

### 节点发生异常pod发生驱逐的原理

一般我们节点发生异常的时候，节点为什么会把pod驱逐走呢，其实这个背后的原理是污点起的作用，当节点出现了异常的情况的时候，控制器会给节点打上一些污点，由于这个节点上的pod没有容忍这些污点从而被驱逐。下面是节点不同异常状态下控制器给节点打的污点类型

```
node.kubernetes.io/not-ready：节点未准备好。这相当于节点状态 Ready 的值为 "False"。
node.kubernetes.io/unreachable：节点控制器访问不到节点. 这相当于节点状态 Ready 的值为 "Unknown"。
node.kubernetes.io/out-of-disk：节点磁盘耗尽。
node.kubernetes.io/memory-pressure：节点存在内存压力。
node.kubernetes.io/disk-pressure：节点存在磁盘压力。
node.kubernetes.io/network-unavailable：节点网络不可用。
node.kubernetes.io/unschedulable: 节点不可调度。
node.cloudprovider.kubernetes.io/uninitialized：如果 kubelet 启动时指定了一个 "外部" 云平台驱动， 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后，kubelet 将删除这个污点。
```

如果节点恢复了，节点控制器会删除给节点打上的污点，这样pod就可以调度到这些节点上了。

如果我希望节点发生异常后，pod不马上发生驱逐，想在节点上再运行一段时间，那么这里可以用tolerationSeconds参数来处理，比如，一个使用了很多本地状态的应用程序在网络断开时，仍然希望停留在当前节点上运行一段较长的时间， 愿意等待网络恢复以避免被驱逐。在这种情况下，Pod 的容忍度可能是下面这样的：

```
tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000
```

节点异常后k8s中控制器会为默认给pod加上tolerationSeconds=600这个参数，避免node发生异常pod立马被驱逐

**说明**：Kubernetes 会自动给 Pod 添加一个 key 为 node.kubernetes.io/not-ready 的容忍度 并配置 tolerationSeconds=300，除非用户提供的 Pod 配置中已经已存在了 key 为 node.kubernetes.io/not-ready 的容忍度。

**同样**，Kubernetes 会给 Pod 添加一个 key 为 node.kubernetes.io/unreachable 的容忍度 并配置 tolerationSeconds=300，除非用户提供的 Pod 配置中已经已存在了 key 为 node.kubernetes.io/unreachable 的容忍度。

### DaemonSet的调度和驱逐

我们在集群中使用ds类型的时候会发现，当你部署ds的负载无论节点上有多少污点都可以调度上去，节点异常ds类型的pod也不发生驱逐，这是为什么呢？其实这里的根本原因也是因为ds类型的负载有添加污点的容忍，我们可以看下kube-proxy类型的ds容忍度是怎么设置的。

```
[root@VM_1_4_centos kubernetes]# kubectl get ds kube-proxy -n kube-system -o yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
......
      serviceAccount: kube-proxy
      serviceAccountName: kube-proxy
      terminationGracePeriodSeconds: 30
      tolerations:
      - operator: Exists
      volumes:
      - hostPath:
```

从上面的yaml中可以看出ds类型容忍度是：空的 key 如果再配合 Exists 就能匹配所有的 key 与 value，也是是能容忍所有 node 的所有 Taints

所以这也就是为什么ds类型的pod不会发生驱逐和能调度到所有节点的原因，因为ds类型容忍所有污点。

## TKE上的调度实践

我们在tke集群中如果要配置调度策略，除了修改编写yaml实现，也可以在控制台进行操作，这样对于一些yaml不是很熟悉的同学可以提供更加简便的配置方式。

![upload-image](/files/-MN2J94345CR2-FYssE_)

![upload-image](/files/-MN2J944nVVjr46yORrt)

![upload-image](/files/-MN2J945erQdq22G9Bdw)

![upload-image](/files/-MN2J946oUHNnM_x6Rqs)

控制台支持配置3种策略，分别是不使用调度策略、指定节点调度（nodeSelector）、自定义调度策略（节点亲和性），这里控制台不能配置pod的亲和性和反亲和性，如果需要配置只能通过yaml配置。

如果需要修改已经部署的deployment的调度策略可以通过depolyment的更新调度策略来修改调整

![upload-image](/files/-MN2J947R0-2rFtf3ETx)

![upload-image](/files/-MN2J948g0S9vd8kGxPG)

对于pod的亲和性和反亲和性如何配置，大家可以参考集群中的coredns来进行配置

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  generation: 1
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: CoreDNS
  name: coredns
  namespace: kube-system
spec:
......
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - kube-dns
            topologyKey: kubernetes.io/hostname
      containers:
      - args:
        - -conf
        - /etc/coredns/Corefile
        image: ccr.ccs.tencentyun.com/library/coredns:1.2.2
```

## 参考链接

<https://kubernetes.io/zh/docs/concepts/scheduling-eviction/taint-and-toleration/>

<https://www.qikqiak.com/post/kubernetes-affinity-scheduler/>


# Kubectl命令行jsonpath的使用

本篇文章主要介绍了jsonpath的语法和使用。

## jsonpath的语法介绍

Kubectl 支持 JSONPath 模板。

JSONPath 模板由 {} 包起来的 JSONPath 表达式组成。Kubectl 使用 JSONPath 表达式来过滤 JSON 对象中的特定字段并格式化输出。除了原始的 JSONPath 模板语法，以下函数和语法也是有效的:

1. 使用双引号将 JSONPath 表达式内的文本引起来。
2. 使用 range，end 运算符来迭代列表。
3. 使用负片索引后退列表。负索引不会"环绕"列表，并且只要 -index + listLength> = 0 就有效。

| 函数                 | 描述         | 示例                                                               | 结果                                                  |
| ------------------ | ---------- | ---------------------------------------------------------------- | --------------------------------------------------- |
| text               | 纯文本        | kind is {.kind}                                                  | kind is List                                        |
| @                  | 当前对象       | {@}                                                              | 与输入相同                                               |
| . or \[]           | 子运算符       | {.kind} or {\['kind']}                                           | List                                                |
| ..                 | 递归下降       | {..name}                                                         | 127.0.0.1 127.0.0.2 myself e2e                      |
| \*                 | 通配符。获取所有对象 | {.items\[\*].metadata.name}                                      | \[127.0.0.1 127.0.0.2]                              |
| \[start:end :step] | 下标运算符      | {.users\[0].name}                                                | myself                                              |
| \[,]               | 并集运算符      | {.items\[\*]\['metadata.name', 'status.capacity']}               | 127.0.0.1 127.0.0.2 map\[cpu:4] map\[cpu:8]         |
| ?()                | 过滤         | {.users\[?(@.name=="e2e")].user.password}                        | secret                                              |
| range, end         | 迭代列表       | {range .items\[\*]}\[{.metadata.name}, {.status.capacity}] {end} | \[127.0.0.1, map\[cpu:4]] \[127.0.0.2, map\[cpu:8]] |
| ''                 | 引用解释执行字符串  | {range .items\[\*]}{.metadata.name}{'\t'}{end}                   | 127.0.0.1 127.0.0.2                                 |

下面我们在k8s中使用jsonpath来获取我们想要的内容

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o json
{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "annotations": {
            "tke.cloud.tencent.com/networks-status": "[{\n    \"name\": \"tke-bridge\",\n    \"ips\": [\n        \"172.16.3.92\"\n    ],\n    \"default\": true,\n    \"dns\": {}\n}]"
        },
        "creationTimestamp": "2020-08-25T09:15:55Z",
        "generateName": "redis-5b4495ddb4-",
        "labels": {
            "k8s-app": "redis",
            "pod-template-hash": "5b4495ddb4",
            "qcloud-app": "redis"
        },
        "name": "redis-5b4495ddb4-szjtz",
        "namespace": "default",
        "ownerReferences": [
            {
                "apiVersion": "apps/v1",
                "blockOwnerDeletion": true,
                "controller": true,
                "kind": "ReplicaSet",
                "name": "redis-5b4495ddb4",
                "uid": "bef91151-d94e-46d5-a8a9-59a8f3ae634e"
            }
        ],
        "resourceVersion": "12100505479",
        "selfLink": "/api/v1/namespaces/default/pods/redis-5b4495ddb4-szjtz",
        "uid": "968a56ba-80ed-41d3-ad27-deb7d060bfe7"
    },
    "spec": {
        "containers": [
            {
                "image": "redis",
                "imagePullPolicy": "Always",
                "name": "redis",
                "resources": {
                    "limits": {
                        "cpu": "500m",
                        "memory": "1Gi"
                    },
                    "requests": {
                        "cpu": "250m",
                        "memory": "256Mi"
                    }
                },
                "securityContext": {
                    "privileged": false
                },
                "terminationMessagePath": "/dev/termination-log",
                "terminationMessagePolicy": "File",
                "volumeMounts": [
                    {
                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
                        "name": "default-token-cl2h8",
                        "readOnly": true
                    }
                ]
            }
        ],
        "dnsPolicy": "ClusterFirst",
        "enableServiceLinks": true,
        "imagePullSecrets": [
            {
                "name": "qcloudregistrykey"
            }
        ],
        "nodeName": "10.168.1.4",
        "restartPolicy": "Always",
        "schedulerName": "default-scheduler",
        "securityContext": {},
        "serviceAccount": "default",
        "serviceAccountName": "default",
        "terminationGracePeriodSeconds": 30,
        "volumes": [
            {
                "name": "default-token-cl2h8",
                "secret": {
                    "defaultMode": 420,
                    "secretName": "default-token-cl2h8"
                }
            }
        ]
    },
    "status": {
        "conditions": [
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2020-08-25T09:15:55Z",
                "status": "True",
                "type": "Initialized"
            },
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2020-08-25T09:15:59Z",
                "status": "True",
                "type": "Ready"
            },
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2020-08-25T09:15:59Z",
                "status": "True",
                "type": "ContainersReady"
            },
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2020-08-25T09:15:55Z",
                "status": "True",
                "type": "PodScheduled"
            }
        ],
        "containerStatuses": [
            {
                "containerID": "docker://0a4540282684c9dd282d13eba9a4ae44433f625e65ef40473cda7ca00b2ea73e",
                "image": "redis:latest",
                "imageID": "docker-pullable://redis@sha256:09c33840ec47815dc0351f1eca3befe741d7105b3e95bc8fdb9a7e4985b9e1e5",
                "lastState": {},
                "name": "redis",
                "ready": true,
                "restartCount": 0,
                "started": true,
                "state": {
                    "running": {
                        "startedAt": "2020-08-25T09:15:59Z"
                    }
                }
            }
        ],
        "hostIP": "10.168.1.4",
        "phase": "Running",
        "podIP": "172.16.3.92",
        "podIPs": [
            {
                "ip": "172.16.3.92"
            }
        ],
        "qosClass": "Burstable",
        "startTime": "2020-08-25T09:15:55Z"
    }
}
```

## 通过jsonpath获取信息示例

### 纯文本方式

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='kind is {.kind}'
kind is Pod
```

### 获取当前对象

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath={@}
map[apiVersion:v1 kind:Pod metadata:map[annotations:map[tke.cloud.tencent.com/networks-status:[{
    "name": "tke-bridge",
    "ips": [
        "172.16.3.92"
    ],
    "default": true,
    "dns": {}
}]] creationTimestamp:2020-08-25T09:15:55Z generateName:redis-5b4495ddb4- labels:map[k8s-app:redis pod-template-hash:5b4495ddb4 qcloud-app:redis] name:redis-5b4495ddb4-szjtz namespace:default ownerReferences:[map[apiVersion:apps/v1 blockOwnerDeletion:true controller:true kind:ReplicaSet name:redis-5b4495ddb4 uid:bef91151-d94e-46d5-a8a9-59a8f3ae634e]] resourceVersion:12100505479 selfLink:/api/v1/namespaces/default/pods/redis-5b4495ddb4-szjtz uid:968a56ba-80ed-41d3-ad27-deb7d060bfe7] spec:map[containers:[map[image:redis imagePullPolicy:Always name:redis resources:map[limits:map[cpu:500m memory:1Gi] requests:map[cpu:250m memory:256Mi]] securityContext:map[privileged:false] terminationMessagePath:/dev/termination-log terminationMessagePolicy:File volumeMounts:[map[mountPath:/var/run/secrets/kubernetes.io/serviceaccount name:default-token-cl2h8 readOnly:true]]]] dnsPolicy:ClusterFirst enableServiceLinks:true imagePullSecrets:[map[name:qcloudregistrykey]] nodeName:10.168.1.4 restartPolicy:Always schedulerName:default-scheduler securityContext:map[] serviceAccount:default serviceAccountName:default terminationGracePeriodSeconds:30 volumes:[map[name:default-token-cl2h8 secret:map[defaultMode:420 secretName:default-token-cl2h8]]]] status:map[conditions:[map[lastProbeTime:<nil> lastTransitionTime:2020-08-25T09:15:55Z status:True type:Initialized] map[lastProbeTime:<nil> lastTransitionTime:2020-08-25T09:15:59Z status:True type:Ready] map[lastProbeTime:<nil> lastTransitionTime:2020-08-25T09:15:59Z status:True type:ContainersReady] map[lastProbeTime:<nil> lastTransitionTime:2020-08-25T09:15:55Z status:True type:PodScheduled]] containerStatuses:[map[containerID:docker://0a4540282684c9dd282d13eba9a4ae44433f625e65ef40473cda7ca00b2ea73e image:redis:latest imageID:docker-pullable://redis@sha256:09c33840ec47815dc0351f1eca3befe741d7105b3e95bc8fdb9a7e4985b9e1e5 lastState:map[] name:redis ready:true restartCount:0 started:true state:map[running:map[startedAt:2020-08-25T09:15:59Z]]]] hostIP:10.168.1.4 phase:Running podIP:172.16.3.92 podIPs:[map[ip:172.16.3.92]] qosClass:Burstable startTime:2020-08-25T09:15:55Z]]
```

### 获取pod的apiversion

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{.apiVersion}'
v1
```

### 获取pod的name

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{.metadata.name}'
redis-5b4495ddb4-szjtz
```

### 递归获取yaml所有的name

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{..name}'
qcloudregistrykey default-token-cl2h8 redis default-token-cl2h8 redis redis-5b4495ddb4-szjtz redis-5b4495ddb4
```

### 获取所有状态条件中的类型

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath={.status.conditions[*].type}
Initialized Ready ContainersReady PodScheduled[
```

### 获取状态第一个条件的类型

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath={.status.conditions[0].type}
Initialized
```

### 从第一个状态条件开始到最后一个结束，每隔2个获取一次

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath={.status.conditions[0:3:2].type}
Initialized ContainersReady
```

### 获取状态条件中的状态和类型

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{range .status.conditions[*]}[{..status}, {..type}]{end}'
[True, Initialized][True, Ready][True, ContainersReady][True, PodScheduled]
```

### 空格和换行符的引用

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{range .status.conditions[*]}[{..status}, {..type}]{"\n"}{end}'
[True, Initialized]
[True, Ready]
[True, ContainersReady]
[True, PodScheduled]
```

### 获取request中的cpu值

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{range .spec.containers[0].resources}[{..cpu}]{end}'
[500m 250m]
```

### 获取request中的cpu和memory

```
[root@VM_1_4_centos ~]# kubectl get pod redis-5b4495ddb4-szjtz -o=jsonpath='{range .spec.containers[0].resources}[{..cpu}, {..memory}]{end}'
[500m 250m, 1Gi 256Mi]
```

### 获取promotheus这个pod中alertmanage的容器端口

prometheus的yaml文件只截取部分

```
[root@VM_1_4_centos ~]# kubectl get pod prometheus-7674d56d7f-dqxfx -n kube-ops -o json
{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "prometheus-7674d56d7f-dqxfx",
        "namespace": "kube-ops",
    },
    "spec": {
        "containers": [
            {
                "args": [
                    "--config.file=/etc/alertmanager/config.yml",
                    "--storage.path=/alertmanager/data"
                ],
                "image": "prom/alertmanager:v0.15.3",
                "imagePullPolicy": "IfNotPresent",
                "name": "alertmanager",
                "ports": [
                    {
                        "containerPort": 9093,
                        "name": "http",
                        "protocol": "TCP"
                    }
                ],  
             .........
            },
            {
                "command": [
                    "/bin/prometheus"
                ],
                "image": "prom/prometheus:v2.4.3",
                "imagePullPolicy": "IfNotPresent",
                "name": "prometheus",
                "ports": [
                    {
                        "containerPort": 9090,
                        "name": "http",
                        "protocol": "TCP"
                    }
            }
.......
[root@VM_1_4_centos ~]# kubectl get pod prometheus-7674d56d7f-dqxfx -n kube-ops -o=jsonpath='{.spec.containers[?(@.name=="alertmanager")].ports}'
[map[containerPort:9093 name:http protocol:TCP]]
```

## 参考文档

<https://kubernetes.io/zh/docs/reference/kubectl/jsonpath/>


# Kubernetes插件之ip-masq-agent

本章主要介绍了ip-masq-agent在k8s集群中的部署和使用

## ip-masq-agent简介

ip-masq-agent 配置 iptables 规则以隐藏位于集群节点 IP 地址后面的 pod 的 IP 地址。 这通常在将流量发送到集群的 podCIDR范围之外的目的地时使用。 什么是snat

源地址转换是内网地址向外访问时，发起访问的内网ip地址转换为指定的ip地址（可指定具体的服务以及相应的端口或端口范围），这可以使内网中使用保留ip地址的主机访问外部网络，即内网的多部主机可以通过一个有效的公网ip地址访问外部网络。

A公司拥有多个公网IP（60.191.82.105-107），A公司希望内部用户（IP为192.168.1.50）使用某个特定的IP（60.191.82.107）访问互联网，则需在出口路由设备上需要配置源地址转换。

## 部署ip-masq-agent

通过运行以下 kubectl 指令创建 ip-masq-agent:

```
kubectl apply -f https://raw.githubusercontent.com/kubernetes-incubator/ip-masq-agent/master/ip-masq-agent.yaml
```

您必须同时将适当的节点标签应用于集群中希望代理运行的任何节点。

```
kubectl label nodes my-node beta.kubernetes.io/masq-agent-ds-ready=true
```

## ip-masq-agent配置项

代理配置文件必须使用 YAML 或 JSON 语法编写，并且可能包含三个可选值：

* nonMasqueradeCIDRs: CIDR 表示法中的字符串列表，用于指定不需伪装的地址范围。
* masqLinkLocal: 布尔值 (true / false)，表示是否将流量伪装到本地链路169.254.0.0/16 前缀，默认为 false。
* resyncInterval: 代理尝试从磁盘重新加载配置的时间间隔。 例如 '30s'，其中 's' 是秒，'ms' 是毫秒等...

## TKE中的ip-masq-agent配置和使用

VPC 网络：10.168.0.0/16

集群网络：172.18.0.0/16

```
apiVersion: v1
data:
  config: '{"NonMasqueradeCIDRs":["172.16.0.0/16","10.168.0.0/16"],"MasqLinkLocal":true,"ResyncInterval":"1m0s"}'
kind: ConfigMap
metadata:
  creationTimestamp: "2020-06-02T03:16:59Z"
  name: ip-masq-agent-config
  namespace: kube-system
```

上述配置的意思是：访问目的网络10.168.0.0/16和 172.18.0.0/16 不做 SNAT，其他网段都做 SNAT，不对网段 169.254.0.0/16 做特殊处理，同步周期为1分钟。

节点的iptables规则

```
[root@VM_1_4_centos ~]# iptables -t nat -L IP-MASQ-AGENT
Chain IP-MASQ-AGENT (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             172.16.0.0/16        /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             10.168.0.0/16        /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
MASQUERADE  all  --  anywhere             anywhere             /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL
```

容器访问外网的数据链路图

![upload-image](/files/-MN2JA59ffW5H2zRm7Sa)

添加描述

使用问题

问题: 客户在vpc内开启了nat网关，节点上可以访问外网，但是到容器内却无法访问外网，节点上的所有pod都无法访问。

排查：这边客户之前为了不让pod访问外网修改了ip-masq-agent-config中的NonMasqueradeCIDRs为0.0.0.0/0从而导致集群内所有pod都无法上网。

解决方案：修改NonMasqueradeCIDRs，配置成pod网段和节点所在的网段。

## 参考链接

<https://cloud.tencent.com/developer/article/1598539>

<https://kubernetes.io/zh/docs/tasks/administer-cluster/ip-masq-agent/>


# linux定位问题常用命令

本篇文章主要介绍了在使用linux过程中常见的一些定位问题的命令。

## ifconfig(查看网卡)

```
$ ifconfig

enp1s0    Link encap:Ethernet  HWaddr 28:d2:44:eb:bd:98  
          inet addr:192.168.0.103  Bcast:192.168.0.255  Mask:255.255.255.0
          inet6 addr: fe80::8f0c:7825:8057:5eec/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:169854 errors:0 dropped:0 overruns:0 frame:0
          TX packets:125995 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:174146270 (174.1 MB)  TX bytes:21062129 (21.0 MB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:15793 errors:0 dropped:0 overruns:0 frame:0
          TX packets:15793 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:2898946 (2.8 MB)  TX bytes:2898946 (2.8 MB)
```

如果要显示所有的网络接口，包含在线（up）的或下线（down）的，使用-a 选项。

```
$ ifconfig -a
```

如果要给一个网络接口分配一个IP地址，使用下面的命令

```
$ sudo ifconfig eth0 192.168.56.5 netmask 255.255.255.0
```

如果要启用一个网络接口，使用下面命令

```
$ sudo ifconfig up eth0
```

如果要禁用一个网络接口，使用下面命令

```
$ sudo ifconfig down eth0
```

## ip(查看网卡)

显示所有网卡

```
# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 52:54:00:eb:2b:9b brd ff:ff:ff:ff:ff:ff
    inet 10.168.1.4/24 brd 10.168.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:feeb:2b9b/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 20:90:6f:6a:7f:ae brd ff:ff:ff:ff:ff:ff
    inet6 fe80::2290:6fff:fe6a:7fae/64 scope link 
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:76:73:f4:f6 brd ff:ff:ff:ff:ff:ff
```

查看路由信息

```
[root@VM_1_4_centos ~]# ip route show
default via 10.168.1.1 dev eth0 
10.168.1.0/24 dev eth0 proto kernel scope link src 10.168.1.4 
10.168.100.3 dev enic3c3d718fcd scope link 
10.168.100.11 dev enib229b17365d scope link 
169.254.0.0/16 dev eth0 scope link metric 1002 
169.254.32.0/28 dev docker0 proto kernel scope link src 169.254.32.1 linkdown 
172.16.3.64/26 dev cbr0 proto kernel scope link src 172.16.3.65
```

## ifup，ifdown(激活或者禁用网卡)

ifup命令用于激活一个网络接口，使得可以接收或传输数据。

```
$ sudo ifup eth0
```

ifdown命令可以禁用一个网络接口，禁掉后就不能传输和接收数据了。

```
$ sudo ifdown eth0
```

## ping(探测网络连通性)

```
[root@VM_1_4_centos ~]# ping www.baidu.com
PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38 (14.215.177.38): icmp_seq=1 ttl=54 time=3.30 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp_seq=2 ttl=54 time=3.15 ms
```

发送指定数量的包

```
[root@VM_1_4_centos ~]# ping -c 4 www.baidu.com
PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38 (14.215.177.38): icmp_seq=1 ttl=54 time=3.59 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp_seq=2 ttl=54 time=3.16 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp_seq=3 ttl=54 time=3.19 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp_seq=4 ttl=54 time=3.14 ms

--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 3.148/3.275/3.591/0.191 ms
```

## traceroute(查看数据包路由途径)

```
[root@VM_1_4_centos ~]# traceroute 106.55.165.251
traceroute to 106.55.165.251 (106.55.165.251), 30 hops max, 60 byte packets
 1  9.31.61.129 (9.31.61.129)  1.033 ms  1.954 ms  1.574 ms
 2  9.31.123.96 (9.31.123.96)  0.831 ms  1.058 ms  1.319 ms
 3  9.31.123.105 (9.31.123.105)  0.763 ms  1.330 ms  1.052 ms
 4  106.55.165.251 (106.55.165.251)  0.504 ms  0.524 ms  0.555 ms
```

## mtr(ping+traceroute)

```
# mtr qq.com
```

![upload-image](/files/-MN2J7Opsoen9BIXGoCj)

## route(查看路由表)

```
[root@VM_1_4_centos ~]# route 
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         gateway         0.0.0.0         UG    0      0        0 eth0
10.168.1.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0
10.168.100.3    0.0.0.0         255.255.255.255 UH    0      0        0 enic3c3d718fcd
10.168.100.11   0.0.0.0         255.255.255.255 UH    0      0        0 enib229b17365d
link-local      0.0.0.0         255.255.0.0     U     1002   0        0 eth0
169.254.32.0    0.0.0.0         255.255.255.240 U     0      0        0 docker0
172.16.3.64     0.0.0.0         255.255.255.192 U     0      0        0 cbr0
```

添加一个网络路由到一个路由表：

```
$ sudo route add -net <network ip/cidr> gw <gateway ip> <interface>
```

从路由表中删除特定的路由项：

```
$ sudo route del -net <network ip/cidr>
```

## netstat(查看连接数)

查看某个服务的TCP连接数

```
# netstat -an | grep ESTABLISHED | grep 126:80 | wc -l
```

查看udp的连接数

```
netstat -nu
```

查看网卡列表

```
[root@VM_1_4_centos ~]# netstat -i 
Kernel Interface table
Iface             MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
cbr0             1500 421966834      0      0 0      478598834      0      0      0 BMPRU
docker0          1500        0      0      0 0             7      0      0      0 BMU
enib229b17365d   1500  3399766      0      0 0       5942984      0      0      0 BMRU
enic3c3d718fcd   1500  1187714      0      0 0       1188197      0      0      0 BMRU
eth0             1500 945294724      0      0 0      695281730      0      0      0 BMRU
eth1             1500  8390504      0      0 0       4818989      0      0      0 BMRU
lo              65536  6202712      0      0 0       6202712      0      0      0 LRU
veth00bf0de2     1500       10      0      0 0         10516      0      0      0 BMRU
```

显示组播组的关系

```
# netstat -g
IPv6/IPv4 Group Memberships
Interface    RefCnt Group
--------------- ------ ---------------------
lo       1   ALL-SYSTEMS.MCAST.NET
eth0      1   ALL-SYSTEMS.MCAST.NET
lo       1   ff02::1
eth0      1   ff02::1:ff0a:b0c
eth0      1   ff02::1
```

显示网络统计信息

```
# netstat -s
Ip:
  184695 total packets received
  0 forwarded
  0 incoming packets discarded
  184687 incoming packets delivered
  143917 requests sent out
  32 outgoing packets dropped
  30 dropped because of missing route
Icmp:
  676 ICMP messages received
```

显示监听的套接口

```
# netstat -l
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTEN     
tcp        0      0 VM_1_4_centos:33437     0.0.0.0:*               LISTEN     
tcp        0      0 VM_1_4_centos:50050     0.0.0.0:*               LISTEN     
tcp        0      0 VM_1_4_centos:10248     0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:46313           0.0.0.0:*               LISTEN     
tcp        0      0 VM_1_4_centos:10249     0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:sunrpc          0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:30099              [::]:*                  LISTEN
```

## ss(检测套接字)

```
$ ss -ta
State      Recv-Q Send-Q                                         Local Address:Port                                                          Peer Address:Port                
LISTEN     0      128                                                        *:ssh                                                                      *:*                    
LISTEN     0      32768                                              127.0.0.1:33437                                                                    *:*                    
LISTEN     0      32768                                              127.0.0.1:50050                                                                    *:*                    
LISTEN     0      32768                                              127.0.0.1:10248                                                                    *:*                    
LISTEN     0      64                                                         *:46313                                                                    *:*                    
LISTEN     0      32768                                              127.0.0.1:10249                                                                    *:*                    
LISTEN     0      128                                                        *:sunrpc                                                                   *:*                    
TIME-WAIT  0      0                                                172.16.3.65:48682                                                          172.16.3.75:websm                
ESTAB      0      0                                               172.16.252.1:54536                                                         172.16.252.1:https                
ESTAB      0      0                                                 10.168.1.4:46820                                                       169.254.128.15:60002                
ESTAB      0      0                                                 10.168.1.4:ssh                                                          163.177.68.35:11123                
ESTAB      0      0                                                 10.168.1.4:51178                                                         169.254.0.71:http                 
ESTAB      0      0                                               172.16.252.1:45940                                                         172.16.252.1:https
```

显示所有活动的TCP连接以及计时器，运行以下命令：

```
$ ss -to
```

## ncat(nc)

ncat/nc既是一个端口扫描工具，也是一款安全工具，还是一款监测工具，甚至可以做为一个简单的TCP代理。 由于有这么多的功能，它被誉为是网络界的瑞士军刀。 安装

```
yum install nmap-ncat -y
```

监听某个端口的入站连接

```
ncat -l 80
```

连接远程服务器执行命令

```
ncat IP_address port_number

[root@VM-6-17-centos ~]# ncat 49.235.179.157 80
curl localhost  
HTTP/1.1 400 Bad Request
Server: nginx/1.16.1
Date: Fri, 28 Aug 2020 09:08:14 GMT
Content-Type: text/html
Content-Length: 157
Connection: close

<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.16.1</center>
</body>
</html>
```

测试主机端口的连通性

```
[root@VM-6-17-centos ~]# ncat -v 49.235.179.157 22
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 49.235.179.157:22.
SSH-2.0-OpenSSH_7.4
```

nc进行主机间通信聊天

```
[root@VM_0_13_centos ~]# ncat 42.194.143.131 8081
hello

[root@VM_1_4_centos ~]# ncat -l 8081
hello
```

## nmap(扫描主机)

扫描www\.niewx.club这个地址的状态

```
[root@VM_1_4_centos ~]# nmap www.niewx.club

Starting Nmap 6.40 ( http://nmap.org ) at 2020-08-28 17:16 CST
Nmap scan report for www.niewx.club (49.235.179.157)
Host is up (0.029s latency).
Not shown: 992 filtered ports
PORT      STATE  SERVICE
20/tcp    closed ftp-data
21/tcp    closed ftp
22/tcp    open   ssh
80/tcp    open   http
443/tcp   open   https
3389/tcp  closed ms-wbt-server
5555/tcp  closed freeciv
55555/tcp closed unknown

Nmap done: 1 IP address (1 host up) scanned in 80.76 seconds
```

## iftop(查看流量)

```
yum install iftop -y
# iftop -i eth0
```

-i: 接口

-B: 以字节而非比特显示

![upload-image](/files/-MN2J7Or4YA9ZWJMI_KS)

TX：发送流量

RX：接收流量

TOTAL：总流量

Cumm：运行iftop到目前时间的总流量

peak：流量峰值

rates：分别表示过去 2s 10s 40s 的平均流量

## trickle(限制带宽)

```
yum install trickle -y
```

这个应用用于限制网络带宽

1. 限制wget的上传和下载速度限制上传为10KB/S,下载为20KB/s

```
# trickle -u 10 -d 20 wget http://mirrors.163.com/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-1503-01.iso
```

1. 单独限制某个进程的下载和上传速度

```
# trickle -s -d 50 -u 25 ftp
```

1. 限制终端下的所有命令带宽为，下载500KB/S，上传250KB/s;

```
# trickle -s -d 500 -u 250 bash
备注，单独命令使用时，必须加-s参数
```

## dstat(监控cpu内存)

```
yum install dstat -y
-c: 显示cpu综合占有率
-m: 显示内存使用情况
-n: 显示网络状况
-l：显示系统负载情况
-r：显示I/O请求(读/写)情况
[root@VM_1_4_centos ~]# dstat -ncmlr
-net/total- ----total-cpu-usage---- ------memory-usage----- ---load-avg--- --io/total-
 recv  send|usr sys idl wai hiq siq| used  buff  cach  free| 1m   5m  15m | read  writ
   0     0 |  8   2  90   0   0   0|6769M 1226M 6870M  661M|0.61 0.50 0.46|2.85  23.7 
  78k  141k|  2   2  95   0   0   0|6777M 1226M 6870M  653M|0.56 0.49 0.45|   0  25.0 
  71k   97k|  6   1  93   0   0   0|6771M 1226M 6870M  659M|0.56 0.49 0.45|   0  9.00 
 273k  235k|  7   1  92   0   0   0|6771M 1227M 6870M  659M|0.56 0.49 0.45|   0  32.0 
 214k  401k|  3   1  96   0   0   0|6771M 1227M 6870M  659M|0.56 0.49 0.45|   0  5.00 
 123k  132k|  2   1  97   0   0   0|6772M 1227M 6870M  658M|0.56 0.49 0.45|   0  35.0
```

## tcpdump(网络抓包)

```
tcpdump [ -adeflnNOpqStvx ] [ -c 数量 ] [ -F 文件名 ]
　　　　　　　　　　[ -i 网络接口 ] [ -r 文件名] [ -s snaplen ]
                                       [ -T 类型 ] [ -w 文件名 ] [表达式 ]
 tcpdump的选项介绍
　　　-a 　　　将网络地址和广播地址转变成名字；
　　　-d 　　　将匹配信息包的代码以人们能够理解的汇编格式给出；
　　　-dd 　　　将匹配信息包的代码以c语言程序段的格式给出；
　　　-ddd 　　　将匹配信息包的代码以十进制的形式给出；
　　　-e 　　　在输出行打印出数据链路层的头部信息，包括源mac和目的mac，以及网络层的协议；
　　　-f 　　　将外部的Internet地址以数字的形式打印出来；
　　　-l 　　　使标准输出变为缓冲行形式；
　　　-n 　　　指定将每个监听到数据包中的域名转换成IP地址后显示，不把网络地址转换成名字；
     -nn：    指定将每个监听到的数据包中的域名转换成IP、端口从应用名称转换成端口号后显示
　　　-t 　　　在输出的每一行不打印时间戳；
　　　-v 　　　输出一个稍微详细的信息，例如在ip包中可以包括ttl和服务类型的信息；
　　　-vv 　　　输出详细的报文信息；
　　　-c 　　　在收到指定的包的数目后，tcpdump就会停止；
　　　-F 　　　从指定的文件中读取表达式,忽略其它的表达式；
　　　-i 　　　指定监听的网络接口；
      -p：    将网卡设置为非混杂模式，不能与host或broadcast一起使用
　　　-r 　　　从指定的文件中读取包(这些包一般通过-w选项产生)；
　　　-w 　　　直接将包写入文件中，并不分析和打印出来；
      -s snaplen         snaplen表示从一个包中截取的字节数。0表示包不截断，抓完整的数据包。默认的话 tcpdump 只显示部分数据包,默认68字节。
　　　-T 　　　将监听到的包直接解释为指定的类型的报文，常见的类型有rpc （远程过程调用）和snmp（简单网络管理协议；）
     -X            告诉tcpdump命令，需要把协议头和包内容都原原本本的显示出来（tcpdump会以16进制和ASCII的形式显示），这在进行协议分析时是绝对的利器。
```

1. 抓取包含10.10.10.122的数据包

```
# tcpdump -i eth0 -vnn host 10.10.10.122
```

1. 抓取包含10.10.10.0/24网段的数据包

```
# tcpdump -i eth0 -vnn net 10.10.10.0/24
```

1. 抓取包含端口22的数据包

```
# tcpdump -i eth0 -vnn port 22
```

1. 抓取udp协议的数据包

```
# tcpdump -i eth0 -vnn  udp
```

1. 抓取icmp协议的数据包

```
# tcpdump -i eth0 -vnn icmp
```

1. 抓取arp协议的数据包

```
# tcpdump -i eth0 -vnn arp
```

1. 抓取ip协议的数据包

```
# tcpdump -i eth0 -vnn ip
```

1. 抓取源ip是10.10.10.122数据包。

```
# tcpdump -i eth0 -vnn src host 10.10.10.122
```

1. 抓取目的ip是10.10.10.122数据包

```
# tcpdump -i eth0 -vnn dst host 10.10.10.122
```

1. 抓取源端口是22的数据包

```
# tcpdump -i eth0 -vnn src port 22
```

1. 抓取源ip是10.10.10.253且目的ip是22的数据包

```
# tcpdump -i eth0 -vnn src host 10.10.10.253 and dst port 22
```

1. 抓取源ip是10.10.10.122或者包含端口是22的数据包

```
# tcpdump -i eth0 -vnn src host 10.10.10.122 or port 22
```

1. 抓取源ip是10.10.10.122且端口不是22的数据包

```
# tcpdump -i eth0 -vnn src host 10.10.10.122 and not port 22
```

1. 抓取源ip是10.10.10.2且目的端口是22，或源ip是10.10.10.65且目的端口是80的数据包。

```
# tcpdump -i eth0 -vnn \( src host 10.10.10.2 and dst port 22 \) or   \( src host 10.10.10.65 and dst port 80 \)
```

1. 抓取源ip是10.10.10.59且目的端口是22，或源ip是10.10.10.68且目的端口是80的数据包。

```
[root@localhost ~]# tcpdump -i  eth0 -vnn 'src host 10.10.10.59 and dst port 22' or  ' src host 10.10.10.68 and dst port 80 '
```

1. 把抓取的数据包记录存到/tmp/fill文件中，当抓取100个数据包后就退出程序。

```
# tcpdump –i eth0 -vnn -w  /tmp/fil1 -c 100
```

1. 从/tmp/fill记录中读取tcp协议的数据包

```
# tcpdump –i eth0 -vnn -r  /tmp/fil1 tcp
```

1. 从/tmp/fill记录中读取包含10.10.10.58的数据包

```
# tcpdump –i eth0 -vnn -r  /tmp/fil1 host  10.10.10.58
```

## curl(发送请求)

```
curl www.baidu.com
```

也可以保存源码 用curl -O 文件名 url，这个和wget类似

```
curl -O baidu.txt wwww.baidu.com
```

显示网页头部信息 用-i,当然也会把网页信息显示出来

```
[root@VM_0_11_centos training]# curl -i www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Date: Thu, 02 Apr 2020 02:14:33 GMT
Etag: "588604c8-94d"
Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/

<!DOCTYPE html>
 xxx
  </html>
```

参数 -v可以显示通信的过程：

```
[root@VM_0_11_centos training]# curl -v www.baidu.com
* About to connect() to www.baidu.com port 80 (#0)
*   Trying 180.101.49.11...
* Connected to www.baidu.com (180.101.49.11) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.baidu.com
> Accept: */*
> 
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Length: 2381
< Content-Type: text/html
< Date: Thu, 02 Apr 2020 02:16:36 GMT
< Etag: "588604c8-94d"
< Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
```

更详细的通信信息可以用 参数 --trance 文件名 url,具体信息保存到单独的文件中

```
[root@VM_0_11_centos training]# curl --trace info.txt www.baidu.com
```

htpp的动词，例如GET POST，PUT，DELETE等,需要参数 -X

curl默认的是get请求，如果发送POSt请求

```
curl -X POST www.baidu.com
```

发送表单的时候，GET很简单 只需要把数据拼接到url后面就行

```
curl www.baidu.com?data=xxx&data1=xxx
```

POST也不难

```
curl -X POST --data "data=xxx" example.com/form.cgi
```

POST发送请求的数据体可以用-d

```
$ curl -d'login=emma＆password=123'-X POST https://google.com/login
 或者
$ curl -d 'login=emma' -d 'password=123' -X POST  https://google.com/login
```

使用-d参数以后，HTTP 请求会自动加上标头Content-Type : application/x-www-form-urlencoded。并且会自动将请求转为 POST 方法，因此可以省略-X POST。-d参数可以读取本地文本文件的数据，向服务器发送。

```
$ curl -d '@data.txt' https://google.com/login
```

上面命令读取data.txt文件的内容，作为数据体向服务器发送。 文件上传： 假定文件上传的表单是下面这样：

```
　<form method="POST" enctype='multipart/form-data' action="upload.cgi">
　　　　<input type=file name=upload>
　　　　<input type=submit name=press value="OK">
　　</form>
```

curl上传就应该是：

```
curl --form upload=@localfilename --form press=OK [URL]
```

\--referer参数表示的是你从哪个页面来的

```
[root@VM_0_11_centos training]# curl --referer www.baidu.com www.baidu.com
```

User Agent字段，这个字段表示的是客户端设备的信息，服务器可能会根据这个User Agent字段来判断是手机还是电脑

```
curl --user-agent " xx" url
比如IPhone 
Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) 
AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5


 curl --user-agent "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) 
 AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5"  www.baidu.com
```

* \--user-agent 可以用-A或者-H来替代
* \--cookie参数，使用--cookie可以携带cookie信息

```
curl --cookie "name=xxx" URL
 `-c cookie-file`可以保存服务器返回的cookie到文件，
 `-b cookie-file`可以使用这个文件作为cookie信息，进行后续的请求。
```

增加头部信息 --header

```
curl --header "Content-Type:application/json" http://example.com
```

## 参考链接

<https://www.jianshu.com/p/39b9d66c9dbf>

<https://www.toutiao.com/i6812988437512061452/> <https://www.toutiao.com/i6850221908475003405/>


# 关于kubernetes垃圾回收那点事

本篇文章介绍了在kubernetes中kubelet如何对镜像和容器进行垃圾回收。

## kubelet垃圾回收介绍及源码分析

使用kubernetes的过程中，为了保持磁盘的空间在一个合理的使用率，kubele提供了垃圾回收机制，kubelet的垃圾回收机制分为镜像的回收和container的回收。

Kubelet 垃圾回收（Garbage Collection）是一个非常有用的功能，它负责自动清理节点上的无用镜像和容器。Kubelet 每隔 1 分钟进行一次容器清理，每隔 5 分钟进行一次镜像清理（截止到 v1.18版本，垃圾回收间隔时间还都是在源码中固化的，不可自定义配置）

我们可以在kubelet的源码src\k8s.io\kubernetes\pkg\kubelet\kubelet.go中看下这个时间的配置，其中定义了2个变量分别是ContainerGCPeriod 和ImageGCPeriod ，表示执行镜像和容器的垃圾回收间隔时间

```
    // ContainerGCPeriod is the period for performing container garbage collection.
    ContainerGCPeriod = time.Minute
    // ImageGCPeriod is the period for performing image garbage collection.
    ImageGCPeriod = 5 * time.Minute
```

执行垃圾回收的入口方式是StartGarbageCollection

```
func (kl *Kubelet) StartGarbageCollection() {
    loggedContainerGCFailure := false
    go wait.Until(func() {
        if err := kl.containerGC.GarbageCollect(); err != nil {
            klog.Errorf("Container garbage collection failed: %v", err)
            kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ContainerGCFailed, err.Error())
            loggedContainerGCFailure = true
        } else {
            var vLevel klog.Level = 4
            if loggedContainerGCFailure {
                vLevel = 1
                loggedContainerGCFailure = false
            }

            klog.V(vLevel).Infof("Container garbage collection succeeded")
        }
    }, ContainerGCPeriod, wait.NeverStop)

    // when the high threshold is set to 100, stub the image GC manager
    if kl.kubeletConfiguration.ImageGCHighThresholdPercent == 100 {
        klog.V(2).Infof("ImageGCHighThresholdPercent is set 100, Disable image GC")
        return
    }

    prevImageGCFailed := false
    go wait.Until(func() {
        if err := kl.imageManager.GarbageCollect(); err != nil {
            if prevImageGCFailed {
                klog.Errorf("Image garbage collection failed multiple times in a row: %v", err)
                // Only create an event for repeated failures
                kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ImageGCFailed, err.Error())
            } else {
                klog.Errorf("Image garbage collection failed once. Stats initialization may not have completed yet: %v", err)
            }
            prevImageGCFailed = true
        } else {
            var vLevel klog.Level = 4
            if prevImageGCFailed {
                vLevel = 1
                prevImageGCFailed = false
            }

            klog.V(vLevel).Infof("Image garbage collection succeeded")
        }
    }, ImageGCPeriod, wait.NeverStop)
}
```

### 镜像收集

Kubernetes 通过 imageManager 与 cadvisor 协作的方式管理所有镜像的生命周期。

收集垃圾镜像的策略考虑两个因素： HighThresholdPercent 和 LowThresholdPercent。磁盘使用率超过高阈值将触发垃圾收集策略。该策略将删除最近最少使用的镜像直至满足低阈值。

kl.imageManager.GarbageCollect

上面已经分析了容器回收的主要流程，下面会继续分析镜像回收的流程，kl.imageManager.GarbageCollect 是镜像回收任务启动的方法，镜像回收流程是在 imageManager 中进行的，首先了解下 imageManager 的初始化，imageManager 也是在 NewMainKubelet 方法中进行初始化的。

k8s.io/kubernetes/pkg/kubelet/kubelet.go

```
func NewMainKubelet(){
    ......    
    // 初始化时需要指定三个参数，三个参数已经在上文中提到过
    imageGCPolicy := images.ImageGCPolicy{
        MinAge:               kubeCfg.ImageMinimumGCAge.Duration,
        HighThresholdPercent: int(kubeCfg.ImageGCHighThresholdPercent),
        LowThresholdPercent:  int(kubeCfg.ImageGCLowThresholdPercent),
    }
    ......
    imageManager, err := images.NewImageGCManager(klet.containerRuntime, klet.StatsProvider, kubeDeps.Recorder, nodeRef, imageGCPolicy, crOptions.PodSandboxImage)
    if err != nil {
        return nil, fmt.Errorf("failed to initialize image manager: %v", err)
    }
    klet.imageManager = imageManager
    ......
}
```

kl.imageManager.GarbageCollect 方法的主要逻辑为：

1. 首先调用 im.statsProvider.ImageFsStats 获取容器镜像存储目录挂载点文件系统的磁盘信息；
2. 获取挂载点的 available 和 capacity 信息并计算其使用率；
3. 若使用率大于 HighThresholdPercent，首先根据 LowThresholdPercent 值计算需要释放的磁盘量，然后调用 im.freeSpace 释放未使用的 image 直到满足磁盘空闲率；

k8s.io/kubernetes/pkg/kubelet/images/image\_gc\_manager.go:269

```
func (im *realImageGCManager) GarbageCollect() error {
    // 1、获取容器镜像存储目录挂载点文件系统的磁盘信息
    fsStats, err := im.statsProvider.ImageFsStats()
    if err != nil {
        return err
    }

    var capacity, available int64
    if fsStats.CapacityBytes != nil {
        capacity = int64(*fsStats.CapacityBytes)
    }
    if fsStats.AvailableBytes != nil {
        available = int64(*fsStats.AvailableBytes)
    }

    if available > capacity {
        available = capacity
    }

    if capacity == 0 {
        err := goerrors.New("invalid capacity 0 on image filesystem")
        im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidDiskCapacity, err.Error())
        return err
    }
    // 2、若使用率大于 HighThresholdPercent，此时需要回收镜像
    usagePercent := 100 - int(available*100/capacity)
    if usagePercent >= im.policy.HighThresholdPercent {
        // 3、计算需要释放的磁盘量
        amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available

        // 4、调用 im.freeSpace 回收未使用的镜像信息
        freed, err := im.freeSpace(amountToFree, time.Now())
        if err != nil {
            return err
        }

        if freed < amountToFree {
            err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d bytes, but freed %d bytes", amountToFree, freed)
            im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.FreeDiskSpaceFailed, err.Error())
            return err
        }
    }

    return nil
}
```

im.freeSpace

im.freeSpace 是回收未使用镜像的方法，其主要逻辑为：

1. 首先调用 im.detectImages 获取已经使用的 images 列表作为 imagesInUse；
2. 遍历 im.imageRecords 根据 imagesInUse 获取所有未使用的 images 信息，im.imageRecords 记录 node 上所有 images 的信息；
3. 根据使用时间对未使用的 images 列表进行排序；
4. 遍历未使用的 images 列表然后调用 im.runtime.RemoveImage 删除镜像，直到回收完所有未使用 images 或者满足空闲率；

k8s.io/kubernetes/pkg/kubelet/images/image\_gc\_manager.go:328

```
func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) {
    // 1、获取已经使用的 images 列表
    imagesInUse, err := im.detectImages(freeTime)
    if err != nil {
        return 0, err
    }

    im.imageRecordsLock.Lock()
    defer im.imageRecordsLock.Unlock()

    // 2、获取所有未使用的 images 信息
    images := make([]evictionInfo, 0, len(im.imageRecords))
    for image, record := range im.imageRecords {
        if isImageUsed(image, imagesInUse) {
            klog.V(5).Infof("Image ID %s is being used", image)
            continue
        }
        images = append(images, evictionInfo{
            id:          image,
            imageRecord: *record,
        })
    }
    // 3、按镜像使用时间进行排序
    sort.Sort(byLastUsedAndDetected(images))
    // 4、回收未使用的镜像
    var deletionErrors []error
    spaceFreed := int64(0)
    for _, image := range images {
        if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) {
            continue
        }

        if freeTime.Sub(image.firstDetected) < im.policy.MinAge {
            continue
        }

        // 5、调用 im.runtime.RemoveImage 删除镜像
        err := im.runtime.RemoveImage(container.ImageSpec{Image: image.id})
        if err != nil {
            deletionErrors = append(deletionErrors, err)
            continue
        }
        delete(im.imageRecords, image.id)
        spaceFreed += image.size
        if spaceFreed >= bytesToFree {
            break
        }
    }

    if len(deletionErrors) > 0 {
        return spaceFreed, fmt.Errorf("wanted to free %d bytes, but freed %d bytes space with errors in image deletion: %v", bytesToFree, spaceFreed,   errors.NewAggregate(deletionErrors))
    }
    return spaceFreed, nil
}
```

### 容器收集

容器收集策略考虑三个用户自定义变量。MinAge 是容器可以被收集的最小运行时间。MaxPerPodContainer 是每个pod (UID, container name) 中允许拥有死亡容器的最大数。MaxContainers全局死亡容器的最大数。通过将 MinAge 设置为零并将 MaxPerPodContainer 和 MaxContainers 分别设置为小于零，可以单独禁用这些变量。

Kubelet作用于未能被识别的，被删除的或超出上述变量边界的容器。最久远的容器首先被移除。当每个 pod(MaxPerPodContainer) 允许的最大容器数超出全局死亡容器的界限(MaxContainers) 时，MaxPerPodContainer 和 MaxContainer 可能会相互冲突。MaxPerPodContainer 可以在根据以下情形进行调整：最坏的情况是将 MaxPerPodContainer 降级至1并排除最旧的容器。此外，已被删除的 pod 所拥有的容器一旦比MinAge更旧，也会被移除。

kl.containerGC.GarbageCollect

kl.containerGC.GarbageCollect 调用的是 ContainerGC manager 中的方法，ContainerGC 是在 NewMainKubelet 中初始化的，ContainerGC 在初始化时需要指定一个 runtime，该 runtime 即 ContainerRuntime，在 kubelet 中即 kubeGenericRuntimeManager，也是在 NewMainKubelet 中初始化的。

k8s.io/kubernetes/pkg/kubelet/kubelet.go

```
func NewMainKubelet(){
    ......
    // MinAge、MaxPerPodContainer、MaxContainers 分别上文章开头提到的与容器垃圾回收有关的
    // 三个参数
    containerGCPolicy := kubecontainer.ContainerGCPolicy{
        MinAge:             minimumGCAge.Duration,
        MaxPerPodContainer: int(maxPerPodContainerCount),
        MaxContainers:      int(maxContainerCount),
    }

    // 初始化 containerGC 模块
    containerGC, err := kubecontainer.NewContainerGC(klet.containerRuntime, containerGCPolicy, klet.sourcesReady)
    if err != nil {
        return nil, err
    }
    ......
}
```

以下是 ContainerGC 的初始化以及 GarbageCollect 的启动：

k8s.io/kubernetes/pkg/kubelet/container/container\_gc.go:68

```
func NewContainerGC(runtime Runtime, policy ContainerGCPolicy, sourcesReadyProvider SourcesReadyProvider) (ContainerGC, error) {
    if policy.MinAge < 0 {
        return nil, fmt.Errorf("invalid minimum garbage collection age: %v", policy.MinAge)
    }

    return &realContainerGC{
        runtime:              runtime,
        policy:               policy,
        sourcesReadyProvider: sourcesReadyProvider,
    }, nil
}

func (cgc *realContainerGC) GarbageCollect() error {
    return cgc.runtime.GarbageCollect(cgc.policy, cgc.sourcesReadyProvider.AllReady(), false)
}
```

可以看到，ContainerGC 中的 GarbageCollect 最终是调用 runtime 中的 GarbageCollect 方法，runtime 即 kubeGenericRuntimeManager。

cgc.runtime.GarbageCollect

cgc.runtime.GarbageCollect 的实现是在 kubeGenericRuntimeManager 中，其主要逻辑为：

1. 回收 pod 中的 container；
2. 回收 pod 中的 sandboxes；
3. 回收 pod 以及 container 的 log dir；

k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime\_gc.go:378

```
func (cgc *containerGC) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, evictTerminatedPods bool) error {
    errors := []error{}
    // 1、回收 pod 中的 container
    if err := cgc.evictContainers(gcPolicy, allSourcesReady, evictTerminatedPods); err != nil {
        errors = append(errors, err)
    }

    // 2、回收 pod 中的 sandboxes
    if err := cgc.evictSandboxes(evictTerminatedPods); err != nil {
        errors = append(errors, err)
    }

    // 3、回收 pod 以及 container 的 log dir
    if err := cgc.evictPodLogsDirectories(allSourcesReady); err != nil {
        errors = append(errors, err)
    }
    return utilerrors.NewAggregate(errors)
}
```

## kubelet垃圾回收的参数配置实践

src\k8s.io\kubernetes\pkg\kubelet\apis\config\fuzzer\fuzzer.go 配置了kubelet参数的默认配置

```
func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
    return []interface{}{
        // provide non-empty values for fields with defaults, so the defaulter doesn't change values during round-trip
        func(obj *kubeletconfig.KubeletConfiguration, c fuzz.Continue) {
            c.FuzzNoCustom(obj)
            obj.EnableServer = true
            obj.Authentication.Anonymous.Enabled = true
            obj.Authentication.Webhook.Enabled = false
            obj.Authentication.Webhook.CacheTTL = metav1.Duration{Duration: 2 * time.Minute}
            obj.Authorization.Mode = kubeletconfig.KubeletAuthorizationModeAlwaysAllow
            obj.Authorization.Webhook.CacheAuthorizedTTL = metav1.Duration{Duration: 5 * time.Minute}
            obj.Authorization.Webhook.CacheUnauthorizedTTL = metav1.Duration{Duration: 30 * time.Second}
            obj.Address = "0.0.0.0"
            obj.VolumeStatsAggPeriod = metav1.Duration{Duration: time.Minute}
            obj.RuntimeRequestTimeout = metav1.Duration{Duration: 2 * time.Minute}
            obj.CPUCFSQuota = true
            obj.EventBurst = 10
            obj.EventRecordQPS = 5
            obj.EnableControllerAttachDetach = true
            obj.EnableDebuggingHandlers = true
            obj.FileCheckFrequency = metav1.Duration{Duration: 20 * time.Second}
            obj.HealthzBindAddress = "127.0.0.1"
            obj.HealthzPort = 10248
            obj.HTTPCheckFrequency = metav1.Duration{Duration: 20 * time.Second}
            obj.ImageMinimumGCAge = metav1.Duration{Duration: 2 * time.Minute}
            obj.ImageGCHighThresholdPercent = 85
            obj.ImageGCLowThresholdPercent = 80
            obj.KernelMemcgNotification = false
            obj.MaxOpenFiles = 1000000
            obj.MaxPods = 110
            obj.PodPidsLimit = -1
            obj.NodeStatusUpdateFrequency = metav1.Duration{Duration: 10 * time.Second}
            obj.NodeStatusReportFrequency = metav1.Duration{Duration: time.Minute}
            obj.NodeLeaseDurationSeconds = 40
            obj.CPUManagerPolicy = "none"
            obj.CPUManagerReconcilePeriod = obj.NodeStatusUpdateFrequency
            obj.NodeStatusMaxImages = 50
            obj.TopologyManagerPolicy = kubeletconfig.NoneTopologyManagerPolicy
            obj.QOSReserved = map[string]string{
                "memory": "50%",
            }
            obj.OOMScoreAdj = int32(qos.KubeletOOMScoreAdj)
            obj.Port = ports.KubeletPort
            obj.ReadOnlyPort = ports.KubeletReadOnlyPort
            obj.RegistryBurst = 10
            obj.RegistryPullQPS = 5
            obj.ResolverConfig = kubetypes.ResolvConfDefault
            obj.SerializeImagePulls = true
            obj.StreamingConnectionIdleTimeout = metav1.Duration{Duration: 4 * time.Hour}
            obj.SyncFrequency = metav1.Duration{Duration: 1 * time.Minute}
            obj.ContentType = "application/vnd.kubernetes.protobuf"
            obj.KubeAPIQPS = 5
            obj.KubeAPIBurst = 10
            obj.HairpinMode = v1beta1.PromiscuousBridge
            obj.EvictionHard = kubeletconfigv1beta1.DefaultEvictionHard
            obj.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 5 * time.Minute}
            obj.MakeIPTablesUtilChains = true
            obj.IPTablesMasqueradeBit = kubeletconfigv1beta1.DefaultIPTablesMasqueradeBit
            obj.IPTablesDropBit = kubeletconfigv1beta1.DefaultIPTablesDropBit
            obj.CgroupsPerQOS = true
            obj.CgroupDriver = "cgroupfs"
            obj.EnforceNodeAllocatable = kubeletconfigv1beta1.DefaultNodeAllocatableEnforcement
            obj.StaticPodURLHeader = make(map[string][]string)
            obj.ContainerLogMaxFiles = 5
            obj.ContainerLogMaxSize = "10Mi"
            obj.ConfigMapAndSecretChangeDetectionStrategy = "Watch"
            obj.AllowedUnsafeSysctls = []string{}
            obj.VolumePluginDir = kubeletconfigv1beta1.DefaultVolumePluginDir
            if obj.Logging.Format == "" {
                obj.Logging.Format = "text"
            }
            obj.EnableSystemLogHandler = true
        },
    }
}
```

### 镜像垃圾回收参数配置

* \--image-gc-high-threshold，默认 85，高于此阈值将进行回收
* \--image-gc-low-threshold，默认 80，低于此阈值不进行会
* \--minimum-image-ttl-duration，默认 2m0s，回收 image 最小年龄

```
obj.ImageGCHighThresholdPercent = 85
obj.ImageGCLowThresholdPercent = 80
obj.ImageMinimumGCAge = metav1.Duration{Duration: 2 * time.Minute}
```

我们在节点上修改kubelet的镜像回收配置

```
[Service]
EnvironmentFile=-/etc/kubernetes/kubelet
ExecStart=/usr/bin/kubelet ${SERIALIZE_IMAGE_PULLS} ${REGISTER_SCHEDULABLE} ${V} ${CLOUD_PROVIDER} ${FAIL_SWAP_ON} ${AUTHORIZATION_MODE} ${CLOUD_CONFIG} ${CLUSTER_DNS} ${IMAGE_PULL_PROGRESS_DEADLINE} ${HOSTNAME_OVERRIDE} ${EVICTION_HARD} ${CLIENT_CA_FILE} ${NON_MASQUERADE_CIDR} ${KUBE_RESERVED} ${MAX_PODS} ${AUTHENTICATION_TOKEN_WEBHOOK} ${POD_INFRA_CONTAINER_IMAGE} ${ANONYMOUS_AUTH} ${KUBECONFIG} ${NETWORK_PLUGIN} ${CLUSTER_DOMAIN} --image-gc-high-threshold=70 --image-gc-low-threshold=65
```

开始磁盘的使用率如下

![upload-image](/files/-MN2J9S9kWX3d8A7XnV_)

执行垃圾回收后，磁盘使用率降到了57%

![upload-image](/files/-MN2J9SAB1jLcL4P1to0)

查看kubelet日志可以发现执行垃圾回收成功

```
Sep 10 11:15:17 VM_1_4_centos kubelet[1134461]: I0910 11:15:17.054567 1134461 kubelet.go:1323] Image garbage collection succeeded
```

### 容器垃圾回收参数配置

![upload-image](/files/-MN2J9SBdPAx4tHQhgxH)

* minimum-container-ttl-duration：容器可被回收的最小生存年龄，默认是 0 分钟，这意味着每个死亡容器都会被立即执行垃圾回收
* maximum-dead-containers-per-container：每个 Pod 要保留的死亡容器的最大数量，默认值为 1
* maximum-dead-containers：节点可保留的死亡容器的最大数量，默认值是 -1，这意味着节点没有限制死亡容器数量

```
--maximum-dead-containers="-1"
--maximum-dead-containers-per-container="1"
--minimum-container-ttl-duration="0s"
```

注意：当MaxPerPodContainer与MaxContainers发生冲突时，Kubelet 会自动调整MaxPerPodContainer的取值以满足MaxContainers要求。

还是以 nginx 为例，创建一个 nginx 服务：

```
root@shida-machine:~# kubectl run nginx --image nginx
deployment.apps/nginx created
root@shida-machine:~# docker ps -a | grep nginx
7bef0308d9ea        nginx                     "nginx -g 'daemon of…"   16 seconds ago      Up 14 seconds                                 k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
7e65e0db52c2        k8s.gcr.io/pause:3.1      "/pause"                 2 minutes ago       Up 2 minutes                                  k8s_POD_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
12345
```

可以看到，Kubelet 启动了一个 sandbox 以及一个 nginx 实例。

手动杀死 nginx 实例，模拟容器异常退出：

```
root@shida-machine:~# docker kill 7bef0308d9ea
7bef0308d9ea
root@shida-machine:~# docker ps -a | grep nginx
408b23b2b72a        nginx                     "nginx -g 'daemon of…"   3 seconds ago       Up 2 seconds                                      k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_1
7bef0308d9ea        nginx                     "nginx -g 'daemon of…"   2 minutes ago       Exited (137) 15 seconds ago                       k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
7e65e0db52c2        k8s.gcr.io/pause:3.1      "/pause"                 5 minutes ago       Up 5 minutes                                      k8s_POD_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
123456
```

可以看到 Kubelet 重新拉起了一个新的 nginx 实例。

等待几分钟，发现 Kubelet 并未清理异常退出的 nginx 容器（因为此时仅有一个 dead container）。

```
root@shida-machine:~# docker ps -a | grep nginx
408b23b2b72a        nginx                     "nginx -g 'daemon of…"   3 minutes ago       Up 3 minutes                                     k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_1
7bef0308d9ea        nginx                     "nginx -g 'daemon of…"   5 minutes ago       Exited (137) 3 minutes ago                       k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
7e65e0db52c2        k8s.gcr.io/pause:3.1      "/pause"                 8 minutes ago       Up 8 minutes                                     k8s_POD_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
1234
```

继续杀死当前 nginx 实例：

```
root@shida-machine:~# docker kill 408b23b2b72a
408b23b2b72a
root@shida-machine:~# docker ps -a | grep nginx
e064e376819f        nginx                     "nginx -g 'daemon of…"   9 seconds ago       Up 7 seconds                                      k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_2
408b23b2b72a        nginx                     "nginx -g 'daemon of…"   5 minutes ago       Exited (137) 40 seconds ago                       k8s_nginx_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_1
7e65e0db52c2        k8s.gcr.io/pause:3.1      "/pause"                 10 minutes ago      Up 10 minutes                                     k8s_POD_nginx-7db9fccd9b-p2p2t_default_69c38c2b-a64e-11e9-94bd-000c29ce064a_0
123456
```

这下看到效果了，仍然只有一个退出的容器被保留，而且被清理掉的是最老的死亡容器，这与之前的分析是一致的！

删除这个 nginx Deployment，会发现所有的 nginx 容器都会被清理：

```
root@shida-machine:~# kubectl delete deployment nginx
deployment.extensions "nginx" deleted
root@shida-machine:~# docker ps -a | grep nginx
root@shida-machine:~# 
1234
```

进一步，我们修改 Kubelet 参数，设置 maximum-dead-containers 为 0，这就告诉 Kubelet 清理所有死亡容器。

重复前边的实验步骤：

```
root@shida-machine:~# kubectl run nginx --image nginx
deployment.apps/nginx created
root@shida-machine:~# docker ps -a | grep nginx
8de9ae8e2c9b        nginx                     "nginx -g 'daemon of…"   33 seconds ago      Up 32 seconds                                   k8s_nginx_nginx-7db9fccd9b-jl2xn_default_0cd67a29-a6a2-11e9-94bd-000c29ce064a_0
d2cdfafdbe50        k8s.gcr.io/pause:3.1      "/pause"                 41 seconds ago      Up 38 seconds                                   k8s_POD_nginx-7db9fccd9b-jl2xn_default_0cd67a29-a6a2-11e9-94bd-000c29ce064a_0
root@shida-machine:~# docker kill 8de9ae8e2c9b
8de9ae8e2c9b
root@shida-machine:~# docker ps -a | grep nginx
95ee5bd2cab2        nginx                     "nginx -g 'daemon of…"   About a minute ago   Up About a minute                             k8s_nginx_nginx-7db9fccd9b-jl2xn_default_0cd67a29-a6a2-11e9-94bd-000c29ce064a_1
d2cdfafdbe50        k8s.gcr.io/pause:3.1      "/pause"                 2 minutes ago        Up About a minute                             k8s_POD_nginx-7db9fccd9b-jl2xn_default_0cd67a29-a6a2-11e9-94bd-000c29ce064a_0
12345678910
```

结果显示，nginx Pod 的所有死亡容器都会被清理，因为我们已经强制要求节点不保留任何死亡容器，与预期一致！

那对于手动运行的容器呢？我们通过 docker run 运行 nginx：

```
root@shida-machine:~# docker run --name nginx -d nginx
46ebb365f6be060a6950f44728e4f11e4666bf2fb007cad557ffc65ecf8aded8
root@shida-machine:~# docker ps | grep nginx
46ebb365f6be        nginx                     "nginx -g 'daemon of…"   9 seconds ago       Up 6 seconds        80/tcp              nginx
1234
```

杀死该容器：

```
root@shida-machine:~# docker kill 46ebb365f6be
46ebb365f6be
root@shida-machine:~# docker ps -a | grep nginx
46ebb365f6be        nginx                     "nginx -g 'daemon of…"   About a minute ago   Exited (137) 18 seconds ago                       nginx
1234
```

经过几分钟，我们发现该死亡容器还是会存在的，Kubelet 不会清理这类容器！

小结

Kubelet 每 5 分钟进行一次镜像清理。当磁盘使用率超过上限阈值，Kubelet 会按照 LRU 策略逐一清理没有被任何容器所使用的镜像，直到磁盘使用率降到下限阈值或没有空闲镜像可以清理。Kubelet 认为镜像可被清理的标准是未被任何 Pod 容器（包括那些死亡了的容器）所引用，那些非 Pod 容器（如用户通过 docker run 启动的容器）是不会被用来计算镜像引用关系的。也就是说，即便用户运行的容器使用了 A 镜像，只要没有任何 Pod 容器使用到 A，那 A 镜像对于 Kubelet 而言就是可被回收的。但是我们无需担心手动运行容器使用的镜像会被意外回收，因为 Kubelet 的镜像删除是非 force 类型的，底层容器运行时会使存在容器关联的镜像删除操作失败（因为 Docker 会认为仍有容器使用着 A 镜像）。

Kubelet 每 1 分钟执行一次容器清理。根据启动配置参数，Kubelet 会按照 LRU 策略依次清理每个 Pod 内的死亡容器，直到达到死亡容器限制数要求，对于 sandbox 容器，Kubelet 仅会保留最新的（这不受 GC 策略的控制）。对于日志目录，只要已经没有 Pod 继续占用，就将其清理。对于非 Pod 容器（如用户通过 docker run 启动的容器）不会被 Kubelet 垃圾回收。

## 参考文档

<https://blog.csdn.net/shida_csdn/article/details/99734411>

<https://zhuanlan.zhihu.com/p/110869559>


# Kubernetes在pod中配置hosts解析域名

本篇文章介绍了如何给pod配置host域名解析

当 DNS 配置以及其它选项不合理的时候，通过向 Pod 的 /etc/hosts 文件中添加条目，可以在 Pod 级别覆盖对主机名的解析。在 1.7 版本后，用户可以通过 PodSpec 的 HostAliases 字段来添加这些自定义的条目。

建议通过使用 HostAliases 来进行修改，因为该文件由 Kubelet 管理，并且可以在 Pod 创建/重启过程中被重写

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "2"
  creationTimestamp: "2020-09-11T08:35:00Z"
  generation: 2
  labels:
    k8s-app: nginx-hosts-alis
    qcloud-app: nginx-hosts-alis
  name: nginx-hosts-alis
  namespace: test
  resourceVersion: "12673987137"
  selfLink: /apis/apps/v1beta2/namespaces/test/deployments/nginx-hosts-alis
  uid: 61493e8e-b0c9-4b5d-a031-6ea2799e9de8
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: nginx-hosts-alis
      qcloud-app: nginx-hosts-alis
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: nginx-hosts-alis
        qcloud-app: nginx-hosts-alis
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx-hosts-alis
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      hostAliases:
      - hostnames:
        - foo.local
        - bar.local
        ip: 127.0.0.1
      - hostnames:
        - foo.remote
        - bar.remote
        ip: 10.1.2.3
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
```

yaml修改好之后，我们可以进入pod内进行验证，查看下pod的/etc/hosts文件是否有加上配置的域名解析

```
root@nginx-hosts-alis-5db8d7c54c-gf6km:/# cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
172.16.2.188    nginx-hosts-alis-5db8d7c54c-gf6km

# Entries added by HostAliases.
127.0.0.1       foo.local       bar.local
10.1.2.3        foo.remote      bar.remote
```

从上面的结果看，这里域名解析已经加入到对应的pod中。

注意事项：这里修改yaml需要注意字段的缩进，HostAliases是在.spec.hostAliases这个层级，和containers是同级的。


# Kubernetes之helm部署使用

本篇文章主要介绍了如何通过helm部署应用到k8s集群中

## Helm的介绍

Helm就相当于kubernetes环境下的yum包管理工具。

### 用途

做为 Kubernetes 的一个包管理工具，Helm具有如下功能：

* 创建新的 chart
* chart 打包成 tgz 格式
* 上传 chart 到 chart 仓库或从仓库中下载 chart
* 在Kubernetes集群中安装或卸载 chart
* 管理用Helm安装的 chart 的发布周期

### 重要概念

Helm 有三个重要概念：

* chart：包含了创建Kubernetes的一个应用实例的必要信息
* config：包含了应用发布配置信息
* release：是一个 chart 及其配置的一个运行实例

### Helm组件

Helm 有以下两个组成部分：

#### Helm Client 是用户命令行工具，其主要负责如下：

* 本地 chart 开发
* 仓库管理
* 与 Tiller sever 交互
* 发送预安装的 chart
* 查询 release 信息
* 要求升级或卸载已存在的 release

#### Tiller Server主要负责如

是一个部署在Kubernetes集群内部的 server，其与 Helm client、Kubernetes API server 进行交互

* 监听来自 Helm client 的请求
* 通过 chart 及其配置构建一次发布
* 安装 chart 到Kubernetes集群，并跟踪随后的发布
* 通过与Kubernetes交互升级或卸载 chart
* 简单的说，client 管理 charts，而 server 管理发布 release

## Helm的安装部署

下载客户端安装包安装

```
wget https://get.helm.sh/helm-v2.14.3-linux-amd64.tar.gz
tar -xf helm-v2.14.3-linux-amd64.tar.gz
cd helm-v2.14.3-linux-amd64/
cp helm /usr/local/bin/
```

通过脚本下载安装

```
$ curl https://raw.githubusercontent.com/helm/helm/master/scripts/get > get_helm.sh
$ chmod 700 get_helm.sh
$ ./get_helm.sh
```

helm 服务端安装Tiller。默认会部署swift和tiller这2个pod在kube-system命名空间下

```
[root@VM-6-17-centos helm-data]# helm init
[root@VM-6-17-centos helm-data]# kubectl get pod -n kube-system 
NAME                                    READY   STATUS    RESTARTS   AGE
swift-566d576-jtv9q                     2/2     Running   0          22h
tiller-deploy-698956c985-brzps          1/1     Running   1          33h
```

安装正常后可以通过查看helm版本来检查客户端和服务端是否安装正常

```
[root@VM-6-17-centos helm-data]# helm version
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
```

另外一个值得注意的问题是RBAC，我们的 kubernetes 集群是1.8.x版本的，默认开启了RBAC访问控制，所以我们需要为Tiller创建一个ServiceAccount，让他拥有执行的权限，详细内容可以查看 Helm 文档中的Role-based Access Control创建rbac.yaml

```
apiVersion: v1
kind: ServiceAccount
metadata:
 name: tiller
 namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: tiller
  namespace: kube-system
```

然后使用kubectl创建：

```
$ kubectl create -f rbac-config.yaml
serviceaccount "tiller" created
clusterrolebinding "tiller" created
```

创建了tiller的 ServceAccount 后还没完，因为我们的 Tiller 之前已经就部署成功了，而且是没有指定 ServiceAccount 的，所以我们需要给 Tiller 打上一个 ServiceAccount 的补丁

```
$ kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
```

至此, Helm客户端和服务端都配置完成了，接下来我们看看如何使用吧。

**安装遇到的问题**

1. 由于 Helm 默认会去gcr.io拉取镜像，所以如果你当前执行的机器没有配置科学上网的话可以实现下面的命令代替：

```
helm init -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.14.3 --stable-repo-url http://mirror.azure.cn/kubernetes/charts/ --service-account tiller --override spec.selector.matchLabels.'name'='tiller',spec.selector.matchLabels.'app'='helm' --output yaml | sed 's@apiVersion: extensions/v1beta1@apiVersion: apps/v1@' | kubectl apply -f -
```

1. 我在安装过程中遇到了一些其他问题，比如初始化的时候出现了如下错误：

```
E0125 14:03:19.093131   56246 portforward.go:331] an error occurred forwarding 55943 -> 44134: error forwarding port 44134 to pod d01941068c9dfea1c9e46127578994d1cf8bc34c971ff109dc6faa4c05043a6e, uid : unable to do port forwarding: socat not found.
2018/01/25 14:03:19 (0xc420476210) (0xc4203ae1e0) Stream removed, broadcasting: 3
2018/01/25 14:03:19 (0xc4203ae1e0) (3) Writing data frame
2018/01/25 14:03:19 (0xc420476210) (0xc4200c3900) Create stream
2018/01/25 14:03:19 (0xc420476210) (0xc4200c3900) Stream added, broadcasting: 5
Error: cannot connect to Tiller
```

解决方案：在节点上安装socat可以解决

```
$ sudo yum install -y socat
```

## helm的使用

### 仓库

```
[root@master hello-helm]# helm repo list
NAME URL
stable https://kubernetes-charts.storage.googleapis.com
local http://127.0.0.1:8879/charts
替换 repo 为阿里镜像
root@rancherk8sm1:~# helm repo list
NAME URL
stable https://kubernetes-charts.storage.googleapis.com
local http://127.0.0.1:8879/charts

root@rancherk8sm1:~# helm repo remove stable
"stable" has been removed from your repositories

root@rancherk8sm1:~# helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
"stable" has been added to your repositories

root@rancherk8sm1:~# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈

root@rancherk8sm1:~# helm repo list
NAME URL
local http://127.0.0.1:8879/charts
stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
```

我们可以在本地启动一个 Repository Server，并将其加入到 Helm Repo 列表中。Helm Repository 必须以 Web 服务的方式提供，这里我们就使用 helm serve 命令启动一个 Repository Server，该 Server 缺省使用 $HOME/.helm/repository/local 目录作为 Chart 存储，并在 8879 端口上提供服务。

```
$ helm serve &
Now serving you on 127.0.0.1:8879
```

默认情况下该服务只监听 127.0.0.1，如果你要绑定到其它网络接口，可使用以下命令：

```
$ helm serve --address 192.168.100.211:8879 &
```

如果你想使用指定目录来做为 Helm Repository 的存储目录，可以加上 --repo-path 参数：

```
$ helm serve --address 192.168.100.211:8879 --repo-path /data/helm/repository/ --url http://192.168.100.211:8879/charts/
```

通过 helm repo index 命令将 Chart 的 Metadata 记录更新在 index.yaml 文件中:

```
# 更新 Helm Repository 的索引文件
$ cd /home/k8s/.helm/repository/local
$ helm repo index --url=http://192.168.100.211:8879 .
```

完成启动本地 Helm Repository Server 后，就可以将本地 Repository 加入 Helm 的 Repo 列表。

```
$ helm repo add local http://127.0.0.1:8879
"local" has been added to your repositories
```

### 查找chart

```
helm search
```

如果没有使用过滤条件，helm search 显示所有可用的 charts

```
helm search mysql
helm inspect stable/mysql
```

使用 inspect 命令可以查看到该 chart 里面所有描述信息，包括运行方式、配置信息等等。

### 安装chart

```
helm install stable/mysql
```

安装 chart 会创建一个新 release 对象。上面的 release 被命名为 torrid-gnat

```
helm install stable/mysql --name mydb
```

如果你想使用你自己的 release 名称，只需使用--name参数指定即可

```
helm status mydb
```

要跟踪 release 状态或重新读取配置信息，可以使用 helm status 查看

### 自定义chart

```
helm inspect values stable/mysql
```

要查看 chart 上可配置的选项，使用helm inspect values命令即可，比如我们这里查看上面的 mysql 的配置选项：

然后，我们可以直接在 YAML 格式的文件中来覆盖上面的任何配置，在安装的时候直接使用该配置文件即可：(config.yaml)

```
mysqlUser: haimaxyUser
mysqlDatabase: haimaxyDB
service:
 type: NodePort
```

我们这里通过 config.yaml 文件定义了 mysqlUser 和 mysqlDatabase，并且把 service 的类型更改为了 NodePort，然后现在我们来安装的时候直接指定该 yaml 文件：

```
$ helm install -f config.yaml stable/mysql --name mydb
```

可以看到当前 release 的名字已经变成 mydb 了。然后可以查看下 mydb 关联的 Service 是否变成 NodePort 类型的了：

```
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1  <none> 443/TCP 110d
mewing-squid-mysql ClusterIP 10.108.197.48 <none> 3306/TCP 46m
mydb-mysql NodePort 10.96.150.198 <none> 3306:32604/TCP 8m
```

看到服务 mydb-mysql 变成了 NodePort 类型的，二之前默认创建的 mewing-squid-mysql 是 ClusterIP 类型的，证明上面我们通过 YAML 文件来覆盖 values 是成功的。

接下来我们查看下 Pod 的状况：

```
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mewing-squid-mysql-69f587bdf9-z7glv 0/1 Pending 0 49m
mydb-mysql-dfc999888-hbw5d 0/1 Pending 0 11m
```

比较奇怪的是之前默认创建的和现在的 mydb 的 release 创建的 Pod 都是 Pending 状态，直接使用 describe 命令查看下：

```
$ kubectl describe pod mydb-mysql-dfc999888-hbw5d
Name: mydb-mysql-dfc999888-hbw5d
Namespace:  default
Node: <none>
Labels: app=mydb-mysql
 pod-template-hash=897555444
...
Events:
 Type Reason Age From Message
 ---- ------ ---- ---- -------
 Warning FailedScheduling 2m (x37 over 12m) default-scheduler pod has unbound PersistentVolumeClaims (repeated 2 times)
```

我们可以发现两个 Pod 处于 Pending 状态的原因都是 PVC 没有被绑定上，所以这里我们可以通过 storageclass 或者手动创建一个合适的 PV 对象来解决这个问题。

另外为了说明 helm 更新的用法，我们这里来直接禁用掉数据持久化，可以在上面的config.yaml 文件中设置：

```
persistence:
 enabled: false
```

另外一种方法就是在安装过程中使用--set来覆盖对应的 value 值，比如禁用数据持久化，我们这里可以这样来覆盖：

```
$ helm install stable/mysql --set persistence.enabled=false --name mydb
```

### 升级chart

我们这里将数据持久化禁用掉来对上面的 mydb 进行升级：

```
$ echo config.yaml
mysqlUser: haimaxyUser
mysqlDatabase: haimaxyDB
service:
 type: NodePort
persistence:
 enabled: false
$ helm upgrade -f config.yaml mydb stable/mysql
helm upgrade -f config.yaml mydb stable/mysql
Release "mydb" has been upgraded. Happy Helming!
LAST DEPLOYED: Wed Sep 5 00:38:33 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
...
```

可以看到已经变成 DEPLOYED 状态了，现在我们再去看看 Pod 的状态呢：

```
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mewing-squid-mysql-69f587bdf9-z7glv 0/1 Pending 0 1h
mydb-mysql-6ffc84bbf6-lcn4d 0/1 PodInitializing 0 49s
```

我们可以看到现在没有任何关于 PVC 的错误信息了，这是因为我们刚刚更新的版本中就是禁用掉了的数据持久化的，证明 helm upgrade 和 –values 是生效了的。现在我们使用 helm ls 命令查看先当前的 release：

```
$ helm ls
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
mewing-squid 1 Tue Sep 4 23:31:23 2018 DEPLOYED mysql-0.10.1 5.7.14 default
mydb 2 Wed Sep 5 00:38:33 2018 DEPLOYED mysql-0.10.1 5.7.14 default
```

可以看到 mydb 这个 release 的REVISION已经变成2了，这是因为 release 的版本是递增的，每次安装、升级或者回滚，版本号都会加1，第一个版本号始终为1，同样我们可以使用 helm history 命令查看 release 的历史版本：

```
$ helm history mydb
REVISION UPDATED STATUS CHART DESCRIPTION
1 Wed Sep 5 00:09:44 2018 SUPERSEDED mysql-0.10.1 Install complete
2 Wed Sep 5 00:38:33 2018 DEPLOYED mysql-0.10.1 Upgrade complete
```

当然如果我们要回滚到某一个版本的话，使用 helm rollback 命令即可，比如我们将 mydb 回滚到上一个版本：

```
$ helm rollback mydb 1
```

### 删除chart

我们就学习了要删除一个 release 直接使用 helm delete 命令就 OK：

```
$ helm delete mewing-squid
release "mewing-squid" deleted
```

这将从集群中删除该 release，但是这并不代表就完全删除了，我们还可以通过--deleted参数来显示被删除掉 release:

```
$ helm list --deleted
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
mewing-squid 1 Tue Sep 4 23:31:23 2018 DELETED mysql-0.10.1 5.7.14 default
$ helm list --all
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
mewing-squid 1 Tue Sep 4 23:31:23 2018 DELETED mysql-0.10.1 5.7.14 default
mydb 2 Wed Sep 5 00:38:33 2018 DEPLOYED mysql-0.10.1 5.7.14 default
```

helm list --all则会显示所有的 release，包括已经被删除的

由于 Helm 保留已删除 release 的记录，因此不能重新使用 release 名称。（如果 确实 需要重新使用此 release 名称，则可以使用此 –replace 参数，但它只会重用现有 release 并替换其资源。）这点是不是和 docker container 的管理比较类似

请注意，因为 release 以这种方式保存，所以可以回滚已删除的资源并重新激活它。

如果要彻底删除 release，则需要加上--purge参数：

```
$ helm delete mewing-squid --purge
release "mewing-squid" deleted
```

### helm的打包和上传下载

打包发布到本地repo

```
[root@VM-6-17-centos ~]# helm package jenkins --debug
Successfully packaged chart and saved it to: /root/jenkins-0.13.5.tgz
[debug] Successfully saved /root/jenkins-0.13.5.tgz to /root/.helm/repository/local
```

打包发布到远程repo

```
[root@VM-6-17-centos ~]# helm plugin install https://github.com/chartmuseum/helm-push
[root@VM-6-17-centos ~]# helm push ./jenkins nwx-tcr-nwx-ns
Pushing jenkins-0.13.5.tgz to nwx-tcr-nwx-ns...
Done.
[root@VM-6-17-centos ~]# cd /root/.helm/repository/local
[root@VM-6-17-centos local]# ll
total 24
-rw-r--r-- 1 root root  1241 Sep 11 20:28 index.yaml
-rw-r--r-- 1 root root 12866 Sep 11 20:28 jenkins-0.13.5.tgz
-rw-r--r-- 1 root root  2595 Jun  5 10:05 mongodb-0.1.0.tgz
[root@VM-6-17-centos local]# helm push jenkins-0.13.5.tgz nwx-tcr-nwx-ns
Pushing jenkins-0.13.5.tgz to nwx-tcr-nwx-ns...
Done.
```

下载chart包

```
helm fetch nwx-tcr-nwx-ns/jenkins --version 0.13.5
```

有的时候我们需要填写第三方的chart包，该如何填写呢？我们拿es的官方库来举例说明

登录这个网站<https://helm.elastic.co/，可以根据网站key去获取对用的chart包>

### helm包托管到到腾讯云的TCR上

首先在TCR上开通实例，并将实例开通外网访问，配置helm客户端所在机器的ip到外网访问白名单

![upload-image](/files/-MN2J9UHGhkCq9gSHMCA)

可以手动将chart包上传到仓库上

![upload-image](/files/-MN2J9UIkB8B0sKb1D79)

这边也可以用命令将包上传

```
# helm repo add nwx-tcr-nwx-ns https://nwx-tcr.tencentcloudcr.com/chartrepo/nwx-ns --username XXXXXXX --password eyJhbGciOiJSUzI1NiIsImtpZCI6Ikg0SjM6WFJQVjo0QkRDOkNDWUI6SUJVUzpUNjdUOkxXSlc6WUNMRjo2WkNCOkNDVFY6UlE3RzpJT1pBIn0.eyJvd25lclVpbiI6IjIxNTk5NzM0MTciLCJvcGVyYXRvclVpbiI6IjEwMDAxMTAwNzQ5MSIsImV4cCI6MTU5OTgzMjMzNCwibmJmIjoxNTk5ODI4NzM0LCJpYXQiOjE1OTk4Mjg3MzR9.SYaux93fNpZMjltRbITA-AZr3b7RVEfZdK3cQ89qJByZvc733EBUoonSNGPmlU0YhgjVOJrrBcSoZGG12euQjT62FaWNbN_QhEOUEWZ84BekKHIYKXRFP9B78epXmzpJKrU-uoD4GLxduBMkmnK1S1kbRgoYdS3uRSqrGdO9rblk6Zut_s96TyibBicExheJOXfigjLThjkhnA4wR6TxY48qzpjNp12xnA394R7VTQQFCsV4X3En5aaeOZgjcjLB8-LmJr0qtenAkc_cqplMYncnbXhN8owAhFjYuYBF-
# helm plugin install https://github.com/chartmuseum/helm-push
# helm push ./jenkins nwx-tcr-nwx-ns
# helm push jenkins-0.13.5.tgz nwx-tcr-nwx-ns
```

从TCR上下载helm包，直接浏览器下载到本地

![upload-image](/files/-MN2J9UJXEyH9hNlvyxk)

用命令下载到liunx机器上

```
helm fetch nwx-tcr-nwx-ns/jenkins --version 0.13.5
```

查看chart包的yaml文件信息

![upload-image](/files/-MN2J9UKpLBtDpUgz1T1)

## Helm3更新和部署

### 移除了Tiller

在Helm 2的开发周期中，我们引入了Tiller。Tiller在团队协作中共享集群时扮演了重要角色。 它使得不同的操作员与相同的版本进行交互称为了可能。

Kubernetes 1.6默认使用了基于角色的访问控制（RBAC），在生产环境对Tiller的锁定使用变得难于管理。 由于大量可能的安全策略，我们的立场是提供一个自由的默认配置。这样可以允许新手用户可以乐于尝试Helm 和Kubernetes而不需要深挖安全控制。 不幸的是这种自由的配置会授予用户他们不该有的权限。DevOps和SRE 在安装多用户集群时不得不去学习额外的操作步骤。

在听取了社区成员在特定场景使用Helm之后，我们发现Tiller的版本管理系统不需要依赖于集群内部用户去维护 状态或者作为一个Helm版本信息的中心hub。取而代之的是，我们可以简单地从Kubernetes API server获取信息， 在Chart客户端处理并在Kubernetes中存储安装记录。

Tiller的首要目标可以在没有Tiller的情况下实现，因此针对于 Helm 3 我们做的首要决定之一就是完全移除Tiller。

随着Tiller的消失，Helm的安全模块从根本上被简化。Helm 3 现在支持所有Kubernetes流行的安全、 身份和授权特性。Helm的权限通过你的 kubeconfig文件进行评估。 集群管理员可以限制用户权限，只要他们觉得合适， 无论什么粒度都可以做到。版本发布记录和Helm的剩余保留功能仍然会被记录在集群中。

这边命令行操作只需按照客户端，按照步骤如下

```
wget https://get.helm.sh/helm-v3.4.1-linux-amd64.tar.gz
# 只需要下载二进制即安装完成
mv linux-amd64/helm /usr/local/bin/helm
```

### helm3回滚策略

你的团队正好在Kubernetes上使用Helm部署了生产环境应用。chart包含了一个部署对象使用了三套副本：

```
$ helm install myapp ./myapp
```

开发新人加入了团队。当他们第一点观察生产环境集群时，发生了一个像是咖啡洒在了键盘上一样的严重事故， 他们使用 kubectl scale 对生产环境部署进行缩容，将副本数从3降到了0 。

```
$ kubectl scale --replicas=0 deployment/myapp
```

团队里面的另一个人看到线上环境已经挂了就决定回滚这个版本到之前的状态：

```
$ helm rollback myapp
```

### 发布名称现在限制在namespace范围内

在Helm 3中，特定的版本信息作为发布本身存储在相同的命名空间中。 意味着用户现在可以在两个分开的命名空间中使用helm install wordpress stable/wordpress， 并且每个都能使用 helm list 改变当前命名空间。 (例如 helm list --namespace foo)。

与本地集群命名空间更好的一致性，使得 helm list 命令不再需要默认列出所有发布版本的列表。 取而代之的是，仅仅会在命名空间中列出当前kubernetes上下文的版本。 (也就是说运行kubectl config view --minify时会显示命名空间). 也就意味着您在执行helm list时必须提供 --all-namespaces 标识才能获得和Helm 2同样的结果。

### 使用Json格式验证Chart Values

chart values现在可以使用JSON结构了。这保证用户提供value可以按照chart维护人员设置的结构排列， 并且当用户提供了错误的chart value时会有更好错误提示。

当调用以下命令时会进行JSON格式验证：

```
helm install
helm upgrade
helm template
helm lint
```

### 安装时必须指定Name(或者--generate-name)

Helm 2中，如果没有提供名称， 会自动生成一个名称。在生产环境，这被证明是一个麻烦事而不是一个有用的特性。 而在Helm 3中，如果 helm install 没有提供name，会抛异常。

如果仍然需要一个自动生成的名称，您可以使用 --generate-name 创建。

### 命令行重命名

为了更好地从包管理器中调整不当措辞，helm delete被重命名为helm uninstall。helm delete 依然作为 helm uninstall 的别名保留，因此其他格式也能使用。

Helm 2 中为了清除版本清单，必须提供--purge参数。这个功能现在是默认使用的。 为保留之前的操作行为，要使用 helm uninstall --keep-history。

另外，其他一些重命名的命令提供了以下约定：

helm inspect -> helm show helm fetch -> helm pull

这些命令都保留了老的动词作为别名，因此您能够使用任意一种格式。

### 命名空间不存在则报错

当用命名空间创建版本时，命名空间不存在，Helm 2会创建一个命名空间。 Helm 3中沿用了其他Kubernetes 工具的形式，如果命名空间不存在，就返回错误。 如果您明确指定 --create-namespace 参数，Helm 3 会创建一个命名空间。


# kubeadm部署k8s

本文主要介绍了如何在centos上采用kubeadm搭建k8s集群。

## 环境准备

### 服务器

master01:192.168.1.110 （最少2核CPU）\
node01:192.168.1.100

### 规划

services网络：10.96.0.0/12\
pod网络：10.244.0.0/16

## 配置hosts解析各主机

```
vim /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.1.110 master01
192.168.1.100 node01
```

## 同步各主机时间

```
yum install -y ntpdate
ntpdate time.windows.com
14 Mar 16:51:32 ntpdate[46363]: adjust time server 13.65.88.161 offset -0.001108 sec
```

## 关闭SWAP，关闭selinux

```
swapoff -a
vim /etc/selinux/config

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
```

## 安装docker-ce

```
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast
yum -y install docker-ce

Docker 安装后出现：WARNING: bridge-nf-call-iptables is disabled 的解决办法
vim /etc/sysctl.conf

# sysctl settings are defined through files in
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
#
# Vendors settings live in /usr/lib/sysctl.d/.
# To override a whole file, create a new file with the same in
# /etc/sysctl.d/ and put new settings there. To override
# only specific settings, add a file with a lexically later
# name in /etc/sysctl.d/ and put new settings there.
#
# For more information, see sysctl.conf(5) and sysctl.d(5).
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1

systemctl enable docker && systemctl start docker
```

## 安装kubernetes

```
cat <<EOF > /etc/yum.repos.d/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
EOF

setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
```

## 初始化集群

```
kubeadm init --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.13.1 --pod-network-cidr=10.244.0.0/16
Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the following on each node
as root:

  kubeadm join 192.168.1.110:6443 --token wgrs62.vy0trlpuwtm5jd75 --discovery-token-ca-cert-hash sha256:6e947e63b176acf976899483d41148609a6e109067ed6970b9fbca8d9261c8d0
```

## 手动部署flannel

```
flannel网址：https://github.com/coreos/flannel for Kubernetes v1.7+

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
podsecuritypolicy.extensions/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.extensions/kube-flannel-ds-amd64 created
daemonset.extensions/kube-flannel-ds-arm64 created
daemonset.extensions/kube-flannel-ds-arm created
daemonset.extensions/kube-flannel-ds-ppc64le created
daemonset.extensions/kube-flannel-ds-s390x created
```

## node配置

安装docker kubelet kubeadm docker安装同步骤4。\
kubelet kubeadm安装同步骤5

## node加入到master

```
kubeadm join 192.168.1.110:6443 --token wgrs62.vy0trlpuwtm5jd75 --discovery-token-ca-cert-hash sha256:6e947e63b176acf976899483d41148609a6e109067ed6970b9fbca8d9261c8d0
kubectl get nodes  #查看node状态
NAME                    STATUS     ROLES    AGE     VERSION
localhost.localdomain   NotReady   <none>   130m    v1.13.4
master01                Ready      master   4h47m   v1.13.4
node01                  Ready      <none>   94m     v1.13.4

kubectl get cs  #查看组件状态
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok                   
controller-manager   Healthy   ok                   
etcd-0               Healthy   {"health": "true"}  

kubectl get ns  #查看名称空间
NAME          STATUS   AGE
default       Active   4h41m
kube-public   Active   4h41m
kube-system   Active   4h41m

kubectl get pods -n kube-system  #查看pod状态
NAME                               READY   STATUS    RESTARTS   AGE
coredns-78d4cf999f-bszbk           1/1     Running   0          4h44m
coredns-78d4cf999f-j68hb           1/1     Running   0          4h44m
etcd-master01                      1/1     Running   0          4h43m
kube-apiserver-master01            1/1     Running   1          4h43m
kube-controller-manager-master01   1/1     Running   2          4h43m
kube-flannel-ds-amd64-27x59        1/1     Running   1          126m
kube-flannel-ds-amd64-5sxgk        1/1     Running   0          140m
kube-flannel-ds-amd64-xvrbw        1/1     Running   0          91m
kube-proxy-4pbdf                   1/1     Running   0          91m
kube-proxy-9fmrl                   1/1     Running   0          4h44m
kube-proxy-nwkl9                   1/1     Running   0          126m
kube-scheduler-master01            1/1     Running   2          4h43m
```


# TKE使用自建NFS持久化存储

使用TKE的过程中，我们需要把pod一些文件持久化存储到外部，这边我们会用到nfs存储，其实在腾讯云上有CFS服务，可以用CFS作为文件存储服务器，TKE也支持将文件挂载到CFS上存储。但是如果你想自己管理nfs服务器，这边也可以通过自建nfs服务器来作为tke集群中pod存储。下面我们来说一下如何将pod的文件挂载到自建的nfs服务器来进行存储。

## 创建nfs服务器

首先我们先在腾讯云上申请一台cvm服务器，这边建议将对应的磁盘空间配置大点，并且cvm服务器的网络需要和tke集群处于一个vpc内，这样TKE集群可以通过内网直接访问nfs服务器进行挂载

### centos部署nfs服务端

```
# yum -y install nfs-utils rpcbind
# systemctl start rpcbind
# systemctl enable rpcbind
```

### ubuntu部署nfs服务端

```
# sudo apt-get install nfs-kernel-server
# sudo service nfs-kernel-server restart
# sudo systemctl enable nfs-kernel-server.service
```

## 配置nfs的挂载目录

下面我们以centos系统为例进行操作实例，我们在nfs服务器上创建好pod需要挂载的目录，并给对应的目录编辑共享配置文件设置好权限，然后重启nfs服务器。

```
# mkdir -p /data/volums
# vi /etc/exports
/data/volums *(async,insecure,no_root_squash,no_subtree_check,rw)
# systemctl restart rpcbind
```

/etc/exports配置文件参数及作用说明

* ro：只读
* rw：读写
* root\_squash：当NFS客户端以root管理员访问时，映射为NFS服务器的匿名用户
* no\_root\_squash：当NFS客户端以root管理员访问时，映射为NFS服务器的root管理员
* all\_squash：无论NFS客户端使用什么账户访问，均映射为NFS服务器的匿名用户
* sync：同时将数据写入到内存与硬盘中，保证不丢失数据
* async：优先将数据保存到内存，然后再写入硬盘；这样效率更高，但可能会丢失数据
* insecure：允许客户端从大于1024的tcp/ip端口连接服务器

```
[root@VM-1-2-centos volums]# showmount -e 10.168.1.2
Export list for 10.168.1.2:
/data/volums *
```

查看nfs服务器上的共享状态，出现上述的内容输出，这边说明/data/volums可以进行挂载了

## 节点安装nfs客户端工具

因为k8s的调度机制，这边如果不指定调度，会随机指定节点调度，所以这边建议所有节点都按照nfs的客户端，保证pod调度到节点上可以执行挂载操作。

### centos部署nfs客户端工具

```
yum -y install nfs-utils
```

### ubuntu部署nfs客户端工具

```
sudo apt install nfs-common
```

## 集群中部署nfs客户端nfs-client-provisioner

这边我们在TKE集群中通过部署nfs-client-provisioner客户端工具。

### 通过rbac给nfs-client-provisioner分配权限

```
kind: ServiceAccount
apiVersion: v1
metadata:
  name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
```

### 创建nfs-client-provisioner

```
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 10.168.1.2
            - name: NFS_PATH
              value: /data/volums
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.168.1.2
            path: /data/volums
```

### 创建nfs对应的StorageClass

```
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false"
4.4 创建nfs类型的pvc
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
```

## 创建pod挂载到自建nfs服务器

### 直接挂载到nfs盘的目录下

![upload-image](/files/-MN2J8sCvtgk3ZCXXJ-S)

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: "2020-09-17T12:01:00Z"
  generation: 1
  labels:
    k8s-app: nginx-cfs-server
    qcloud-app: nginx-cfs-server
  name: nginx-cfs-server
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: nginx-cfs-server
      qcloud-app: nginx-cfs-server
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: nginx-cfs-server
        qcloud-app: nginx-cfs-server
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx-cfs-server
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: vol
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: qcloudregistrykey
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - name: vol
        nfs:
          path: /data/volums/nginx
          server: 10.168.1.2
```

下面我们在容器内创建几个文件，看下在nfs服务器下/data/volums/nginx的目录是否也有，我们在容器内创建test1和test2

```
root@nginx-cfs-server-69fb8cb9f6-2hzc6:/etc/nginx/conf.d# ls
root@nginx-cfs-server-69fb8cb9f6-2hzc6:/etc/nginx/conf.d# echo "test1" > test1
root@nginx-cfs-server-69fb8cb9f6-2hzc6:/etc/nginx/conf.d# echo "test2" > test2
root@nginx-cfs-server-69fb8cb9f6-2hzc6:/etc/nginx/conf.d# ls
test1  test2
root@nginx-cfs-server-69fb8cb9f6-2hzc6:/etc/nginx/conf.d# cat test1
test1
root@nginx-cfs-server-69fb8cb9f6-2hzc6:/etc/nginx/conf.d# cat test2
test2
```

去nfs的/data/volums/nginx目录验证下

```
[root@VM-1-2-centos nginx]# ll
total 8
-rw-r--r-- 1 root root 6 Sep 17 20:04 test1
-rw-r--r-- 1 root root 6 Sep 17 20:04 test2
[root@VM-1-2-centos nginx]# pwd
/data/volums/nginx
[root@VM-1-2-centos nginx]# cat test1
test1
[root@VM-1-2-centos nginx]# cat test2
test2
[root@VM-1-2-centos nginx]# pwd
/data/volums/nginx
```

对应的文件已成功挂载到nfs服务中

### 通过pvc挂载某个目录到nfs服务器

这边我们将上面创建的test-claim这个pvc挂载到容器内

![upload-image](/files/-MN2J8sEk9NwCp3mRHCp)

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: "2020-09-17T11:53:11Z"
  generation: 1
  labels:
    k8s-app: nginx-nfs
    qcloud-app: nginx-nfs
  name: nginx-nfs
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: nginx-nfs
      qcloud-app: nginx-nfs
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: nginx-nfs
        qcloud-app: nginx-nfs
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx-nfs
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: vol
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: qcloudregistrykey
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - name: vol
        persistentVolumeClaim:
          claimName: test-claim
```

下面我们做个简单的验证，在容器内创建文件，看下对应的pvc下会不会有文件生成

![upload-image](/files/-MN2J8sGxPEVneUCAkLS)

```
[root@VM-1-2-centos volums]# ll
total 8
-rw-r--r-- 1 root root    0 Sep  6 17:58 aa
-rw-r--r-- 1 root root    0 Sep  6 17:58 aab
drwxrwxrwx 2 root root 4096 Sep 17 20:47 default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92
drwxr-xr-x 2 root root 4096 Sep 17 20:04 nginx
[root@VM-1-2-centos volums]# cd default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92/
[root@VM-1-2-centos default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92]# ll
total 4
-rw-r--r-- 1 root root 11 Sep 17 20:47 hello
[root@VM-1-2-centos default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92]# cat hello 
hello word
[root@VM-1-2-centos default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92]# ls
hello
[root@VM-1-2-centos default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92]# cat hello 
hello word
```

容器内的test文件成功挂载到pvc卷的目录。

### 动态创建pvc挂载到nfs上

k8s中只有StatefulSet需要动态创建pvc来挂载每一个pod内生成的文件，下面我们创建一个StatefulSet来动态创建pvc挂载到容器上。

```
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: web
spec:
  replicas: 2
  volumeClaimTemplates:
  - metadata:
      name: test
      annotations:
        volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
    spec:
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 1Gi
  template:
    metadata:
      labels:
        app: nginx1
    spec:
      serviceAccount: nfs-provisioner
      containers:
      - name: nginx1
        image: nginx
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: "/etc/nginx/conf.d"
          name: test
```

执行上面的yaml创建2个pod，然后登录pod创建文件，这边我们验证下是否挂载成功

```
root@web-0:/# cd /etc/nginx/conf.d/
root@web-0:/etc/nginx/conf.d# echo "web0" > a.txt
root@web-0:/etc/nginx/conf.d# ls
a.txt
root@web-0:/etc/nginx/conf.d# cat a.txt 
web0


root@web-1:/# cd /etc/nginx/conf.d/
root@web-1:/etc/nginx/conf.d# ls 
root@web-1:/etc/nginx/conf.d# echo "web1" > 2.txt
root@web-1:/etc/nginx/conf.d# ls
2.txt
root@web-1:/etc/nginx/conf.d# cat 2.txt 
web1
root
```

我们登录到nfs服务器上查看下pod对应的pvc所在目录是否有文件生成

```
[root@VM-1-2-centos volums]# ll
total 16
-rw-r--r-- 1 root root    0 Sep  6 17:58 aa
-rw-r--r-- 1 root root    0 Sep  6 17:58 aab
drwxrwxrwx 2 root root 4096 Sep 17 20:47 default-test-claim-pvc-6927bda4-1e81-4187-af6f-611e07e45a92
drwxrwxrwx 2 root root 4096 Sep 17 20:59 default-test-web-0-pvc-e8e6637c-76a5-4256-b589-c29d31f19afe
drwxrwxrwx 2 root root 4096 Sep 17 21:01 default-test-web-1-pvc-3298add1-0626-4973-ae47-4cae5885f024
drwxr-xr-x 2 root root 4096 Sep 17 20:04 nginx
[root@VM-1-2-centos volums]# cd default-test-web-0-pvc-e8e6637c-76a5-4256-b589-c29d31f19afe
[root@VM-1-2-centos default-test-web-0-pvc-e8e6637c-76a5-4256-b589-c29d31f19afe]# ls
a.txt
[root@VM-1-2-centos default-test-web-0-pvc-e8e6637c-76a5-4256-b589-c29d31f19afe]# cat a.txt 
web0
[root@VM-1-2-centos default-test-web-0-pvc-e8e6637c-76a5-4256-b589-c29d31f19afe]# cd ..
[root@VM-1-2-centos volums]# cd default-test-web-1-pvc-3298add1-0626-4973-ae47-4cae5885f024
[root@VM-1-2-centos default-test-web-1-pvc-3298add1-0626-4973-ae47-4cae5885f024]# ls
2.txt
[root@VM-1-2-centos default-test-web-1-pvc-3298add1-0626-4973-ae47-4cae5885f024]# cat 2.txt 
web1
```

从上面查看，对应的文件已在服务器上挂载成功。


# 二进制部署k8s

本文主要介绍了如何在centos上采用二进制搭建k8s集群。

## 软件环境

| 软件         | 版本             |
| ---------- | -------------- |
| 操作系统       | CentOS7.6\_x64 |
| Docker     | 19-ce          |
| Kubernetes | 1.15.12        |

## 服务器角色

| 角色 | IP | 组件 |
| -- | -- | -- |

k8s-master| 192.168.21.31| kube-apiserver，kube-controller-manager，kube-scheduler，etcd\
k8s-node1| 192.168.21.32| kubelet，kube-proxy，docker，flannel，etcd\
k8s-node2| 192.168.21.33 |kubelet，kube-proxy，docker，flannel，etcd

## 部署前环境配置

以下操作每台机器都需要执行

### 关闭swap

```
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
```

### 关闭selinux

```
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
```

### 关闭防护墙

```
systemctl stop firewalld
```

### 设置hosts

```
echo "192.168.21.31 master
192.168.21.32 node1
192.168.21.33 node2" >> /etc/hosts
```

## 部署Etcd集群

### 下载证书生成工具

```
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64 cfssl-certinfo_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
mv cfssl-certinfo_linux-amd64 /usr/bin/cfssl-certinfo
```

### 生成etcd证书

```
mkdir /root/k8s/etcd
cd /root/k8s/etcd

cat <<EOF > ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "www": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

cat <<EOF > ca-csr.json
{
    "CN": "etcd CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

cat <<EOF > server-csr.json
{
    "CN": "etcd",
    "hosts": [
    "192.168.31.63",
    "192.168.31.65",
    "192.168.31.66"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "BeiJing",
            "ST": "BeiJing"
        }
    ]
}
EOF
```

执行命令生成证书

```
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www server-csr.json | cfssljson -bare server

ls *pem
ca-key.pem  ca.pem  server-key.pem  server.pem
```

### master部署etcd

```
二进制包下载地址：https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz  

创建目录存放包
mkdir -p /root/k8s/software

获取包解压
# wget https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz
# tar zxvf etcd-v3.2.12-linux-amd64.tar.gz

创建etcd存放配置文件，bin文件和证书的目录，并拷贝对应的文件过去
mkdir /opt/etcd/{bin,cfg,ssl} -p
mv etcd-v3.3.10-linux-amd64/{etcd,etcdctl} /opt/etcd/bin/

创建etcd的配置文件
cat <<EOF > /opt/etcd/cfg/etcd   
#[Member]
ETCD_NAME="etcd01"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.21.31:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.21.31:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.21.31:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.21.31:2379"
ETCD_INITIAL_CLUSTER="etcd01=https://192.168.21.31:2380,etcd02=https://192.168.21.32:2380,etcd03=https://192.168.21.33:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
EOF

设置systemd管理etcd
cat <<EOF > /usr/lib/systemd/system/etcd.service 
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
EnvironmentFile=/opt/etcd/cfg/etcd
ExecStart=/opt/etcd/bin/etcd \\
--name=\${ETCD_NAME} \\
--data-dir=\${ETCD_DATA_DIR} \\
--listen-peer-urls=\${ETCD_LISTEN_PEER_URLS} \\
--listen-client-urls=\${ETCD_LISTEN_CLIENT_URLS},http://127.0.0.1:2379 \\
--advertise-client-urls=\${ETCD_ADVERTISE_CLIENT_URLS} \\
--initial-advertise-peer-urls=\${ETCD_INITIAL_ADVERTISE_PEER_URLS} \\
--initial-cluster=\${ETCD_INITIAL_CLUSTER} \\
--initial-cluster-token=\${ETCD_INITIAL_CLUSTER_TOKEN} \\
--initial-cluster-state=new \\
--cert-file=/opt/etcd/ssl/server.pem \\
--key-file=/opt/etcd/ssl/server-key.pem \\
--peer-cert-file=/opt/etcd/ssl/server.pem \\
--peer-key-file=/opt/etcd/ssl/server-key.pem \\
--trusted-ca-file=/opt/etcd/ssl/ca.pem \\
--peer-trusted-ca-file=/opt/etcd/ssl/ca.pem
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

拷贝之前生成的etcd证书
# cp ca*pem server*pem /opt/etcd/ssl

master启动etcd
# systemctl start etcd
# systemctl enable etcd
```

### node节点部署etcd

```
node节点上部署etcd只需要把master的文件拷贝过来，然后修改下etcd的配置文件就行

scp -r /opt/etcd/ root@192.168.21.32:/opt
scp -r /opt/etcd/ root@192.168.21.33:/opt
scp /usr/lib/systemd/system/etcd.service root@192.168.21.32:/usr/lib/systemd/system
scp /usr/lib/systemd/system/etcd.service root@192.168.21.33:/usr/lib/systemd/system

node1和node2上修改/opt/etcd/cfg/etcd
修改后如下
node1
[root@node1 cfg]# cat /opt/etcd/cfg/etcd
#[Member]
ETCD_NAME="etcd02"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.21.32:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.21.32:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.21.32:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.21.32:2379"
ETCD_INITIAL_CLUSTER="etcd01=https://192.168.21.31:2380,etcd02=https://192.168.21.32:2380,etcd03=https://192.168.21.33:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

node2
[root@node2 kubernetes]# cat /opt/etcd/cfg/etcd
#[Member]
ETCD_NAME="etcd03"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.21.33:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.21.33:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.21.33:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.21.33:2379"
ETCD_INITIAL_CLUSTER="etcd01=https://192.168.21.31:2380,etcd02=https://192.168.21.32:2380,etcd03=https://192.168.21.33:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

然后再node1和node2上启动etcd
# systemctl start etcd
# systemctl enable etcd

验证etcd集群
# cd /opt/etcd/ssl
# /opt/etcd/bin/etcdctl --ca-file=ca.pem --cert-file=server.pem --key-file=server-key.pem --endpoints="https://192.168.21.31:2379,https://192.168.21.32:2379,https://192.168.21.33:2379" cluster-health
```

![upload-image](/files/-MN2J99N-dAoS87bS2Qy)

## 安装docker

所有机器节点都执行

```
# sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# sudo yum -y install docker-ce
# curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://bc437cce.m.daocloud.io
# systemctl start docker
# systemctl enable docker
```

## 部署Flannel网络

部署flannel网络需要用etcd存储一个子网信息，所以要保证能成功连接etcd

```
master上执行命令写入
# cd /opt/etcd/ssl
# /opt/etcd/bin/etcdctl --ca-file=ca.pem --cert-file=server.pem --key-file=server-key.pem --endpoints="https://192.168.21.31:2379,https://192.168.21.32:2379,https://192.168.21.33:2379" set /coreos.com/network/config  '{ "Network": "172.17.0.0/16", "Backend": {"Type": "vxlan"}}'

在node1节点上执行操作,其他节点后续拷贝文件就行
下载二进制包
cd /root/k8s/software
# wget https://github.com/coreos/flannel/releases/download/v0.10.0/flannel-v0.10.0-linux-amd64.tar.gz
# tar zxvf flannel-v0.9.1-linux-amd64.tar.gz
# mv flanneld mk-docker-opts.sh /opt/kubernetes/bin

配置Flannel.
# mkdir -p /opt/kubernetes/{cfg,bin,ssl}
# cat << EOF > /opt/kubernetes/cfg/flanneld
FLANNEL_OPTIONS="--etcd-endpoints=https://192.168.21.31:2379,https://192.168.21.32:2379,https://192.168.21.33:2379 -etcd-cafile=/opt/etcd/ssl/ca.pem -etcd-certfile=/opt/etcd/ssl/server.pem -etcd-keyfile=/opt/etcd/ssl/server-key.pem"
EOF

systemd管理Flannel
# cat << EOF > /usr/lib/systemd/system/flanneld.service
[Unit]
Description=Flanneld overlay address etcd agent
After=network-online.target network.target
Before=docker.service

[Service]
Type=notify
EnvironmentFile=/opt/kubernetes/cfg/flanneld
ExecStart=/opt/kubernetes/bin/flanneld --ip-masq \$FLANNEL_OPTIONS
ExecStartPost=/opt/kubernetes/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/subnet.env
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

配置docker启动指定子网段
cat << EOF > /usr/lib/systemd/system/docker.service 

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
EnvironmentFile=/run/flannel/subnet.env
ExecStart=/usr/bin/dockerd $DOCKER_NETWORK_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target
EOF

重启flannel和docker
# systemctl daemon-reload
# systemctl start flanneld
# systemctl enable flanneld
# systemctl restart docker

其他节点部署（master上可部署也可不部署）
# scp -r /opt/kubernetes/ root@192.168.21.33:/opt
# scp -r /opt/kubernetes/ root@192.168.21.31:/opt
# scp -r /usr/lib/systemd/system/{flanneld,docker}.service root@192.168.21.31:/usr/lib/systemd/system
# scp -r /usr/lib/systemd/system/{flanneld,docker}.service root@192.168.21.33:/usr/lib/systemd/system

其他节点重启flannel和docker
# systemctl daemon-reload
# systemctl start flanneld
# systemctl enable flanneld
# systemctl restart docker

测试
确保docker0与flannel.1在同一网段。
测试不同节点互通，在当前节点访问另一个Node节点docker0 IP：
```

![upload-image](/files/-MN2J99Ot9OTTIWzxZQ5)

## Master节点上部署组件

### 生成证书

```
# mkdir /root/k8s/master
# cd /root/k8s/master
创建ca证书
# cat << EOF > ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

# cat << EOF > ca-csr.json
{
    "CN": "kubernetes",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}
EOF

# cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

生成apiserver证书
下面配置文件中的ip包含lb的ip，masterip和网络ip还有本地ip
# cat << EOF > server-csr.json
{
    "CN": "kubernetes",
    "hosts": [
      "10.0.0.1",
      "127.0.0.1",
      "192.168.21.31",
      "192.168.21.32",
      "192.168.21.33",
      "kubernetes",
      "kubernetes.default",
      "kubernetes.default.svc",
      "kubernetes.default.svc.cluster",
      "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "BeiJing",
            "ST": "BeiJing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}
EOF

# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes server-csr.json | cfssljson -bare server

生成kube-proxy的证书
# cat << EOF > kube-proxy-csr.json
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "L": "BeiJing",
      "ST": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
EOF

# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy

最终生成证书如下
# ls *pem
ca-key.pem  ca.pem  kube-proxy-key.pem  kube-proxy.pem  server-key.pem  server.pem
```

### 部署apiserver组件

```
下载二进制包
wget https://dl.k8s.io/v1.15.12/kubernetes-server-linux-amd64.tar.gz /root/k8s/software

拷贝所需要的文件
# mkdir /opt/kubernetes/{bin,cfg,ssl} -p
# tar zxvf kubernetes-server-linux-amd64.tar.gz
# cd kubernetes/server/bin
# cp kube-apiserver kube-scheduler kube-controller-manager kubectl /opt/kubernetes/bin

创建token文件
# head -c 16 /dev/urandom | od -An -t x | tr -d ' '
# cat << EOF > /opt/kubernetes/cfg/token.csv
231f18a1d882a5037e4374e51c6ec7e0,kubelet-bootstrap,10001,"system:kubelet-bootstrap"
EOF
第一列：随机字符串，自己执行第一条命令生成
第二列：用户名
第三列：UID
第四列：用户组

创建apiserver配置文件
# cat << EOF > /opt/kubernetes/cfg/kube-apiserver 
KUBE_APISERVER_OPTS="--logtostderr=true \\
--v=4 \\
--etcd-servers=https://192.168.21.31:2379,https://192.168.21.32:2379,https://192.168.21.33:2379 \\
--bind-address=192.168.21.31 \\
--secure-port=6443 \\
--advertise-address=192.168.21.31 \\
--allow-privileged=true \\
--service-cluster-ip-range=10.0.0.0/24 \\
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota,NodeRestriction \\
--authorization-mode=RBAC,Node \\
--enable-bootstrap-token-auth \\
--token-auth-file=/opt/kubernetes/cfg/token.csv \\
--service-node-port-range=30000-50000 \\
--tls-cert-file=/opt/kubernetes/ssl/server.pem  \\
--tls-private-key-file=/opt/kubernetes/ssl/server-key.pem \\
--client-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \\
--etcd-cafile=/opt/etcd/ssl/ca.pem \\
--etcd-certfile=/opt/etcd/ssl/server.pem \\
--etcd-keyfile=/opt/etcd/ssl/server-key.pem"
EOF

systemd管理apiserver
# cat << EOF >  /usr/lib/systemd/system/kube-apiserver.service 
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-apiserver
ExecStart=/opt/kubernetes/bin/kube-apiserver \$KUBE_APISERVER_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

启动apiserver
# systemctl daemon-reload
# systemctl enable kube-apiserver
# systemctl restart kube-apiserver
```

### 部署scheduler组件

```
创建scheduler配置文件
cat << EOF > /opt/kubernetes/cfg/kube-scheduler 
KUBE_SCHEDULER_OPTS="--logtostderr=true \\
--v=4 \\
--master=127.0.0.1:8080 \\
--leader-elect"
EOF

systemd管理scheduler组件
cat << EOF > /usr/lib/systemd/system/kube-scheduler.service 
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-scheduler
ExecStart=/opt/kubernetes/bin/kube-scheduler \$KUBE_SCHEDULER_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

启动scheduler
# systemctl daemon-reload
# systemctl enable kube-scheduler
# systemctl restart kube-scheduler
```

### 部署controller-manager组件

```
创建controller-manager配置文件
cat << EOF > /opt/kubernetes/cfg/kube-controller-manager 
KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=true \\
--v=4 \\
--master=127.0.0.1:8080 \\
--leader-elect=true \\
--address=127.0.0.1 \\
--service-cluster-ip-range=10.0.0.0/24 \\
--cluster-name=kubernetes \\
--cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \\
--cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem  \\
--root-ca-file=/opt/kubernetes/ssl/ca.pem \\
--service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem"
EOF

systemd管理controller-manager组件
cat << EOF > /usr/lib/systemd/system/kube-controller-manager.service 
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-controller-manager
ExecStart=/opt/kubernetes/bin/kube-controller-manager \$KUBE_CONTROLLER_MANAGER_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

启动controller-manager
# systemctl daemon-reload
# systemctl enable kube-controller-manager
# systemctl restart kube-controller-manager

检查组件状态
[root@master cfg]# /opt/kubernetes/bin/kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
controller-manager   Healthy   ok
scheduler            Healthy   ok
etcd-1               Healthy   {"health":"true"}
etcd-2               Healthy   {"health":"true"}
etcd-0               Healthy   {"health":"true"}
```

## node节点上部署组件

Master apiserver启用TLS认证后，Node节点kubelet组件想要加入集群，必须使用CA签发的有效证书才能与apiserver通信，当Node节点很多时，签署证书是一件很繁琐的事情，因此有了TLS Bootstrapping机制，kubelet会以一个低权限用户自动向apiserver申请证书，kubelet的证书由apiserver动态签署。\
![upload-image](/files/-MN2J99QMmVr6UsK4kxn)

### 将kubelet-bootstrap用户绑定到系统集群角色

```
# kubectl create clusterrolebinding kubelet-bootstrap \
  --clusterrole=system:node-bootstrapper \
  --user=kubelet-bootstrap
```

### 创建kubeconfig文件

在生成kubernetes证书的目录下执行以下命令生成kubeconfig文件

```
[root@node1 cfg]# cd /opt/kubernetes/ssl/
[root@master ssl]# cat kubeconfig.sh
BOOTSTRAP_TOKEN=231f18a1d882a5037e4374e51c6ec7e0
KUBE_APISERVER="https://192.168.21.31:6443"


# 设置集群参数
kubectl config set-cluster kubernetes \
  --certificate-authority=./ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=bootstrap.kubeconfig

# 设置客户端认证参数
kubectl config set-credentials kubelet-bootstrap \
  --token=${BOOTSTRAP_TOKEN} \
  --kubeconfig=bootstrap.kubeconfig

# 设置上下文参数
kubectl config set-context default \
  --cluster=kubernetes \
  --user=kubelet-bootstrap \
  --kubeconfig=bootstrap.kubeconfig

# 设置默认上下文
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

#----------------------

# 创建kube-proxy kubeconfig文件

kubectl config set-cluster kubernetes \
  --certificate-authority=./ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config set-credentials kube-proxy \
  --client-certificate=./kube-proxy.pem \
  --client-key=./kube-proxy-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes \
  --user=kube-proxy \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

将文件拷贝到节点的配置文件目录
scp *.kubeconfig root@192.168.21.32: /opt/kubernetes/cfg
scp *.kubeconfig root@192.168.21.33: /opt/kubernetes/cfg
```

### 部署kubelet组件

```
从master上拷贝执行文件
# cd /root/k8s/software/kubernetes/server/bin
# scp kubelet kube-proxy root2192.168.21.32: /opt/kubernetes/bin
# scp kubelet kube-proxy root2192.168.21.33: /opt/kubernetes/bin

创建kubelet配置文件（node上都要执行，注意修改ip）
cat << EOF > /opt/kubernetes/cfg/kubelet
KUBELET_OPTS="--logtostderr=true \\
--v=4 \\
--hostname-override=192.168.21.32 \\
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \\
--bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \\
--config=/opt/kubernetes/cfg/kubelet.config \\
--cert-dir=/opt/kubernetes/ssl \\
--pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"
EOF

配置kubelet.config配置文件（node上都要执行，注意修改ip）
cat << EOF > /opt/kubernetes/cfg/kubelet.config
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 192.168.21.32
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS: ["10.0.0.2"]
clusterDomain: cluster.local.
failSwapOn: false
authentication:
  anonymous:
enabled: true
EOF

systemd管理kubelet组件（node上都要执行）
cat << EOF > /usr/lib/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
After=docker.service
Requires=docker.service

[Service]
EnvironmentFile=/opt/kubernetes/cfg/kubelet
ExecStart=/opt/kubernetes/bin/kubelet \$KUBELET_OPTS
Restart=on-failure
KillMode=process

[Install]
WantedBy=multi-user.target
EOF

启动kubelet（node上都要执行）
# systemctl daemon-reload
# systemctl enable kubelet
# systemctl restart kubelet

master上审批node加入集群
启动后还没加入到集群中，需要手动允许该节点才可以。
在Master节点查看请求签名的Node：
# kubectl get csr
# kubectl certificate approve XXXXID
# kubectl get node
```

### 部署kube-proxy组件

```
创建kube-proxy配置文件（node上都要执行，注意修改ip）
cat  << EOF > /opt/kubernetes/cfg/kube-proxy
KUBE_PROXY_OPTS="--logtostderr=true \\
--v=4 \\
--hostname-override=192.168.21.32 \\
--cluster-cidr=10.0.0.0/24 \\
--kubeconfig=/opt/kubernetes/cfg/kube-proxy.kubeconfig"
EOF

systemd管理kube-proxy组件（node上都要执行）
cat << EOF > /usr/lib/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Proxy
After=network.target

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-proxy
ExecStart=/opt/kubernetes/bin/kube-proxy $KUBE_PROXY_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

启动kube-proxy
# systemctl daemon-reload
# systemctl enable kube-proxy
# systemctl restart kube-proxy
```

## 部署coredns

执行下面操作部署

```
# cp /root/k8s/software/kubernetes/cluster/addons/dns/coredns/ coredns.yaml.base  /root/cordns.yam

# sed -i -e "s/__PILLAR__DNS__DOMAIN__/cluster.local./" -e "s/__PILLAR__DNS__SERVER__/10.0.0.2/" coredns.yaml

# sed -i -e "s/__PILLAR__DNS__MEMORY__LIMIT__/150Mi/" 

# sed -i -e "s/ k8s.gcr.io\/coredns:1.3.1/"coredns\/coredns:1.3.1

# kubectl apply -f /root/ coredns.yaml
```

## 运行测例测试

```
# kubectl run nginx --image=nginx --replicas=3
# kubectl expose deployment nginx --port=88 --target-port=80 --type=NodePort

[root@master coredns]# kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
busybox                  1/1     Running   12         15h
nginx-7bb7cd8db5-g7pxs   1/1     Running   0          15h
nginx-7bb7cd8db5-p22cb   1/1     Running   0          15h
nginx-7bb7cd8db5-z6dwn   1/1     Running   0          15h
[root@master coredns]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP        16h
nginx        NodePort    10.0.0.63    <none>        88:43614/TCP   15h
```

访问集群中部署的Nginx，打开浏览器输入：<http://192.168.31.32:43614>

![upload-image](/files/-MN2J99SUBlA4U1uZslB)


# TKE上关于postStart和preStop使用

Kubernetes 支持 postStart 和 preStop 事件。 当一个容器启动后，Kubernetes 将立即发送 postStart 事件；在容器被终结之前， Kubernetes 将发送一个 preStop 事件。

## 使用限制

Kubernetes 在容器创建后立即发送 postStart 事件。 然而，postStart 处理函数的调用不保证早于容器的入口点（entrypoint） 的执行。postStart 处理函数与容器的代码是异步执行的，但 Kubernetes 的容器管理逻辑会一直阻塞等待 postStart 处理函数执行完毕。 只有 postStart 处理函数执行完毕，容器的状态才会变成 RUNNING。

Kubernetes 在容器结束前立即发送 preStop 事件。除非 Pod 宽限期限超时，Kubernetes 的容器管理逻辑 会一直阻塞等待 preStop 处理函数执行完毕。更多的相关细节，可以参阅 Pods 的结束。

> **Note**: Kubernetes 只有在 Pod 结束（Terminated） 的时候才会发送 preStop 事件， 这意味着在 Pod 完成（Completed） 时 preStop 的事件处理逻辑不会被触发。这个限制在 issue #55087 中被追踪

总结一下就是三条：

* postStart无法保证函数执行才运行pod
* postStop会保证执行完函数后才销毁pod
* preStop只会在pod被Terminated才会被触发

## yaml文件中的字段配置

lifecycle字段是.spec.containers.lifecycle

创建资源对象时，可以使用lifecycle来管理容器在运行前和关闭前的一些动作。

lifecycle有两种回调函数：

* PostStart：容器创建成功后，运行前的任务，用于资源部署、环境准备等。
* PreStop：在容器被终止前的任务，用于优雅关闭应用程序、通知其他系统等等

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

## TKE中使用postStart 和preStop

TKE这边暂时不支持控制台配置postStart 和preStop，这边只能修改yaml来进行配置，如果您现在都是通过yaml部署，则只需要通过在yaml中添加字段lifecycle既可，如果是通过控制台部署，可以选择先部署工作负载，然后修改yaml重新部署既可。

![upload-image](/files/-MN2J8Sg58W64h1i9n3s)

先创建一个负载，然后再修改yaml文件

```yaml
    .......
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        lifecycle:
          postStart:
            exec:
              command:
              - /bin/sh
              - -c
              - echo 'Hello from the postStart handler' >> /var/log/nginx/message
          preStop:
            exec:
              command:
              - /bin/sh
              - -c
              - echo 'Hello from the preStop handler'   >> /var/log/nginx/message
        name: nginx-lifecycle
        ......
```

这边创建再删除pod，可以从日志中查看到对应输出

![upload-image](/files/-MN2J8Sh2sD8BTrHjtYW)

## 参考文档

<https://blog.csdn.net/woshizhangliang999/article/details/108088075?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight>

<https://kubernetes.io/zh/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/>


# DockerFile学习

本文主要介绍了Dockerfile常用的指令使用。

## DockerFile的例子

```
FROM centos
MAINTAINER nobody "xx@qq.com"
RUN mkdir -p /opt/jdk/
RUN mkdir -p /opt/tomcat/
ADD jdk1.7.0_79 /opt/jdk/
ADD tomcat  /opt/tomcat/
ENV CATALINA_HOME /opt/tomcat
ENV JAVA_HOME /opt/jdk
EXPOSE 8080
VOLUME /opt/tomcat/data
ENV PATH $PATH:$JAVA_HOME/bin
CMD ["/opt/tomcat/bin/catalina.sh","run"]
```

## DockerFile的语法解析

Dockerfile的基本指令有十三个，分别是：FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD。下面对这些指令的用法一一说明。

### FROM

用法：FROM \\\
说明：第一个指令必须是FROM了，其指定一个构建镜像的基础源镜像，如果本地没有就会从公共库中拉取，没有指定镜像的标签会使用默认的latest标签，可以出现多次，如果需要在一个Dockerfile中构建多个镜像。

### MAINTAINER

用法：MAINTAINER \ \\\
说明：描述镜像的创建者，名称和邮箱

### RUN

用法：RUN "command" "param1" "param2"\
说明：RUN命令是一个常用的命令，RUN命令可以执行多次，执行完成之后会成为一个新的镜像，这里也是指镜像的分层构建。一句RUN就是一层，也相当于一个版本。这就是之前说的缓存的原理。我们知道docker是镜像层是只读的，所以你如果第一句安装了软件，用完在后面一句删除是不可能的。所以这种情况要在一句RUN命令中完成，可以通过&符号连接多个RUN语句。RUN后面的必须是双引号不能是单引号（没引号貌似也不要紧），command是不会调用shell的，所以也不会继承相应变量，要查看输入RUN "sh" "-c" "echo" "$HOME"，而不是RUN "echo" "$HOME"。

### CMD

用法：CMD command param1 param2\
说明：CMD在Dockerfile中只能出现一次，有多个，只有最后一个会有效。其作用是在启动容器的时候提供一个默认的命令项。如果用户执行docker run的时候提供了命令项，就会覆盖掉这个命令。没提供就会使用构建时的命令。

### EXPOSE

用法：EXPOSE \ \[\\...]\
说明：告诉Docker服务器容器对外映射的容器端口号，在docker run -p的时候生效。

### ENV

用法：EVN \ \ 只能设置一个\
EVN \\=\允许一次设置多个\
说明：设置容器的环境变量，可以让其后面的RUN命令使用，容器运行的时候这个变量也会保留。

### ADD

用法：ADD \ \\\
说明：复制本机文件或目录或远程文件，添加到指定的容器目录，支持GO的正则模糊匹配。路径是绝对路径，不存在会自动创建。如果源是一个目录，只会复制目录下的内容，目录本身不会复制。ADD命令会将复制的压缩文件夹自动解压，这也是与COPY命令最大的不同。

### COPY

用法：COPY \ \\\
说明：COPY除了不能自动解压，也不能复制网络文件。其它功能和ADD相同。

### ENTRYPOINT(推荐使用这个作为启动命令)

用法：ENTRYPOINT "command" "param1" "param2"\
说明：这个命令和CMD命令一样，唯一的区别是不能被docker run命令的执行命令覆盖，如果要覆盖需要带上选项--entrypoint，如果有多个选项，只有最后一个会生效。

### VOLUME

用法：VOLUME \["path"]\
说明：在主机上创建一个挂载，挂载到容器的指定路径。docker run -v命令也能完成这个操作，而且更强大。这个命令不能指定宿主机的需要挂载到容器的文件夹路径。但docker run -v可以，而且其还可以挂载数据容器。

### USER

用法：USER daemon\
说明：指定运行容器时的用户名或UID，后续的RUN、CMD、ENTRYPOINT也会使用指定的用户运行命令。

### WORKDIR

用法:WORKDIR path\
说明：为RUN、CMD、ENTRYPOINT指令配置工作目录。可以使用多个WORKDIR指令，后续参数如果是相对路径，则会基于之前的命令指定的路径。如：WORKDIR /home WORKDIR test 。最终的路径就是/home/test。path路径也可以是环境变量，比如有环境变量HOME=/home，WORKDIR $HOME/test也就是/home/test。

### ONBUILD

用法：ONBUILD \[INSTRUCTION]\
说明：配置当前所创建的镜像作为其它新创建镜像的基础镜像时，所执行的操作指令。意思就是，这个镜像创建后，如果其它镜像以这个镜像为基础，会先执行这个镜像的ONBUILD命令。

### ARG

语法：ARG \\\[=\\]\
设置变量命令，ARG命令定义了一个变量，在docker build创建镜像的时候，使用 --build-arg \\=\来指定参数 如果用户在build镜像时指定了一个参数没有定义在Dockerfile种，那么将有一个Warning


# linux常用命令

作为一个运维人员，linux命令和shell脚本是必不可少，本篇文章简要介绍在运维过程中一些常用的linux命令和脚本一些语法。

## 安装rpm包到指定路径

rpm -ivh --prefix=/java xx.rpm

## 修改git上一次提交记录

git commit --amend

## 全局替换命令

%s/home/\\/home\\/rcs/g

## rpm包命令

解压命令：rpm2cpio xxx.rpm | cpio -idmv

安装命名：rpm -ivh --nodeps xxx.rpm

卸载命令：rpm -e xxx

查看命名：rpm -qa | grep xxx

## 查看I/O命令

iostat -t 3

## 生成线程分析文件

jmap -dump:live,format=b,file=swa8.bin PID（top查看）

## 查找某个类所属的包：

grep -l "com/huawei/payment/console/id/IdTool" \* -R

grep "com.huawei.payment.ag.socket.iso8583.template.ISOBasicConfig" \* -R

git查看多少天文件的变化：git whatchanged --since="100 days ago"

## 查看文件的CRC值

cksum file （可以根据CRC值判断文件是否有变化）

## 重启sshd服务

/etc/init.d/sshd restart

## 查找某个文件的值

find ./ -name cps.beanconf.properties | xargs grep "Cps.Bean.CustomizePolicy=\*"

## 查看suse版本号

lsb\_release -d

## 解压tar.gz包到指定路径

tar -zxvf jdk-8u71-linux-x64.tar.gz -C /home/ci/nwx/targz/temp

## 替换多个文件内容

find ./ -name cps.datasource.xml | xargs sed -i 's/10.62.172.249/10.62.159.192/g'

## 不挂断某个命令

nohup command &

## 查看某个端口所建立的连接

netstat -an | grep 31100

## 查看system信息

jinfo pid

## 查看目录文件的个数

ls -l|grep "^-"| wc -l

## 查看目录空间使用情况

du -sh \* | sort -n

## 通过其他用户执行命令

su - $tomcatuse -c "cd tools;stop.sh -y&"

## 查看某个文件的大小

du -k ${logfilepath}/${log*module\_name}*${logfile} | awk -F ' ' '{print $1}'

## 检查文件sha256sum

sha256sunm test.txt>test.txt.sha256sum sha256sum -c <(grep test.txt test.txt.sha256sum)

## 检查文件的md5值

md5sum test1 test2 >tmp.test md5sum -c tem.test

## 浮点数的计算

ab=\`echo "scale=5;a=1/3;if(length(a)==scale(a)) print 0;print a"|bc\`

## 提取字符串中的数字

echo "2014年7月21日" | tr -cd "\[0-9]"

## 往文件的某一行中追加字符

sed -i "0,/^AllowGroups.\*/s//& wheel/" $sshd\_config

## 替换yaml中的镜像名

tag=$(date +"%Y%m%d-%H%M%S")

newimage=106.54.126.251:5000/sprintboot:$tag

sed -i "s#image: .\*#image: $newimage#g" /opt/k8s/nginx.yaml

## 获取一行的第n个字段到最后一个字段

log\_content=\`echo "$line" | awk '{for (i=7 ;i<=NF;i++) printf $i " "; printf "\n" }'\`

## Exit和return的区别

（1）作用不同。exit用于在程序运行的过程中随时结束程序，exit的参数是返回给OS的。exit是结束一个进程，它将删除进程使用的内存空间，同时把错误信息返回父进程。而return是返回函数值并退出函数；

（2）语义层级不同。return是语言级别的，它表示了调用堆栈的返回；而exit是系统调用级别的，它表示了一个进程的结束；

（3）使用方法不用。return一般用在函数方法体内，exit可以出现在Shell脚本中的任意位置。

## >/dev/null 2>&1

2>&1，将错误输出绑定到标准输出上。由于此时的标准输出是默认值，也就是输出到屏幕，所以错误输出会输出到屏幕。

\>/dev/null，将标准输出1重定向到/dev/null中。

## 获取字符串的第1个字符

first\_character=\`echo ${line:0:1}\`

## 大于小于英文

-ne —比较两个参数是否不相等

-lt —参数1是否小于参数2

-le —参数1是否小于等于参数2

-gt —参数1是否大于参数2

-ge —参数1是否大于等于参数2

## grep参数的用法

![upload-image](/files/-MN2J8QY8zSh2wtSahN7)

## ssh-copy-id

ssh-copy-id命令可以把本地主机的公钥复制到远程主机的authorized\_keys文件上，

ssh-copy-id命令也会给远程主机的用户主目录（home）和\~/.ssh, 和\~/. ssh/authorized\_keys设置合适的权限。

使用模式： ssh-copy-id \[-i \[identity\_file]] \[user@]machine

描述：\
ssh-copy-id 是一个实用ssh去登陆到远程服务器的脚本（假设使用一个登陆密码，因此，密码认证应该被激活直到你已经清理了做了多个身份的使用）。 它也能够改变远程用户名的权限，\~/.ssh和\~/.ssh/authorized\_keys 删除群组写的权限（在其它方面，如果远程机上的sshd在它的配置 文件中是严格模式的话，这能够阻止你登陆。）。

如果这个 “-i”选项已经给出了，然后这个认证文件（默认是\~/.ssh/id\_rsa.pub）被使用，不管在你的ssh-agent那里是否有任何密钥。另外，命令 “ssh-add -L” 提供任何输出，它使用这个输出优先于 身份认证文件。如果给出了参数“-i”选项，或者ssh-add不产生输出， 然后它使用身份认证文件的内容。一旦它有一个或者多个指纹，它使 用ssh将这些指纹填充到远程机\~/.ssh/authorized\_keys文件中。

## split切割文件

```
先使用 dd 命令来生成一个 700MB 文件来作为我们的拆分对象：
[root@roclinux ~]$ dd if=/dev/zero bs=1024 count=700000 of=king_of_ring.avi
700000+0 records in
700000+0 records out
716800000 bytes (717 MB) copied, 12.9189 s, 55.5 MB/s
[root@roclinux ~]$  ls -l king_of_ring.avi
-rw-r--r-- 1 root root 716800000 Apr 12 13:01 king_of_ring.avi

这里使用到了 split 的-b选项，来指定每个拆分文件的大小：  
[root@roclinux ~]$ split -b 400M king_of_ring.avi  
[root@roclinux ~]$ ls -l  
total 1400008  
-rw-r--r-- 1 root root 716800000 Apr 12 13:01 king_of_ring.avi  
-rw-r--r-- 1 root root 419430400 Apr 12 13:04 xaa    
-rw-r--r-- 1 root root 297369600 Apr 12 13:04 xab

默认情况下，分割后的文件的名称会以 x 作为前缀，以 aa、ab、ac 这样的双字母格式作为后缀，形成 xaa、xab 这样的名称格式。

我们来一起看看 split 的命令格式：
split [-b ][-C ][-][-l ][要切割的文件][输出文件名前缀][-a ]
最常用的选项，都在这里了：  
-b<字节>：指定按多少字节进行拆分，也可以指定 K、M、G、T 等单位。
-<行数>或-l<行数>：指定每多少行要拆分成一个文件。  
输出文件名前缀：设置拆分后的文件的名称前缀，split 会自动在前缀后加上编号，默认从 aa 开始。  
-a<后缀长度>：默认的后缀长度是 2，也就是按 aa、ab、ac 这样的格式依次编号。  

使用 cat 命令将拆分文件 xaa 和 xab 合并成一个文件，可以看出合并后的文件和源文件的大小是一致的：  
[root@roclinux ~]$ cat xaa xab > king_of_ring_merge.avi

[root@roclinux ~]$ ls -l  
total 2100012  
-rw-r--r-- 1 root root 716800000 Apr 12 13:01 king_of_ring.avi  
-rw-r--r-- 1 root root 716800000 Apr 12 13:07   king_of_ring_merge.avi   
-rw-r--r-- 1 root root 419430400 Apr 12 13:04 xaa  
-rw-r--r-- 1 root root 297369600 Apr 12 13:04 xab  

对了，如果是在 Windows 下的话，我们要先运行 cmd，然后用 copy 命令来进行文件的合并：
copy /b xaa + xab king_of_ring.avi
格式上和 Linux 有些区别，但原理是一样的。
设置拆分文件的名称前缀
下面的例子，我们尝试以 king_of_ring_part_ 作为拆分后文件的名称前缀：
我们指定了king_of_ring_part_前缀
[root@roclinux ~]$ split -b 400m king_of_ring.avi king_of_ring_part_

可以看到, 文件名的可读性提高了很多
[root@roclinux ~]$ ls -l king*
-rw-r--r-- 1 root root 716800000 Feb 25 18:29 king_of_ring.avi
-rw-r--r-- 1 root root 419430400 Feb 25 19:24 king_of_ring_part_aa
-rw-r--r-- 1 root root 297369600 Feb 25 19:24 king_of_ring_part_ab

设置数字后缀
如果大家看不惯以 aa、ab 这种字母作为文件后缀，我们还可以通过-d选项来指定数字形式的文件后缀：
使用了-d选项
[root@roclinux ~]$ split -b 400m -d king_of_ring.avi king_of_ring_part_

后缀从原来的aa、ab变成了00、01
[root@roclinux ~]$ ls -l king*
-rw-r--r-- 1 root root 716800000 Feb 25 18:29 king_of_ring.avi
-rw-r--r-- 1 root root 419430400 Feb 25 19:24 king_of_ring_part_00
-rw-r--r-- 1 root root 297369600 Feb 25 19:24 king_of_ring_part_01


按照行数进行拆分
前面我们讲的是按照文件大小（如 400MB）进行文件拆分的方法，但是并非所有情况都适合于用文件大小作为拆分单元。比如，我们希望把 /etc/passwd 文件按照一个文件 10 行记录的方式进行拆分，又该怎么操作呢？
使用-N来指定拆分的行数,本例中为-10
[root@roclinux ~]$ split -d -10 /etc/passwd my_passwd_
可以看到拆分成功
[root@roclinux ~]$ wc -l my_passwd_*
  10 my_passwd_00
  10 my_passwd_01
   5 my_passwd_02
  25 total

#对原先的文件计算md5值
[root@roclinux ~]$ md5sum king_of_ring.avi
eacff27bf2db99c7301383b7d8c1c07c  king_of_ring.avi

#对合并后的文件计算md5值, 并与原值进行比较
[root@roclinux ~]$ md5sum king_of_ring_merge.avi
eacff27bf2db99c7301383b7d8c1c07c  king_of_ring_merge.avi
如果前后一致，那么恭喜你，文件合并成功！
```

## 随机生成12位密码

PassWd=$(strings /dev/urandom | grep -o '\[\[:alnum:]]' | head -n 12 | tr -d "\n";echo)

## test命令

Shell中的 test 命令用于检查某个条件是否成立，它可以进行数值、字符和文件三个方面的测试。

数值测试 参数 说明\
-eq  等于则为真\
-ne  不等于则为真\
-gt  大于则为真\
-ge  大于等于则为真\
-lt  小于则为真\
-le  小于等于则为真

字符串测试 参数 说明\
\= 等于则为真\
!= 不相等则为真\
-z 字符串 字符串的长度为零则为真\
-n 字符串 字符串的长度不为零则为真

文件测试 参数 说明\
-e 文件名 如果文件存在则为真\
-r 文件名 如果文件存在且可读则为真\
-w 文件名 如果文件存在且可写则为真\
-x 文件名 如果文件存在且可执行则为真\
-s 文件名 如果文件存在且至少有一个字符则为真\
-d 文件名 如果文件存在且为目录则为真\
-f 文件名 如果文件存在且为普通文件则为真\
-c 文件名 如果文件存在且为字符型特殊文件则为真\
-b 文件名 如果文件存在且为块特殊文件则为真

## sudo执行echo命令

$ echo 3 | sudo tee /proc/sys/vm/drop\_caches

## 查看cpu数量

cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc –l

## let命令

let 命令是 BASH 中用于计算的工具，用于执行一个或多个表达式，变量计算中不需要加上 $ 来表示变量。如果表达式中包含了空格或其他特殊字符，则必须引起来。\
let a=5+4\
let b=9-3

## shift命令

从上可知 shift(shift 1) 命令每执行一次，变量的个数($#)减一（之前的$1变量被销毁,之后的$2就变成了$1），而变量值提前一位。同理，shift n后，前n位参数都会被销毁，比如：\
输入5个参数： abcd e 那么$1=a,$2=b,$3=c,$4=d,$5=e,执行shift 3操作后，前3个参数a、b、c被销毁，就剩下了2个参数：d,e（这时d=$1,e=$2，其中d由$4—>$1,e由$5—>$2）

## 获取所有参数中的第m个后面的n个参数

${variable:offset:length}\
获得variable值位置从offset开始长度为length的子串。

${@:m:n}\
m表示从第m个参数开始（从1开始），n表示输出m参数后面的n个参数

例子

```
a b c d e f g h   
${@:3:2}  
输出结果：c d   

test.sh  
aaaaaa=(${@:2:4}); shift 2  
echo $aaaaaa

sh test.sh    
a b c d e f g
结果：输出b
```

## linux下进程通讯的8中方法

Linux下进程通信的八种方法：管道(pipe)，命名管道(FIFO)，内存映射(mapped memeory)，消息队列(message queue)，共享内存(shared memory)，信号量(semaphore)，信号(signal)，套接字(Socket) (1) 管道（pipe）：管道允许一个进程和另一个与它有共同祖先的进程之间进行通信；\
(2) 命名管道（FIFO）：类似于管道，但是它可以用于任何两个进程之间的通信，命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建；\
(3) 信号（signal）：信号是比较复杂的通信方式，用于通知接收进程有某种事情发生，除了用于进程间通信外，进程还可以发送信号给进程本身；Linux除了支持UNIX早期信号语义函数signal外，还支持语义符合POSIX.1标准的信号函数sigaction(实际上，该函数是基于BSD的，BSD即能实现可靠信号机制，又能够统一对外接口，用sigaction函数重新实现了signal函数的功能);\
(4) 内存映射（mapped memory）：内存映射允许任何多个进程间通信，每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它；\
(5) 消息队列（message queue）：消息队列是消息的连接表，包括POSIX消息对和System V消息队列。有足够权限的进程可以向队列中添加消息，被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少，管道只能成该无格式字节流以及缓冲区大小受限等缺点；\
(6) 信号量（semaphore）：信号量主要作为进程间以及同进程不同线程之间的同步手段；\
(7) 共享内存 （shared memory）：它使得多个进程可以访问同一块内存空间，是最快的可用IPC形式。这是针对其他通信机制运行效率较低而设计的。它往往与其他通信机制，如信号量结合使用，以达到进程间的同步及互斥；\
(8) 套接字（Socket）：它是更为通用的进程间通信机制，可用于不同机器之间的进程间通信。起初是由UNIX系统的BSD分支开发出来的，但现在一般可以移植到其他类UNIX系统上：Linux和System V的变种都支持套接字。

## 使用sed在文件中插入带有变量的字符串

解决方式为：sed -i '10i-A INPUT -m state --state NEW -m tcp -p tcp --dport '$port' -j ACCEPT' /etc/sysconfig/iptables

授人以鱼不如授人以渔，产生问题的原因是： 因为 $port 是shell变量而不是sed中的变量，需要单独拿到 sed 的单引号外面来才能被 shell 解析。单引号里面是 sed 的势力范围，shell 无法触及。 sed的参数后的命令，是以单引号开始，单引号结束的，所以想将shell变量拿出来，那就在变量前面加个单引号让sed命令结束，再在变量后面再加个单引号让sed命令再开始

## Shell中要如何调用别的脚本，变量，函数

方法一: . ./subscript.sh\
方法二: source ./subscript.sh\
注意: 1.两个点之间，有空格，千万注意.\
2.两个脚本不在同一目录，要用绝对路径\
3.为简单起见，通常用第一种方法

## Shell中获取脚本最后一个参数

echo ${@:${#@}}

## Shell中连续输出n个数

\[root\@master \~]# echo a-{0..11}\
a-0 a-1 a-2 a-3 a-4 a-5 a-6 a-7 a-8 a-9 a-10 a-11

## 查看内存占比

ps -eo pmem,pcpu,rss,vsize,args | sort -k 1 -r


# k8s中通过ExternalName访问外部服务副本

本文主要介绍了ExternalName类型的service在k8s中的使用。·

## ExternalName访问外部服务

其实我们很多服务都是在在aws上，比如mysql和redis等数据库服务，如果我们代码中想要访问这些服务，那应该怎么访问，我们的代码服务是运行在pod中的，也就是相当于我们的k8s集群中的pod需要访问aws上的mysql或者redis等服务。 其实我们只需要提供一个ExternalName的servcie对外部服务进行映射就可以了，我们创建好这样service，然后通过service name就能直接访问到aws上提供的mysql服务。

```
---
kind: Service
apiVersion: v1
metadata:
  name: mysql-5-7-01-service
spec:
  type: ExternalName
  externalName: ai-production-mysql-bot.cfyipcsxzevb.rds.cn-northwest-1.amazonaws.com.cn
---
```

我们可以通过mysql-5-7-01-service这样一个servcie来访问aws的mysql数据库

## Endpoint访问外部服务

ExternalName只在kubedns中能够解析，在coredns中无法解析，如果集群中采用的coredns，则需要修改成endpoint的方式

```
---
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch-2-4-0-01-service
spec:
  clusterIP: None
---
kind: Endpoints
apiVersion: v1
metadata:
  name: elasticsearch-2-4-0-01-service
subsets:
  - addresses:
      - ip: 10.42.94.157
```

## 参考链接

<https://blog.csdn.net/Juwenzhe_HEBUT/article/details/89577459>


# docker常用命令

本文介绍了日常运维中用的一些docker命令和知识。

## docker容器访问其他容器服务

docker run -d --network host -p 8081:8081 xxxxxxxx:v3

## docker-compose

基于docker-compose.yml,通常启动的时候是一个服务，这个服务通常由多个container共同组成，并且端口，配置等由docker-compose定义好。

## 启动docker

systemctl start docker

## 设置docker开机启动

systemctl enable docker

## 启动一个mysql的docker镜像

sudo docker run --name mysqltest -p 3306:3306 -e MYSQL\_ROOT\_PASSWORD=123456 -v /data/mysql:/var/lib/mysql -d docker.io/mysql

## 启动一个mongo的docker镜像

sudo docker run -d -p 27017:27017 -v /data/mongodb-2-6-01-service/db:/data/db -v /data/dbback\_up\:/data/mongodbBack --restart=always 10.0.64.13:5000/mongo:2.6

## 从容器中拷贝文件到宿主机

docker cp 【CONTAINER ID】:【路径】文件名 【宿主机的绝对路径+文件名】

## docker服务重启后容器也自动重启

docker run --restart=always / docker update --restart=always&#x20;

## Docker保存多个镜像到tar中

docker save \[images] \[images] > \[name.tar]

## Docker导出镜像到环境

docker load<\[name.tar]

## Dockerfile 创建镜像打标签

docker build -t runoob/ubuntu:v1 .

## 查看docker磁盘使用

docker system df

## 删除所有未使用的本地卷

docker volume prune

## 清理所有处于终止状态的容器

docker container prune / docker rm \`docker ps -a | grep Exited | awk '{print $1}'\`

## 清理所有未使用的镜像

docker rmi -f \`docker images | grep '' | awk '{print $3}'\`

## 进入docker容器内部

sudo docker attach 44fc0f0582d9

## 查看docker容器的cpu及内存的使用情况

docker stats\
具体使用方法参考链接：\
<https://blog.csdn.net/hu_jinghui/article/details/80198492>

## 更新已经启动容器的参数

如果已经启动了则可以使用如下命令： docker update --restart=always&#x20;

## dockerfile中参数-Djava.security.egd=file:/dev/./urandom

docker+tomcat 启动时非常慢，一般正常启动几十秒的，发现docker+tomcat启动竟需要几分钟，不可思议Tomcat7/8都使用org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom 类产生安全随SecureRandom 的实例作为会话 ID，SecureRandom generateSeed 使用 /dev/random 生成种子。但是 /dev/random 是一个阻塞数字生成器，如果它没有足够的随机数据提供，它就一直等，这迫使 JVM 等待。键盘和鼠标输入以及磁盘活动可以产生所需的随机性或熵。但在一个服务器缺乏这样的活动，可能会出现问题。 有2种解决方案：

* 在Tomcat环境中解决：

  可以通过配置 JRE 使用非阻塞的 Entropy Source： &#x20;

  在 catalina.sh 中加入这么一行：-Djava.security.egd=file:/dev/./urandom 即可。 &#x20;
* 在 JVM 环境中解决（本人使用此方法）：

  打开jdk安装路径 $JAVA\_PATH/jre/lib/security/java.security 这个文件，找到下面的内容： &#x20;

  securerandom.source=file:/dev/random替换成：securerandom.source=file:/dev/./urandom

如果是采用docker的方式启动，则在启动命令中加入-Djava.security.egd=file:/dev/./urandom 这样一行

## 动态修改容器的内存大小

docker update --memory 2048m --memory-swap -1 container\_id/container\_name

## 退出容器而不停止容器

如果要正常退出不关闭容器，请按Ctrl+P+Q进行退出容器

## 容器之间访问方式

Docker容器互访三种方式 我们都知道docker容器之间是互相隔离的，不能互相访问，但如果有些依赖关系的服务要怎么办呢。下面介绍三种方法解决容器互访问题。

```
方式一、虚拟ip访问

安装docker时，docker会默认创建一个内部的桥接网络docker0，每创建一个容器分配一个虚拟网卡，容器之间可以根据ip互相访问。

[root@33fcf82ab4dd /]# 
[root@CentOS ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:35ff:feac:66d8  prefixlen 64  scopeid 0x20<link>
        ether 02:42:35:ac:66:d8  txqueuelen 0  (Ethernet)
        RX packets 4018  bytes 266467 (260.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4226  bytes 33935667 (32.3 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
...... 
 运行一个centos镜像， 查看ip地址得到：172.17.0.7

[root@CentOS ~]# docker run -it --name centos-1 docker.io/centos:latest
[root@6d214ff8d70a /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.7  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:acff:fe11:7  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ac:11:00:07  txqueuelen 0  (Ethernet)
        RX packets 16  bytes 1296 (1.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 648 (648.0 B) 

以同样的命令再起一个容器，查看ip地址得到：172.17.0.8

[root@CentOS ~]# docker run -it --name centos-2 docker.io/centos:latest
[root@33fcf82ab4dd /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.8  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:acff:fe11:8  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ac:11:00:08  txqueuelen 0  (Ethernet)
        RX packets 8  bytes 648 (648.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 648 (648.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0 
容器内部ping测试结果如下：

[root@33fcf82ab4dd /]# ping 172.17.0.7
PING 172.17.0.7 (172.17.0.7) 56(84) bytes of data.64 bytes from 172.17.0.7: icmp_seq=1 ttl=64 time=0.205 ms64 bytes from 172.17.0.7: icmp_seq=2 ttl=64 time=0.119 ms64 bytes from 172.17.0.7: icmp_seq=3 ttl=64 time=0.118 ms64 bytes from 172.17.0.7: icmp_seq=4 ttl=64 time=0.101 ms

这种方式必须知道每个容器的ip，在实际使用中并不实用。

方式二、link
运行容器的时候加上参数link
运行第一个容器
docker run -it --name centos-1 docker.io/centos:latest
运行第二个容器
[root@CentOS ~]# docker run -it --name centos-2 --link centos-1:centos-1 docker.io/centos:latest
--link：参数中第一个centos-1是容器名，第二个centos-1是定义的容器别名（使用别名访问容器），为了方便使用，一般别名默认容器名。
测试结果如下：
[root@e0841aa13c5b /]# ping centos-1
PING centos-1 (172.17.0.7) 56(84) bytes of data.64 bytes from centos-1 (172.17.0.7): icmp_seq=1 ttl=64 time=0.210 ms64 bytes from centos-1 (172.17.0.7): icmp_seq=2 ttl=64 time=0.116 ms64 bytes from centos-1 (172.17.0.7): icmp_seq=3 ttl=64 time=0.112 ms64 bytes from centos-1 (172.17.0.7): icmp_seq=4 ttl=64 time=0.114 ms
 此方法对容器创建的顺序有要求，如果集群内部多个容器要互访，使用就不太方便。

方式三、创建bridge网络
1.安装好docker后，运行如下命令创建bridge网络：docker network create testnet
查询到新创建的bridge testnet。
 ![upload-image](/assets/images/blog/docker/1.png) 

2.运行容器连接到testnet网络。
使用方法：docker run -it --name <容器名> ---network <bridge> --network-alias <网络别名> <镜像名>
[root@CentOS ~]# docker run -it --name centos-1 --network testnet --network-alias centos-1 docker.io/centos:latest
[root@CentOS ~]# docker run -it --name centos-2 --network testnet --network-alias centos-2 docker.io/centos:latest

3.从一个容器ping另外一个容器，测试结果如下：
[root@fafe2622f2af /]# ping centos-1
PING centos-1 (172.20.0.2) 56(84) bytes of data.64 bytes from centos-1.testnet (172.20.0.2):   
icmp_seq=1 ttl=64 time=0.158 ms64 bytes from centos-1.testnet (172.20.0.2):   
icmp_seq=2 ttl=64 time=0.108 ms64 bytes from centos-1.testnet (172.20.0.2):   
icmp_seq=3 ttl=64 time=0.112 ms64 bytes from centos-1.testnet (172.20.0.2):   
icmp_seq=4 ttl=64 time=0.113 ms   

4.若访问容器中服务，可以使用这用方式访问 <网络别名>：<服务端口号> 
推荐使用这种方法，自定义网络，因为使用的是网络别名，可以不用顾虑ip是否变动，只要连接到docker内部bright网络即可互访。bridge也可以建立多个，隔离在不同的网段。
```

## Docker容器日志的处理

新建/etc/docker/daemon.json，若有就不用新建了。添加log-dirver和log-opts参数，样例如下：

```
vim /etc/docker/daemon.json

{
  "registry-mirrors": ["http://f613ce8f.m.daocloud.io"],
  "log-driver":"json-file",
  "log-opts": {"max-size":"500m", "max-file":"3"}
}
```

max-size=500m，意味着一个容器日志大小上限是500M， max-file=3，意味着一个容器有三个日志，分别是id+.json、id+1.json、id+2.json。 // 重启docker守护进程\
systemctl daemon-reload\
systemctl restart docker\
注意：设置的日志大小，只对新建的容器有效。

## 赋予其他普通用户docker执行权限

usermod -aG docker ${USER}

## docker的网络类型

* 默认网络模式 - bridge
* 无网络模式 - none
* 宿主网络模式 - host
* 自定义网络

默认网络模式 - bridge\
多由于独立container之间的通信 首先来侃一侃docker0. 之所以说它是默认的网络，是由于当我们运行container的时候没有“显示”的指定网络时，我们的运行起来的container都会加入到这个“默认” docker0 网络。他的模式是bridge。

无网络模式 - none\
顾名思义，所有加入到这个网络模式中的container，都"不能”进行网络通信。貌似有点鸡肋。

宿主网络模式 - host\
直接使用宿主机的网络，端口也使用宿主机的 这种网络模式将container与宿主机的网络相连通，虽然很直接，但是却破获了container的隔离性，因此也比较鸡肋

自定义网络\
由于之前介绍的3种自带的网络模式有各自的局限性，因此，docker推荐大家自定义网络。通过自定义网络，我们可以实现“服务发现”与“DNS解析”。 docker 允许我们创建3种类型的自定义网络，bridge，overlay，MACVLAN（目前我还没有用到）。

bridge\
Bridge模式是Docker默认的网络模式，当Docker进程启动时，会在主机上创建一个名为docker0的虚拟网桥，用来连接宿主机和容器，此主机上的Docker容器都会连接到这个虚拟网桥上 overlay：当有多个docker主机时，跨主机的container通信 macvlan：每个container都有一个虚拟的MAC地址

## docker的存储驱动

镜像是只读的

读写层：每一个容器在运行时，都会基于当前镜像在其最上层挂载一个读写层。而用户针对容器的所有操作都在读写层中完成。一旦容器销毁，这个读写层也随之销毁。

知识点： 容器=镜像+读写层

所有驱动都用到的技术——写时复制（CoW）。CoW就是copy-on-write，表示只在需要写时才去复制，这个是针对已有文件的修改场景。比如基于一个image启动多个Container，如果为每个Container都去分配一个image一样的文件系统，那么将会占用大量的磁盘空间。而CoW技术可以让所有的容器共享image的文件系统，所有数据都从image中读取，只有当要对文件进行写操作时，才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享同一个image，所做的写操作都是对从image中复制到自己的文件系统中的复本上进行，并不会修改image的源文件，且多个容器操作同一个文件，会在每个容器的文件系统里生成一个复本，每个容器修改的都是自己的复本，相互隔离，相互不影响。使用CoW可以有效的提高磁盘的利用率

docker提供了多种存储驱动来实现不同的方式存储镜像，下面是常用的几种存储驱动：

* AUFS
* OverlayFS
* Devicemapper
* Btrfs
* ZFS

AUFS（AnotherUnionFS）是一种Union FS，是文件级的存储驱动。AUFS是一个能透明覆盖一个或多个现有文件系统的层状文件系统，把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的，只有最上层的文件系统是可写的。当需要修改一个文件时，AUFS创建该文件的一个副本，使用CoW将文件从只读层复制到可写层进行修改，结果也保存在可写层。在Docker中，底下的只读层就是image，可写层就是Container

Overlay是Linux内核3.18后支持的，也是一种Union FS，和AUFS的多层不同的是Overlay只有两层：一个upper文件系统和一个lower文件系统，分别代表Docker的镜像层和容器层。当需要修改一个文件时，使用CoW将文件从只读的lower复制到可写的upper进行修改，结果也保存在upper层。在Docker中，底下的只读层就是image，可写层就是Container。目前最新的OverlayFS为Overlay2

Device mapper是Linux内核2.6.9后支持的，提供的一种从逻辑设备到物理设备的映射框架机制，在该机制下，用户可以很方便的根据自己的需要制定实现存储资源的管理策略。前面讲的AUFS和OverlayFS都是文件级存储，而Device mapper是块级存储，所有的操作都是直接对块进行操作，而不是文件。Device mapper驱动会先在块设备上创建一个资源池，然后在资源池上创建一个带有文件系统的基本设备，所有镜像都是这个基本设备的快照，而容器则是镜像的快照。所以在容器里看到文件系统是资源池上基本设备的文件系统的快照，并没有为容器分配空间。当要写入一个新文件时，在容器的镜像内为其分配新的块并写入数据，这个叫用时分配。当要修改已有文件时，再使用CoW为容器快照分配块空间，将要修改的数据复制到在容器快照中新的块里再进行修改。Device mapper 驱动默认会创建一个100G的文件包含镜像和容器。每一个容器被限制在10G大小的卷内，可以自己配置调整。

存储驱动---特点---优点---缺点---适用场景 AUFS 联合文件系统、未并入内核主线、文件级存储 作为docker的第一个存储驱动，已经有很长的历史，比较稳定，且在大量的生产中实践过，有较强的社区支持 有多层，在做写时复制操作时，如果文件比较大且存在比较低的层，可能会慢一些 大并发但少IO的场景

overlayFS 联合文件系统、并入内核主线、文件级存储 只有两层 不管修改的内容大小都会复制整个文件，对大文件进行修改显示要比小文件消耗更多的时间 大并发但少IO的场景

Devicemapper 并入内核主线、块级存储 块级无论是大文件还是小文件都只复制需要修改的块，并不是整个文件 不支持共享存储，当有多个容器读同一个文件时，需要生成多个复本，在很多容器启停的情况下可能会导致磁盘溢出 适合io密集的场景

Btrfs 并入linux内核、文件级存储 可以像devicemapper一样直接操作底层设备，支持动态添加设备 不支持共享存储，当有多个容器读同一个文件时，需要生成多个复本 不适合在高密度容器的paas平台上使用 ZFS 把所有设备集中到一个存储池中来进行管理 支持多个容器共享一个缓存块，适合内存大的环境 COW使用碎片化问题更加严重，文件在硬盘上的物理地址会变的不再连续，顺序读会变的性能比较差 适合paas和高密度的场景

AUFS VS OverlayFS AUFS和Overlay都是联合文件系统，但AUFS有多层，而Overlay只有两层，所以在做写时复制操作时，如果文件比较大且存在比较低的层，则AUSF可能会慢一些。而且Overlay并入了linux kernel mainline，AUFS没有。目前AUFS已基本被淘汰

OverlayFS VS Device mapper OverlayFS是文件级存储，Device mapper是块级存储，当文件特别大而修改的内容很小，Overlay不管修改的内容大小都会复制整个文件，对大文件进行修改显示要比小文件要消耗更多的时间，而块级无论是大文件还是小文件都只复制需要修改的块，并不是整个文件，在这种场景下，显然device mapper要快一些。因为块级的是直接访问逻辑盘，适合IO密集的场景。而对于程序内部复杂，大并发但少IO的场景，Overlay的性能相对要强一些。

## docker的默认存储位置的修改

```
sudo vim /etc/docker/daemon.json
{
"graph": "/home/server/docker"
}
```

通过添加如上字段可以指定docker的存储位置

重启docker生效\
sudo systemctl daemon-reload && sudo systemctl restart docker

## dockerfile的构建知识

* 构建缓存\
  在镜像的构建过程中，Docker 根据 Dockerfile 指定的顺序执行每个指令。在执行每条指令之前，Docker 都会在缓存中查找是否已经存在可重用的镜像，如果有就使用现存的镜像，不再重复创建。当然如果你不想在构建过程中使用缓存，你可以在 docker build 命令中使用 --no-cache=true选项。Docker 中构建缓存遵循的基本规则如下： 从一个基础镜像开始（FROM 指令指定），下 一条指令将和该基础镜像的所有子镜像进行匹配，检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。如果不是，则缓存失效。 对于 ADD 和 COPY 指令，镜像中对应文件的内容也会被检查，每个文件都会计算出一个校验值。在缓存的查找过程中，会将这些校验和已存在镜像中的文件校验值进行对比。如果文件有任何改变，则缓存失效。 除了 ADD 和 COPY 指令，缓存匹配过程不会查看临时容器中的文件来决定缓存是否匹配。例如，当执行完 RUN apt-get -y update 指令后，容器中一些文件被更新，但 Docker 不会检查这些文件。这种情况下，只有指令字符串本身被用来匹配缓存。 一旦缓存失效，所有后续的 Dockerfile 指令都将产生新的镜像，缓存不会被使用
* 使用多阶段构建\
  多阶段构建可以让我们大幅度减小最终的镜像大小，而不需要去想办法减少中间层和文件的数量。因为镜像是在生成过程的最后阶段生成的，所以可以利用生成缓存来最小化镜像层
* 避免安装不必要的包\
  为了降低复杂性、减少依赖、减小文件大小和构建时间，应该避免安装额外的或者不必要的软件包。例如，不要在数据库镜像中包含一个文本编辑器。
* 应用解耦\
  每个容器应用只关心一个方面的事情。将多个应用解耦到不同容器中，可以更轻松地保证容器的横向扩展和复用
* 最小化镜像层数\
  只有 RUN、COPY 和 ADD 指令会创建层，其他指令会创建临时的中间镜像，但是不会直接增加构建的镜像大小了
* 对多行参数进行排序\
  只要有可能，就将多行参数按字母顺序排序。这可以帮助你避免重复包含同一个包，更新包列表时也更容易，也更容易阅读和审查。建议在反斜杠符号  之前添加一个空格，可以增加可读性。


# 搭建Docker私有仓库

本文主要介绍了如何搭建docker私有镜像仓库。

## 服务端下载镜像registry

```
docker pull registry
```

## 生成登录的用户名和密码

```
mkdir -p /data/docker-registry/auth
docker run --entrypoint htpasswd docker.io/registry:latest -Bbn nwx 000000  >> /data/docker-registry/auth/htpasswd
```

## 节设置配置文件，启用删除镜像功能

也可以不启用，看业务需要，修改 storage - delete - enable 为 false 即可

```
mkdir -p /data/docker-registry/config
vim  /data/docker-registry/config/config.yml
version: 0.1
log:
  fields:
    service: registry
storage:
  delete:
    enabled: true
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /var/lib/registry
http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]
health:
  storagedriver:
    enabled: true
    interval: 10s
threshold: 3
```

## 启动registry镜像服务

```
docker run -d -p 5000:5000 --restart=always  --name=registry\
  -v /data/docker-registry/auth/:/auth/ \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -v /data/docker-registry/:/var/lib/registry/ \
docker.io/registry:latest
```

## 开启节点的http形式访问私有仓库

```
vim /etc/docker/daemon.json
{
    "log-driver": "json-file",
    "insecure-registries":["55.18.67.171:5000"]
}

systemctl daemon-reload
systemctl restart docker
```

## 上传和下载镜像到私有仓库

```
docker pull docker.io/hello-world
docker tag docker.io/hello-world:latest 106.54.126.251:5000/hello-word:latest
docker login 106.54.126.251:5000 -u nwx -p 000000
docker push 106.54.126.251:5000/hello-word:latest
curl -u nwx:000000  http://106.54.126.251:5000/v2/_catalog
curl -u nwx:000000  http://106.54.126.251:5000/v2/sprintboot/tags/list
```

## k8s创建拉取镜像秘钥

默认default命名空间使用的secret

```
kubectl create secret docker-registry 10.10.10.149  --docker-server=55.18.67.171:5000 --docker-username=hy --docker-password=000000 --docker-email=niewx@ruyi.ai
```

hy-uat命名空间使用的secret

```
kubectl create secret docker-registry 10.10.10.149  --docker-server=55.18.67.171:5000 –namespace=hy-uat --docker-username=hy --docker-password=000000 --docker-email=niewx@ruyi.ai
```

## 列出所有镜像

```
curl -u hy:000000  http://55.18.67.171:5000/v2/_catalog
```

## 列出busybox镜像有哪些tag

```
curl -u hy:000000  http://55.18.67.171:5000/v2/company-ner/tags/list
```


# crictl调试Kubernetes节点

本文介绍了TKE中使用containerd模式的集群常用的crictl命令

## 开始之前

crictl需要一个具有CRI运行时的Linux操作系统，直接在TKE中创建containerd模式的集群既可。

## 安装crictl

你可以从critools发布页面下载一个压缩的存档crictl，用于几种不同的体系构架。下载与Kubernetes版本对应的版本。解压并将其移动到系统路径上的一个位置，例如/usr/local/bin/。

## 一般用法

crictl命令有几个子命令和运行时选项。有关详细信息，请使用crictl help或crictl  help。

crictl默认连接到 unix:///var/run/dockershim.sock。对于其它运行时，你可以通过多种方式设置端点:

* 设置 --runtime-endpoint 和--image-endpoint选项。
* 设置 CONTAINER\_RUNTIME\_ENDPOINT 和IMAGE\_SERVICE\_ENDPOINT环境变量。
* 在配置文件 --config=/etc/crictl.yaml设置端点。

还可以在连接到服务器时指定超时值，并启用或禁用调试，方法是在配置文件中指定 timeout 和debug 值，或者使用--timeout和--debug命令行选项。 要查看或编辑当前配置，请查看或编辑/etc/crictl.yaml的内容

```
cat /etc/crictl.yamlruntime-endpoint: unix:///var/run/dockershim.sockimage-endpoint: unix:///var/run/dockershim.socktimeout: 10debug: true
```

## crictl命令示例

下面的示例显示了crictl命令和示例输出。

**警告**：如果你使用crictl在运行的Kubernetes集群上创建pod沙箱或容器，Kubelet最终将删除它们。crictl不是一个通用的工作流工具，而是一个对调试有用的工具。

## 获取pod列表

获取所有pod列表：

```
# crictl pods
POD ID              CREATED              STATE               NAME                         NAMESPACE           ATTEMPT
926f1b5a1d33a       About a minute ago   Ready               sh-84d7dcf559-4r2gq          default             0
4dccb216c4adb       About a minute ago   Ready               nginx-65899c769f-wv2gp       default             0
a86316e96fa89       17 hours ago         Ready               kube-proxy-gblk4             kube-system         0
919630b8f81f1       17 hours ago         Ready               nvidia-device-plugin-zgbbv   kube-system         0
```

根据名称获取pod列表：

```
crictl pods --name nginx-65899c769f-wv2gp

POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0

根据标签获取pod列表：
crictl pods --label run=nginx

POD ID              CREATED             STATE               NAME                     NAMESPACE           ATTEMPT
4dccb216c4adb       2 minutes ago       Ready               nginx-65899c769f-wv2gp   default             0
```

## 获取镜像列表

获取所有镜像列表：

```
crictl images

IMAGE                                     TAG                 IMAGE ID            SIZE
busybox                                   latest              8c811b4aec35f       1.15MB
k8s-gcrio.azureedge.net/hyperkube-amd64   v1.10.3             e179bbfe5d238       665MB
k8s-gcrio.azureedge.net/pause-amd64       3.1                 da86e6ba6ca19       742kB
nginx                                     latest              cd5239a0906a6       109MB
输出类似如下：
IMAGE                                     TAG                 IMAGE ID            SIZEbusybox                                   latest              8c811b4aec35f       1.15MBk8s-gcrio.azureedge.net/hyperkube-amd64   v1.10.3             e179bbfe5d238       665MBk8s-gcrio.azureedge.net/pause-amd64       3.1                 da86e6ba6ca19       742kBnginx                                     latest              cd5239a0906a6       109MB
```

根据仓库获取镜像列表：

```
crictl images nginx

IMAGE               TAG                 IMAGE ID            SIZE
nginx               latest              cd5239a0906a6       109MB
输出类似如下：
IMAGE               TAG                 IMAGE ID            SIZEnginx               latest              cd5239a0906a6       109MB
```

只列出镜像ID：

```
crictl images -q

sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
sha256:e179bbfe5d238de6069f3b03fccbecc3fb4f2019af741bfff1233c4d7b2970c5
sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e
sha256:cd5239a0906a6ccf0562354852fae04bc5b52d72a2aff9a871ddb6bd57553569
输出类似如下：
sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472asha256:e179bbfe5d238de6069f3b03fccbecc3fb4f2019af741bfff1233c4d7b2970c5sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43esha256:cd5239a0906a6ccf0562354852fae04bc5b52d72a2aff9a871ddb6bd57553569
```

## 获取容器列表

获取所有容器列表：

```
crictl ps -a

CONTAINER ID        IMAGE                                                                                                             CREATED             STATE               NAME                       ATTEMPT
1f73f2d81bf98       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   7 minutes ago       Running             sh                         1
9c5951df22c78       busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47                                   8 minutes ago       Exited              sh                         0
87d3992f84f74       nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f                                     8 minutes ago       Running             nginx                      0
1941fb4da154f       k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a   18 hours ago        Running
```

在运行的容器中执行命令

```
crictl exec -i -t 1f73f2d81bf98 ls

bin   dev   etc   home  proc  root  sys   tmp   usr   var
输出类似如下：
bin   dev   etc   home  proc  root  sys   tmp   usr   var
```

## 输出容器日志

获取所有容器日志：

```
crictl logs 87d3992f84f74
输出类似如下：
10.240.0.96 - - [06/Jun/2018:02:45:49 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"10.240.0.96 - - [06/Jun/2018:02:45:50 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
```

只获取最近的N行日志：

```
crictl logs --tail=1 87d3992f84f74
输出类似如下：
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
```

## 运行pod沙箱

使用crictl运行pod沙箱对于调试容器运行时非常有用。在运行的Kubernetes集群上，沙箱最终将被Kubelet停止和删除。

创建一个如下内容的JSON文件：

```
  {      "metadata": {          "name": "nginx-sandbox",          "namespace": "default",          "attempt": 1,          "uid": "hdishd83djaidwnduwk28bcsb"      },      "logDirectory": "/tmp",      "linux": {      }  }
```

使用crictl runp命令应用JSON并运行沙箱。

```
 crictl runp pod-config.json
```

返回了沙箱ID。

## 创建容器

使用crictl创建容器对于调试容器运行时非常有用。在运行的Kubernetes集群上，沙箱最终将被Kubelet停止和删除。

拉取沙箱镜像：

```
  crictl pull busybox  Image is up to date for busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
```

为pod和容器创建配置:

pod配置:

```
  {      "metadata": {          "name": "nginx-sandbox",          "namespace": "default",          "attempt": 1,          "uid": "hdishd83djaidwnduwk28bcsb"      },      "log_directory": "/tmp",      "linux": {      }  }
容器配置：
  {    "metadata": {        "name": "busybox"    },    "image":{        "image": "busybox"    },    "command": [        "top"    ],    "log_path":"busybox/0.log",    "linux": {    }  }
```

传递前面创建的pod、容器配置文件和pod配置文件的ID来创建容器。随即返回了容器的ID。

```
 crictl create f84dd361f8dc51518ed291fbadd6db537b0496536c1d2d6c05ff943ce8c9a54f container-config.json pod-config.json
```

列出所有容器，并验证新创建的容器是否将其状态设置为Created。

```
 crictl ps -a
输出类似如下：
  CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT  3e025dd50a72d       busybox             32 seconds ago      Created             busybox             0
```

启动容器

要启动容器，请将其ID传递给 crictl start：

```
crictl start 3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60
输出类似如下：
3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60
```

检查容器是否将其状态设置为Running。

```
crictl ps

CONTAINER ID        IMAGE               CREATED              STATE               NAME                ATTEMPT
3e025dd50a72d       busybox             About a minute ago   Running             busybox             0
```


# 强制删除Terminating的ns

本文主要介绍了在使用k8s的过程中Terminating的ns无法删除，如何强制删除Terminating的ns。

## kubectl get ns 查看处于Terminating的ns

```
[root@VM_1_4_centos ~]# kubectl get ns | grep testns
testns                   Terminating   21d
```

## 将处于Terminating的ns的描述文件保存下来

```
[root@VM_1_4_centos ~]# kubectl get ns testns -o json > tmp.json
[root@VM_1_4_centos ~]# cat tmp.json 
{
    "apiVersion": "v1",
    "kind": "Namespace",
    "metadata": {
        "creationTimestamp": "2020-10-13T14:28:07Z",
        "name": "testns",
        "resourceVersion": "13782744400",
        "selfLink": "/api/v1/namespaces/testns",
        "uid": "9ff63d71-a4a1-43bc-89e3-78bf29788844"
    },
    "spec": {
        "finalizers": [
            "kubernetes"
        ]
    },
    "status": {
        "phase": "Terminating"
    }
}
```

## 本地启动kube proxy

```
kubectl proxy --port=8081
```

## 新开窗口执行删除操作

```
curl -k -H "Content-Type: application/json" -X PUT --data-binary @tmp.json http://127.0.0.1:8081/api/v1/namespaces/testns/finalize
```


# kubernetes之StatefulSet控制器

本文将带你了解k8s中的StatefulSet控制器，将通过实验的方式来说明StatefulSet的用法和配置，让你快速能够将StatefulSet类型的服务用到你的k8s集群中。

## 什么是StatefulSet

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

StatefulSet 用来管理 Deployment 和扩展一组 Pod，并且能为这些 Pod 提供序号和唯一性保证。

和 Deployment 相同的是，StatefulSet 管理了基于相同容器定义的一组 Pod。但和 Deployment 不同的是，StatefulSet 为它们的每个 Pod 维护了一个固定的 ID。这些 Pod 是基于相同的声明来创建的，但是不能相互替换：无论怎么调度，每个 Pod 都有一个永久不变的 ID。

StatefulSet 和其他控制器使用相同的工作模式。你在 StatefulSet 对象 中定义你期望的状态，然后 StatefulSet 的 控制器 就会通过各种更新来达到那种你想要的状态。

StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值：

* 稳定的、唯一的网络标识符。
* 稳定的、持久的存储。
* 有序的、优雅的部署和缩放。
* 有序的、自动的滚动更新。&#x20;

在上面，稳定意味着 Pod 调度或重调度的整个过程是有持久性的。如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩，则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序，比如 Deployment 或者 ReplicaSet 可能更适用于您的无状态应用部署需要。

**使用限制**

* 给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供，或者由管理员预先提供。
* 删除或者收缩 StatefulSet 并不会删除它关联的存储卷。这样做是为了保证数据安全，它通常比自动清除 StatefulSet 所有相关的资源更有价值。
* StatefulSet 当前需要无头服务 来负责 Pod 的网络标识。您需要负责创建此服务。
* 当删除 StatefulSets 时，StatefulSet 不提供任何终止 Pod 的保证。为了实现 StatefulSet 中的 Pod 可以有序和优雅的终止，可以在删除之前将 StatefulSet 缩放为 0。
* 在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新，可能进入需要 人工干预 才能修复的损坏状态。

## pod的标识符

StatefulSet Pod 具有唯一的标识，该标识包括顺序标识、稳定的网络标识和稳定的存储。该标识和 Pod 是绑定的，不管它被调度在哪个节点上。注意的是StatefulSet 需要通过无头服务才能解析到pod ip

有序索引 对于具有 N 个副本的 StatefulSet，StatefulSet 中的每个 Pod 将被分配一个整数序号，从 0 到 N-1，该序号在 StatefulSet 上是唯一的。

稳定的网络 ID StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。 组合主机名的格式为$(StatefulSet 名称)-$(序号)。上例将会创建三个名称分别为 web-0、web-1、web-2 的 Pod。 StatefulSet 可以使用 headless 服务 控制它的 Pod 的网络域。管理域的这个服务的格式为： $(服务名称).$(命名空间).svc.cluster.local，其中 cluster.local 是集群域。 一旦每个 Pod 创建成功，就会得到一个匹配的 DNS 子域，格式为： $(pod 名称).$(所属服务的 DNS 域名)，其中所属服务由 StatefulSet 的 serviceName 域来设定。

下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例

| 集群域名          | 服务(名字空间/名字)   | StatefulSet(名字空间/名字) | StatefulSet 域名                  | Pod DNS                                      | Pod 主机名      |
| ------------- | ------------- | -------------------- | ------------------------------- | -------------------------------------------- | ------------ |
| cluster.local | default/nginx | default/web          | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
| cluster.local | foo/nginx     | foo/web              | nginx.foo.svc.cluster.local     | web-{0..N-1}.nginx.foo.svc.cluster.local     | web-{0..N-1} |
| kube.local    | foo/nginx     | foo/web              | nginx.foo.svc.kube.local        | web-{0..N-1}.nginx.foo.svc.kube.local        | web-{0..N-1} |

```
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: webs
spec:
  selector:
    matchLabels:
      app: nginx # has to match .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # by default is 1
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
```

下面我们在其他容器中解析一下pod名称看一下是否能解析到pod ip

![upload-image](/files/-MN2J8wZODEOGE8xVjl9)

我们在centos容器中解析对应的pod名称看看,可以发现解析每个pod名称都能成功到pod ip，pod标识符也正是StatefulSet 独有的特性。

![upload-image](/files/-MN2J8w_tcZyZPx-0PnK)

## 稳定的存储

Kubernetes 为每个 VolumeClaimTemplate 创建一个 PersistentVolumes。 在上面的 nginx 示例中，每个 Pod 将会得到基于 StorageClass cbs 提供的 10 Gib 的 PersistentVolume。如果没有声明 StorageClass，就会使用默认的 StorageClass。 当一个 Pod 被调度（重新调度）到节点上时，它的 volumeMounts 会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意，当 Pod 或者 StatefulSet 被删除时，与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。

下面我们来测试一下将pod都删除看看，对应的pvc是否会删除

![upload-image](/files/-MN2J8wbcIZISfKsvHDr)

![upload-image](/files/-MN2J8wcyp5_UTWZ5u6u)

删除了所有pod，对用挂载的pvc卷是不会删除的。

## Pod 名称标签

当 StatefulSet 控制器 创建 Pod 时，它会添加一个标签 statefulset.kubernetes.io/pod-name，该标签设置为 Pod 名称。这个标签允许您给 StatefulSet 中的特定 Pod 绑定一个 Service。

```
[root@VM_1_4_centos ~]# kubectl get pod --show-labels | grep webss
webss-0                                   2/2     Running   0          76m   app=nginx,controller-revision-hash=webss-6d657db877,k8s-app=webss,qcloud-app=webss,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=nginx,service.istio.io/canonical-revision=latest,statefulset.kubernetes.io/pod-name=webss-0
webss-1                                   2/2     Running   0          75m   app=nginx,controller-revision-hash=webss-6d657db877,k8s-app=webss,qcloud-app=webss,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=nginx,service.istio.io/canonical-revision=latest,statefulset.kubernetes.io/pod-name=webss-1
webss-2                                   2/2     Running   0          74m   app=nginx,controller-revision-hash=webss-6d657db877,k8s-app=webss,qcloud-app=webss,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=nginx,service.istio.io/canonical-revision=latest,statefulset.kubernetes.io/pod-name=webss-2
```

```
[root@VM_1_4_centos ~]# kubectl get svc  webss-0 -o yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2020-10-25T03:06:35Z"
  labels:
    app: nginx
  name: webss-0
  namespace: default
  resourceVersion: "14187854412"
  selfLink: /api/v1/namespaces/default/services/webss-0
  uid: 16f50d64-533c-4c55-acb6-d7512d86cc58
spec:
  clusterIP: 172.16.255.138
  ports:
  - name: web
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    statefulset.kubernetes.io/pod-name: webss-0
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
```

这边可以通过这个svc单独访问到webss-0

## StatefulSet的创建和扩缩容

* 对于包含 N 个 副本的 StatefulSet，当部署 Pod 时，它们是依次创建的，顺序为 0..N-1。
* 当删除 Pod 时，它们是逆序终止的，顺序为 N-1..0。
* 在将缩放操作应用到 Pod 之前，它前面的所有 Pod 必须是 Running 和 Ready 状态。
* 在 Pod 终止之前，所有的继任者必须完全关闭。

StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds 设置为 0。 这种做法是不安全的，要强烈阻止。更多的解释请参考 强制删除 StatefulSet Pod。

![upload-image](/files/-MN2J8wdMyCCZ4Y2o4vs)

在上面的 nginx 示例被创建后，会按照 webss-0、webss-1、webss-2 的顺序部署三个 Pod。 在 web-0 进入 Running 和 Ready 状态前不会部署 webss-1。在 webss-1 进入 Running 和 Ready 状态前不会部署 webss-2。 如果 web-1 已经处于 Running 和 Ready 状态，而 webss-2 尚未部署，在此期间发生了 web-0 运行失败，那么 webss-2 将不会被部署，要等到 webss-0 部署完成并进入 Running 和 Ready 状态后，才会部署 web-2。

如果用户想将示例中的 StatefulSet 收缩为 replicas=0，首先被终止的是 webss-2。在 webss-2 没有被完全停止和删除前，webss-1 不会被终止。当 webss-2 已被终止和删除、webss-1 尚未被终止，如果在此期间发生 webss-0 运行失败，那么就不会终止 webss-1，必须等到 webss-0 进入 Running 和 Ready 状态后才会终止 webss-1。

我们可以先删除pod，然后创建pod，看下对应的event日志

```
[root@VM_1_4_centos ~]# kubectl describe sts webss
Name:               webss
Namespace:          default
CreationTimestamp:  Sun, 25 Oct 2020 09:39:47 +0800
Selector:           app=nginx,k8s-app=webss,qcloud-app=webss
Labels:             app=nginx
                    k8s-app=webss
                    qcloud-app=webss
Annotations:        <none>
Replicas:           3 desired | 3 total
Update Strategy:    RollingUpdate
...............
Events:
  Type    Reason            Age                  From                    Message
  ----    ------            ----                 ----                    -------
  Normal  SuccessfulDelete  118s (x2 over 151m)  statefulset-controller  delete Pod webss-2 in StatefulSet webss successful
  Normal  SuccessfulDelete  111s (x2 over 151m)  statefulset-controller  delete Pod webss-1 in StatefulSet webss successful
  Normal  SuccessfulDelete  97s (x2 over 151m)   statefulset-controller  delete Pod webss-0 in StatefulSet webss successful
  Normal  SuccessfulCreate  70s (x3 over 3h49m)  statefulset-controller  create Pod webss-0 in StatefulSet webss successful
  Normal  SuccessfulCreate  47s (x3 over 3h49m)  statefulset-controller  create Pod webss-1 in StatefulSet webss successful
  Normal  SuccessfulCreate  24s (x3 over 3h48m)  statefulset-controller  create Pod webss-2 in StatefulSet webss successful
```

可以发现pod的启动是按照顺序创建和删除的

**podManagementPolicy管理pod**

那么StatefulSet 是如何保证对应的pod按照顺序启动的呢，必须需要等前面一个pod启动才能启动后面的pod，那么我们可以去除这个依赖来启动pod吗？让pod并行启动，这边当然是可以的。StatefulSet可以通过podManagementPolicy这个参数来配置pod的启动顺序。

podManagementPolicy: OrderedReady这个是默认的配置，就是按照一定的顺序先后启动。

podManagementPolicy可以将值设置为Parallel，这个值就是StatefulSet 控制器并行的启动或终止所有的 Pod， 启动或者终止其他 Pod 前，无需等待 Pod 进入 Running 和 ready 或者完全停止状态。

```
apiVersion: apps/v1
kind: StatefulSet
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"web","namespace":"default"},"spec":{"podManagementPolicy":"Parallel","replicas":3,"selector":{"matchLabels":{"app":"nginx"}},"serviceName":"nginx","template":{"metadata":{"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"k8s.gcr.io/nginx-slim:0.8","name":"nginx","ports":[{"containerPort":80,"name":"web"}]}],"terminationGracePeriodSeconds":10}}}}
  creationTimestamp: "2020-10-25T05:42:38Z"
  generation: 2
  name: web
  namespace: default
  resourceVersion: "8286286"
  selfLink: /apis/apps/v1/namespaces/default/statefulsets/web
  uid: 4e50f5b0-d23d-4fca-978e-f3547c047dd3
spec:
  podManagementPolicy: Parallel
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
....................
```

![upload-image](/files/-MN2J8wifMuzOGdPzdVg)

可以从上面event事件看出，我们将podManagementPolicy设置为Parallel，pod是并行同时启动的

## StatefulSet的更新

在 Kubernetes 1.7 及以后的版本中，StatefulSet 的.spec.updateStrategy字段让您可以配置和禁用掉自动滚动更新 Pod 的容器、标签、资源请求或限制、以及注解。默认的配置是滚动更新

### RollingUpdate更新

RollingUpdate更新策略对 StatefulSet 中的 Pod 执行自动的滚动更新。在没有声明.spec.updateStrategy时，RollingUpdate是默认配置。 当 StatefulSet 的.spec.updateStrategy.type被设置为RollingUpdate时，StatefulSet 控制器会删除和重建 StatefulSet 中的每个 Pod。 它将按照与 Pod 终止相同的顺序（从最大序号到最小序号）进行，每次更新一个 Pod。它会等到被更新的 Pod 进入 Running 和 Ready 状态，然后再更新其前身。

```
      terminationGracePeriodSeconds: 30
  updateStrategy:
    rollingUpdate:
      partition: 0
    type: RollingUpdate
```

下面我们修改下镜像，看看滚动更新是否和上面描述一致，我们修改镜像版本为1.17版本，从事件看，更新顺序是先更新最大号序号，再更新最小序号的

```
   nginx:
    Image:        nginx:1.17
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Volume Claims:    <none>
Events:
  Type    Reason            Age                  From                    Message
  ----    ------            ----                 ----                    -------
  Normal  SuccessfulDelete  93s                  statefulset-controller  delete Pod web-2 in StatefulSet web successful
  Normal  SuccessfulCreate  85s (x2 over 3m28s)  statefulset-controller  create Pod web-2 in StatefulSet web successful
  Normal  SuccessfulDelete  46s                  statefulset-controller  delete Pod web-1 in StatefulSet web successful
  Normal  SuccessfulCreate  36s (x2 over 4m20s)  statefulset-controller  create Pod web-1 in StatefulSet web successful
  Normal  SuccessfulDelete  12s                  statefulset-controller  delete Pod web-0 in StatefulSet web successful
  Normal  SuccessfulCreate  5s (x2 over 4m59s)   statefulset-controller  create Pod web-0 in StatefulSet web successful
```

### OnDelete 更新

OnDelete 更新策略实现了 1.6 及以前版本的历史遗留行为。当 StatefulSet 的 .spec.updateStrategy.type 设置为 OnDelete 时，它的控制器将不会自动更新 StatefulSet 中的 Pod。用户必须手动删除 Pod 以便让控制器创建新的 Pod，以此来对 StatefulSet 的 .spec.template 的变动作出反应。

```
      containers:
      - image: nginx:1.17
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
          name: web
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 10
  updateStrategy:
    type: OnDelete
```

下面我们把修改镜像成latest，看下对应的pod会不会更新

```
spec:
  podManagementPolicy: OrderedReady
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:latest
        imagePullPolicy: Always
        name: nginx

[root@VM-4-3-centos ~]# kubectl delete pod web-1
pod "web-1" deleted
[root@VM-4-3-centos ~]# kubectl get pod 
NAME    READY   STATUS              RESTARTS   AGE
web-0   1/1     Running             0          6m
web-1   0/1     ContainerCreating   0          2s
web-2   1/1     Running             0          7m20s
[root@VM-4-3-centos ~]# kubectl describe pod web-1
Name:           web-1
Namespace:      default
.....................
Events:
  Type    Reason     Age   From                     Message
  ----    ------     ----  ----                     -------
  Normal  Scheduled  11s   default-scheduler        Successfully assigned default/web-1 to vm-4-11-centos
  Normal  Pulling    10s   kubelet, vm-4-11-centos  Pulling image "nginx:latest"
  Normal  Pulled     0s    kubelet, vm-4-11-centos  Successfully pulled image "nginx:latest"
  Normal  Created    0s    kubelet, vm-4-11-centos  Created container nginx
  Normal  Started    0s    kubelet, vm-4-11-centos  Started container nginx
```

可以发现我们修改yaml后，pod并没有更新，只有手动删除某个pod后，pod才会进行更新

### 分区更新

通过声明.spec.updateStrategy.rollingUpdate.partition的方式，RollingUpdate更新策略可以实现分区。如果声明了一个分区，当 StatefulSet 的.spec.template被更新时，所有序号大于等于该分区序号的 Pod 都会被更新。所有序号小于该分区序号的 Pod 都不会被更新，并且，即使他们被删除也会依据之前的版本进行重建。如果 StatefulSet 的.spec.updateStrategy.rollingUpdate.partition大于它的.spec.replicas，对它的.spec.template的更新将不会传递到它的 Pod。 在大多数情况下，您不需要使用分区，但如果您希望进行阶段更新、执行金丝雀或执行分阶段展开，则这些分区会非常有用

这边通常spec.updateStrategy.rollingUpdate.partition一般需要小于replicas的数值，这样才会生效，下面我们来测试一下分区更新

```
spec:
  podManagementPolicy: OrderedReady
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
          name: web
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 10
  updateStrategy:
    rollingUpdate:
      partition: 2
    type: RollingUpdate
[root@VM-4-3-centos ~]# kubectl describe sts web
Name:               web
Namespace:          default
CreationTimestamp:  Sun, 25 Oct 2020 14:09:02 +0800
Selector:           app=nginx
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration:
                      {"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"web","namespace":"default"},"spec":{"podManagementPolicy...
Replicas:           3 desired | 3 total
Update Strategy:    RollingUpdate
  Partition:        824636860812
Pods Status:        3 Running / 0 Waiting / 0 Succeeded / 0 Failed
........
Events:
  Type    Reason            Age                  From                    Message
  ----    ------            ----                 ----                    -------
  Normal  SuccessfulCreate  5m8s                 statefulset-controller  create Pod web-0 in StatefulSet web successful
  Normal  SuccessfulCreate  4m32s                statefulset-controller  create Pod web-1 in StatefulSet web successful
  Normal  SuccessfulDelete  26s                  statefulset-controller  delete Pod web-2 in StatefulSet web successful
  Normal  SuccessfulCreate  19s (x2 over 4m12s)  statefulset-controller  create Pod web-2 in StatefulSet web successful
[root@VM-4-3-centos ~]# kubectl get pod 
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          5m12s
web-1   1/1     Running   0          4m36s
web-2   1/1     Running   0          23s
```

可以发现，只有大于等于分区需要才会被更新，这里也只有web-2被更新

### 强制回滚

在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新 ，可能进入需要人工干预才能修复的损坏状态。

如果更新后 Pod 模板配置进入无法运行或就绪的状态（例如，由于错误的二进制文件或应用程序级配置错误），StatefulSet 将停止回滚并等待。

在这种状态下，仅将 Pod 模板还原为正确的配置是不够的。由于 已知问题，StatefulSet 将继续等待损坏状态的 Pod 准备就绪（永远不会发生），然后再尝试将其恢复为正常工作配置。

恢复模板后，还必须删除 StatefulSet 尝试使用错误的配置来运行的 Pod。这样， StatefulSet 才会开始使用被还原的模板来重新创建 Pod

我们日常在更新镜像的时候，发现服务启动失败了，然后将镜像回滚成之前的版本，这样仅仅是不够的，因为StatefulSet 还会一直等待新更新的pod状态ready，但是这个永远不会发生，所以为了恢复正常，我们需要删除更新启动失败的pod。

下面我们来测试下，我们将pod修改成k8s.gcr.io/nginx-slim:0.8，节点上无法拉取这个镜像的，所以web-2这个pod会一直启动失败。

```
[root@VM-4-3-centos ~]# kubectl get pod 
NAME    READY   STATUS             RESTARTS   AGE
web-0   1/1     Running            0          13m
web-1   1/1     Running            0          12m
web-2   0/1     ImagePullBackOff   0          44s
下面我们将镜像改成之前的nginx:1.17，看看会不会恢复

spec:
  podManagementPolicy: OrderedReady
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.17
        imagePullPolicy: Always
[root@VM-4-3-centos ~]# kubectl get pod 
NAME    READY   STATUS         RESTARTS   AGE
web-0   1/1     Running        0          15m
web-1   1/1     Running        0          14m
web-2   0/1     ErrImagePull   0          2m52s
[root@VM-4-3-centos ~]# kubectl delete pod web-2 
pod "web-2" deleted
[root@VM-4-3-centos ~]# kubectl get pod 
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          14s
web-1   1/1     Running   0          33s
web-2   1/1     Running   0          45s
```

从上面的结果可以看出，我们不删除web-2这个更新异常的pod，所以的pod都不会更新的，只有删除了web-2这个更新失败的pod，sts才会进行正常更新。

## 参考文档

<https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset/#deployment-and-scaling-guarantees>


# kubewatch监控k8s集群资源变更

这次要介绍一个 Kubernetes 资源观测工具，实时监控 Kubernetes 集群中各种资源的新建、更新和删除，并实时通知到各种协作软件/聊天软件，目前支持的通知渠道有：

* slack
* hipchat
* mattermost
* flock
* webhook

本次实验环境采用的是腾讯云上TKE托管集群，通知发生采用的是发送到slack上。

## 申请slack账号

这边首先申请一个个人slack账号，申请后创建一个app，并且创建一个告警channel将app关联上去

### 创建slack账号

通过企业邮箱去页面 <https://slack.com/get-started#/create> 创建你的slack命名空间，这里根据提示填写邮箱信息即可。

![upload-image](/files/-MN2J7a_XfnV4Z6IZ8XX)

### 创建APP

通过页面 <https://api.slack.com/apps> 点击创建New App

![upload-image](/files/-MN2J7ab9zblMR-MzXVy)

填写你的APP Name和你的workspace，我这里之前创建一个kubewatch的app

![upload-image](/files/-MN2J7acEDy2sqQRyRKV)

给APP申请权限，这边点击0Auth

![upload-image](/files/-MN2J7adnn17TdxpYw7E)

点击添加权限按钮给APP添加权限，这边注意最后给admin的token权限

![upload-image](/files/-MN2J7aeORU6UuzBxgtx)

### 安装APP

添加完权限后，点击安装APP按钮安装到你的workspaces

![upload-image](/files/-MN2J7afXVrrzSBaIv9_)

安装完成后，复制保存APP的token。这里后续需要用到，配置到kubewatch的配置文件中。

![upload-image](/files/-MN2J7agd8MlW8YWgkM2)

### 创建channel关联APP接收信息

这里我们创建一个test的channel来接受kubewatch发送的信息

![upload-image](/files/-MN2J7ahi_ZwA23nYJYO)

点击Connect an app将kubewatch app关联到test channel

![upload-image](/files/-MN2J7aiKSI0qwCj5fJT)

关联成功后，后续消息将会发生到test channel中

![upload-image](/files/-MN2J7ajv9ubOJYopBm_)

## 部署kubewatch到k8s集群中

### 腾讯云控制台部署到TKE集群

点击容器服务的应用页面，选择你的集群，点击新建

![upload-image](/files/-MN2J7ak88NvlZomefbM)

填写你的应用名，所部属的命名空间，选择kubewatch应用，修改value.yaml

![upload-image](/files/-MN2J7alQ5CBgsGV3ukW)

修改enabled为ture，channel为之前接收消息的channel，我这里是test，将之前APP的token填写到token配置项

![upload-image](/files/-MN2J7am83G2drRsSPDC)

### helm命令部署到集群中

通过helm客户端执行命令部署kubewatch，如何安装使用helm可以参考<https://cloud.tencent.com/developer/article/1696689>

```
[root@VM-6-17-centos ~]# helm repo add bitnami https://charts.bitnami.com/bitnami
[root@VM-6-17-centos ~]# helm fetch bitnami/kubewatch
[root@VM-6-17-centos ~]# tar -xvf kubewatch-1.2.6.tgz
[root@VM-6-17-centos ~]# cd kubewatch/
[root@VM-6-17-centos ~]# vi values.yaml
[root@VM-6-17-centos ~]# helm install ./kubewatch --namespace kubewatch --name nwx-kubewatch
```

这里vi修改一下values.yam文件如下,修改enabled为ture，channel为之前接收消息的channel，我这里是test，将之前APP的token填写到token配置项

![upload-image](/files/-MN2J7an5lkQjSxMofTX)

查看pod日志，检查服务是否允许，这边pod出现如下日志，则表示接入slack成功

![upload-image](/files/-MN2J7aoMPkg2XgC-hRd)

## k8s集群资源变更测试

下面我们尝试重建一个pod，看下slack是否会接收到变更信息，我们在test下部署一个busybox的pod

![upload-image](/files/-MN2J7apb-55DSmav7oU)

从下图可以发现，slack有接收到变更的信息，说明我们已经成功部署kubewatch监控k8s集群并接入到slack

![upload-image](/files/-MN2J7aqQ5mDVFSVGEhF)


# 日志聚合工具Loki

本文章简要介绍了如何在腾讯云的TKE集群中搭建Loki日志采集工具。

## Loki日志系统介绍

Loki是受Prometheus启发的水平可扩展，高度可用的多租户日志聚合系统。它的设计具有很高的成本效益，并且易于操作。它不索引日志的内容，而是为每个日志流设置一组标签。

与其他日志聚合系统相比，Loki的特点

* 不对日志进行全文本索引。通过存储压缩的，非结构化的日志以及仅索引元数据，Loki更加易于操作且运行成本更低。
* 使用与Prometheus相同的标签对日志流进行索引和分组，从而使您能够使用与Prometheus相同的标签在指标和日志之间无缝切换。
* 特别适合存储Kubernetes Pod日志。诸如Pod标签之类的元数据会自动被抓取并建立索引。
* 在Grafana中具有本机支持（需要Grafana v6.0）。

基于Loki的日志记录堆栈包含3个组件：

* promtail是代理，负责收集日志并将其发送给Loki。
* loki是主服务器，负责存储日志和处理查询。
* Grafana用于查询和显示日志。

![upload-image](/files/-MN2J9-4Llqef9Ylpp3v)

Loki其实类似于elk和prometheus的架构，部署在k8s中，通过DaemonSet的方式部署promtail用来采集节点的日志，将采集的日志数据投递到loki，最后通过grafana来进行前端的界面查询和展示。

Loki像Prometheus一样，但是对于日志：我们更喜欢基于多维标签的索引方法，并且想要一个没有依赖性的，易于操作的单二进制系统。 Loki与Prometheus的不同之处在于，它侧重于日志而不是指标，并通过推送而不是拉动交付日志。

## 部署Loki和grafana到k8s集群

### 部署loki和promtail

这里我们通过helm来部署loki和promtail到集群中，这里使用的环境是腾讯云上的托管TKE集群，在腾讯云上的容器服务有直接提供loki应用让我们直接部署到集群中，下面我们说一下部署方式

#### 控制台部署loki应用到集群

点击腾讯云容器服务，点击应用，选择你的集群，点击新建。

![upload-image](/files/-MN2J9-64puMeODG_81H)

输入你的应用名和应用部署的命名空间，在日志分析中找到loki-stack，然后点击完成即可。

![upload-image](/files/-MN2J9-7YlKktWilLjsn)

这边部署成功后可以去控制台进入loki查看应用，你会发现部署loki和对应的promtail，这些pod都正常启动，说明loki和promtail都部署成功了。

![upload-image](/files/-MN2J9-8aPaaIhyMkcdh)

![upload-image](/files/-MN2J9-9iUJGl7rGJCue)

#### helm命令部署loki应用到集群

当然如果你习惯用helm命令操作部署也是可以的，下面讲一下helm命名的部署方式

```
//添加loki仓库
$ helm repo add loki https://grafana.github.io/loki/charts
$ helm repo update
//采用默认配置部署
$ helm upgrade --install loki --namespace=loki --name=nwx-loki loki/loki-stack 
//部署Loki和Fluent Bit到您的集群
$ helm upgrade --install loki --namespace=loki --name=nwx-loki \
    --set fluent-bit.enabled=true,promtail.enabled=false
```

### 部署grafana

#### 部署grafana及对应的service

下面我们部署grafana作为前端界面来查询loki所采集的日志，这里在选择grafana版本需要注意，版本需要6.0以上的版本。

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  labels:
    k8s-app: gafana
    qcloud-app: gafana
  name: gafana
  namespace: loki
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: gafana
      qcloud-app: gafana
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      labels:
        k8s-app: gafana
        qcloud-app: gafana
    spec:
      containers:
      - image: grafana/grafana:master
        imagePullPolicy: Always
        name: gafana
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: qcloudregistrykey
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
```

```
apiVersion: v1
kind: Service
metadata:
  name: gafana
  namespace: loki
spec:
  externalTrafficPolicy: Cluster
  ports:
  - name: 3000-3000-tcp
    nodePort: 32074
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    k8s-app: gafana
    qcloud-app: gafana
  sessionAffinity: None
  type: NodePort
```

直接kubectl apply上面2个yaml文件创建grafana应用和对应的service，也可以控制台创建

![upload-image](/files/-MN2J9-DNbCy5e71Mcbh)

![upload-image](/files/-MN2J9-E58ADyBuMCmn0)

#### 通过treafik给grafana配置域名访问

这里集群部署的treafik是2.0版本，关于treafik2.0部署可以参考 <https://cloud.tencent.com/developer/article/1642306>

```
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: loki
  namespace: loki
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`loki.tx.niewx.cn`)
    kind: Rule
    services:
    - name: gafana
      port: 3000
```

直接添加一个IngressRoute就可以了，下面我们通过配置的域名看看是否可以成功。

![upload-image](/files/-MN2J9-HsEkWk4UmbBuy)

可以进入登陆界面表示部署成功。

## 通过grafana界面查询日志

### grafana配置loki数据源

下面我们需要在grafana配置一下loki的数据源，这样我们才可以在grafana上查询loki的日志，首先我们登录grafana，初始化账号密码是admin/admin，第一次登录会要求你修改密码，登录成功后有一个grafana介绍。

![upload-image](/files/-MN2J9-If-7DFFUf-jQR)

找到配置选项的Data Sources，点击添加数据源，选择loki，在loki数据源的url配置上你的service名称地址，我的service名称是nwx-loki，因此我们这里配置的是这个地址 [http://nwx-loki:3100，配置好后点击测试，如果能连接成功说明添加成功。](http://nwx-loki/:3100，配置好后点击测试，如果能连接成功说明添加成功。)

![upload-image](/files/-MN2J9-JE9b4pDMR-3UY)

![upload-image](/files/-MN2J9-KZWMbseKlW_a3)

![upload-image](/files/-MN2J9-LXoczgE8ILGQv)

![upload-image](/files/-MN2J9-MLzCg4UBWkSLq)

### 界面上查询日志

添加数据源后，点击explore可以直接进到loki的查询界面

![upload-image](/files/-MN2J9-NO9bkvvaD-DZo)

![upload-image](/files/-MN2J9-Ou4-HkMJHL7at)

我们这边查询一下loki的日志看看，点击log label选择app中的loki就可以看到loki的业务日志

![upload-image](/files/-MN2J9-PyZ3TYxOqpY-Z)

![upload-image](/files/-MN2J9-Q6ArprBONPHh2)

### loki查询语法

下面我们简单的测试一下loki的查询语法

#### job日志流

```
{job="efk/elasticsearch-master"}
```

![upload-image](/files/-MN2J9-RqENJUIeQo4S_)

#### 合并流选择器

```
{app ="elasticsearch-master",namespace ="efk"}
```

![upload-image](/files/-MN2J9-SmbvUyIhWJZ1_)

#### 标签匹配的运算符

* \=： 完全相等。
* !=： 不相等。
* \=\~： 正则表达式匹配。
* !\~： 正则表达式不匹配。

  ```
  {app != "kibana",namespace ="efk"}
  ```

![upload-image](/files/-MN2J9-TZHrYZlvs3X20)

#### 行筛选器表达式

* |=：日志行包含字符串。
* !=：日志行不包含字符串。
* |\~：日志行与正则表达式匹配。
* !\~：日志行与正则表达式不匹配。

```
{app = "kibana",namespace ="efk"} |= "172.16.2.108"
```

![upload-image](/files/-MN2J9-ULb3l_0X-05zd)

筛选器运算符可以多个并将按顺序筛选表达式 - 生成的日志行必须同时满足所有条件。

```
{app = "kibana",namespace ="efk"} |= "172.16.2.108" != "04:58:40"
```

![upload-image](/files/-MN2J9-VVxOKDrpqUjPi)

#### 统计某一时间段的日志数

```
count_over_time({app = "kibana",namespace ="efk" }[1m])
```

![upload-image](/files/-MN2J9-W3gm4mOtXtmIM)

获取过去1个小时的日志计数，按级别分组

```
sum(count_over_time({job="default/nginx"}[60m])) by (level)
```

![upload-image](/files/-MN2J9-XNzLwhdDEjKR7)

loki配备了自己的查询LogQL，详细的查询语法参考<https://github.com/grafana/loki/blob/master/docs/sources/logql/_index.md>

有两种类型的 LogQL 查询：

* 日志查询返回日志行的内容。
* 指标查询扩展日志查询，并基于日志查询中的日志内容计算示例值。

logQL基本上能满足我们的日常查询需求。大家可以多多学习实际操作下。

## 参考链接

<https://github.com/grafana/loki>

<https://github.com/grafana/loki/blob/master/docs/sources/logql/_index.md>


# 关于kubernetes证书的那点事

本篇文章主要介绍了k8s中用的证书和组件之间是如何进行认证访问的。

## k8s证书介绍

众所周知。Kubernetes 需要 PKI 证书才能进行基于 TLS 的身份验证。如果你是使用kubeadm安装的 Kubernetes， 则会自动生成集群所需的证书。但是如果是通过二进制搭建，所有的证书是需要自己生成的，这里我们说说集群必需的证书。

* 服务器单向认证：只需要服务器端提供证书，客户端通过服务器端证书验证服务的身份，但服务器并不验证客户端的身份。这种情况一般适用于对 Internet 开放的服务，例如搜索引擎网站，任何客户端都可以连接到服务器上进行访问，但客户端需要验证服务器的身份，以避免连接到伪造的恶意服务器。
* 双向 TLS 认证：除了客户端需要验证服务器的证书，服务器也要通过客户端证书验证客户端的身份。这种情况下服务器提供的是敏感信息，只允许特定身份的客户端访问

Kubernetes为了安全性，都是采用双向认证。通常我们在部署k8s时候，kubeadm会自动生成集群所需要的证书，下面我们就这些证书一一给大家进行讲解。 这是我们用kubeadm搭建完一个集群后再/etc/kubernetes目录下所生成的文件

```
[root@VM-4-3-centos etc]# tree kubernetes/
kubernetes/
|-- admin.conf
|-- controller-manager.conf
|-- kubelet.conf
|-- scheduler.conf
|-- manifests
|   |-- etcd.yaml
|   |-- kube-apiserver.yaml
|   |-- kube-controller-manager.yaml
|   `-- kube-scheduler.yaml
|-- pki
|   |-- apiserver.crt
|   |-- apiserver-etcd-client.crt
|   |-- apiserver-etcd-client.key
|   |-- apiserver.key
|   |-- apiserver-kubelet-client.crt
|   |-- apiserver-kubelet-client.key
|   |-- ca.crt
|   |-- ca.key
|   |-- etcd
|   |   |-- ca.crt
|   |   |-- ca.key
|   |   |-- healthcheck-client.crt
|   |   |-- healthcheck-client.key
|   |   |-- peer.crt
|   |   |-- peer.key
|   |   |-- server.crt
|   |   `-- server.key
|   |-- front-proxy-ca.crt
|   |-- front-proxy-ca.key
|   |-- front-proxy-client.crt
|   |-- front-proxy-client.key
|   |-- sa.key
|   `-- sa.pub
```

![upload-image](/files/-MN2J9UFpjgt3HIhnDmY)

下面我们根据这个k8s的组件之间通讯图来一一讲解每个证书的作用。

## CA证书

kubeadm安装的集群中我们都是用3套CA证书来管理和签发其他证书，一套CA给ETCD使用，一套是给kubernetes内部组件使用，还有一套是给配置聚合层使用的，当然如果你觉得管理3套CA比较麻烦，您也可以用一套来管理。

## etcd证书

```
ca.crt  ca.key  healthcheck-client.crt  healthcheck-client.key  peer.crt  peer.key  server.crt  server.key
```

etcd证书位于/etc/kubernetes/pki/etcd目录下，我们根据etcd的yaml配置解释下证书的作用

```
spec:
  containers:
  - command:
    - etcd
    - --advertise-client-urls=https://10.0.4.3:2379
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --client-cert-auth=true
    - --data-dir=/var/lib/etcd
    - --initial-advertise-peer-urls=https://10.0.4.3:2380
    - --initial-cluster=vm-4-3-centos=https://10.0.4.3:2380
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    - --listen-client-urls=https://127.0.0.1:2379,https://10.0.4.3:2379
    - --listen-peer-urls=https://10.0.4.3:2380
    - --name=vm-4-3-centos
    - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
    - --peer-client-cert-auth=true
    - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
    - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --snapshot-count=10000
    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    image: k8s.gcr.io/etcd:3.3.10
    imagePullPolicy: IfNotPresent
    livenessProbe:
      exec:
        command:
        - /bin/sh
        - -ec
        - ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
          --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
          get foo
      failureThreshold: 8
```

* etcd 对外提供服务的服务器证书及私钥

  ```
  server.crt  server.key
  ```
* etcd 节点之间相互进行认证的 peer 证书、私钥以及验证 peer 的 CA

  ```
  ca.crt  ca.key peer.crt  peer.key
  ```
* etcd 验证访问其服务的客户端的 CA

  ```
  healthcheck-client.crt  healthcheck-client.key
  ```

## kube-apiserver证书

```
 name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.0.4.3
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --insecure-port=0
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --requestheader-allowed-names=front-proxy-client
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
    - --requestheader-username-headers=X-Remote-User
    - --secure-port=6443
    - --service-account-key-file=/etc/kubernetes/pki/sa.pub
    - --service-cluster-ip-range=10.96.0.0/12
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: k8s.gcr.io/kube-apiserver:v1.15.2
```

apiserver证书位于/etc/kubernetes/pki，同样我们通过apiserver的yaml文件来一一解释下每个证书的作用。

* 访问etcd的客户端证书及私钥，这个证书是由etcd的CA证书签发，因此也需要在apiserver中配置etcd的CA证书

  ```
  apiserver-etcd-client.key   apiserver-etcd-client.crt
  ```
* 用来签发k8s中其他证书的CA证书及私钥

  ```
  ca.crt  ca.key
  ```
* apiServer的对外提供服务的服务端证书及私钥

  ```
  apiserver.crt   apiserver.key
  ```
* apiserver 访问 kubelet 所需的客户端证书及私钥

  ```
  apiserver-kubelet-client.crt  apiserver-kubelet-client.key
  ```

  配置聚合层（apiserver扩展）的CA和客户端证书及私钥

  说明：要使聚合层在您的环境中正常工作以支持代理服务器和扩展 apiserver 之间的相互 TLS 身份验证， 需要满足一些设置要求。Kubernetes 和 kube-apiserver 具有多个 CA， 因此请确保代理是由聚合层 CA 签名的，而不是由主 CA 签名的。扩展apiserver为了能够和apiserver通讯，所以需要在apiserver中配置，假如你不需要这个功能可以不配置该证书。

  \`\`\`front-proxy-ca.crt  front-proxy-client.crt  front-proxy-ca.key      front-proxy-client.key &#x20;

  \`\`\`
* 验证 service account token 的公钥

  ```
  sa.pub
  ```

  到这里，集群生成的所有证书介绍完了，那么像kube-controller-mananger、kube-scheduler、kube-proxy、kubele这些组件也是需要访问apiserver的，那么他们是怎么通讯的呢？下面我们可以看看这些组件是如何和apiserver进行通讯的。

## kube-controller-mananger

还是和之前一样，我们通过kube-controller-mananger的yaml文件配置来看看是如何访问apiserver。

```
spec:
  containers:
  - command:
    - kube-controller-manager
    - --allocate-node-cidrs=true
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --bind-address=127.0.0.1
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --cluster-cidr=10.244.0.0/16
    - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
    - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
    - --controllers=*,bootstrapsigner,tokencleaner
    - --kubeconfig=/etc/kubernetes/controller-manager.conf
    - --leader-elect=true
    - --node-cidr-mask-size=24
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --root-ca-file=/etc/kubernetes/pki/ca.crt
    - --service-account-private-key-file=/etc/kubernetes/pki/sa.key
    - --use-service-account-credentials=true
    image: k8s.gcr.io/kube-controller-manager:v1.15.2
```

你会发现在yaml中配置了/etc/kubernetes/controller-manager.conf这个配置文件，而不是配置controller-manager的客户端证书之类的。Kubernetes 这里的设计是这样的，kube-controller-mananger、kube-scheduler、kube-proxy、kubelet等组件，采用一个kubeconfig 文件中配置的信息来访问 kube-apiserver。该文件中包含了 kube-apiserver 的地址，验证 kube-apiserver 服务器证书的 CA 证书，自己的客户端证书和私钥等访问信息，这样组件只需要配置这个kubeconfig就行。

由于创建工作负载的时候，我们有时候会用到service accout，那么这里需要和apiserver认证，所以我们需要在controller-manager

配置上sa私钥，当然需要和apiserver通讯，自然需要配置上kubernates的CA证书

下面我们看看controller-manager.conf这个文件配置的证书和秘钥是什么。

```
[root@VM-4-3-centos kubernetes]# cat controller-manager.conf 
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://10.0.4.3:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:kube-controller-manager
  name: system:kube-controller-manager@kubernetes
current-context: system:kube-controller-manager@kubernetes
kind: Config
preferences: {}
users:
- name: system:kube-controller-manager
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lJSWN4Ynk4VWEvV1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TURBNE1qQXdNak13TURWYUZ3MHlNVEE0TWpBd01qTXdNRGRhTUNreApKekFsQmdOVkJBTVRIbk41YzNSbGJUcHJkV0psTFdOdmJuUnliMnhzWlhJdGJXRnVZV2RsY2pDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU5YK0Vqa3c0NDNTNzh5d05LL0dIQTV2eFZCS0Rhbi8KZ21yaUlFTW1hYWhlbDllREJXR0s0dVVtY1VXMXU1TCszeUR0amJlKy83MHZ2M3hvSWY1VkNZQXZqYUorN2twUQpyYW5RUE93cFJnbUlqNTEzV1FsZzMxWDlqREpuNlAybVpYTmZ6YWVOalBwOXdrZGkzZGVqSUZaSm1zYjQ0R3VwCkNrdlpodE5iYUlrVVU1U3dCT3h1dE92Um1uemdHQ3BQa0c4ME9pNWdYcDVzTHJ2dmVYSWxpem5wbHNsa3pxbjQKdWNJMHZMekhQY0JsSWhncEVJOXdCVTFOK3VWLzIxTmRaT3p1UlpFVFRMQ0xmNjhVR0FlM0ZCVXJHblJCUTJJZgpKLzhpNnJVQ2l1T25PQWUvOFNLbzlVM0ExOHN3RDJYandTZVo1NzRRclRGdkFjUjBYQ1BibW4wQ0F3RUFBYU1uCk1DVXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUNNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBcTY0cVBnVllzRzFGb05QQTRTNlJ0bGwrbUdTVUE2QlVNakQrWkt0eVM1NExCVFZnWQp5K1IrL0Zpd3o2RW1xWUpnZ0EyNWZGdkszSWlGNCt5d3JxeDZETlVZa3BBQkZFWXQ5VjU4a2gxV0pha3BvMEZQCnRZRkFaNmlEMlg4UlBZeUUwSXBMYlFqTGRncS9LYTRiSlhZRFhsS3RTV2UwbmJoY2FUWjRpRm5BcldndmpRQ0sKU05kV0tmSUpGNjJiWGE5a1BGc3ROYWVrWjdoQVZEZzhBbEd1c0tlYVFLdFNLZ2dMREFreElRWjlnNTZSVUprYwp6UUhRVHlibmVTcXJEN3cxT0xIR2RpYmZEYXhzMWdtbi9oL20xNk5ib3NMUlgxNkkxK3VKOWV1d29TWlp3Z29zCmpVRExuWVg1Zm1ZcEdhK2ZDbjdiMTJ4Mzg3SFpmbkE4eTFDTQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMWY0U09URGpqZEx2ekxBMHI4WWNEbS9GVUVvTnFmK0NhdUlnUXlacHFGNlgxNE1GCllZcmk1U1p4UmJXN2t2N2ZJTzJOdDc3L3ZTKy9mR2doL2xVSmdDK05vbjd1U2xDdHFkQTg3Q2xHQ1lpUG5YZFoKQ1dEZlZmMk1NbWZvL2FabGMxL05wNDJNK24zQ1IyTGQxNk1nVmttYXh2amdhNmtLUzltRzAxdG9pUlJUbExBRQo3RzYwNjlHYWZPQVlLaytRYnpRNkxtQmVubXd1dSs5NWNpV0xPZW1XeVdUT3FmaTV3alM4dk1jOXdHVWlHQ2tRCmozQUZUVTM2NVgvYlUxMWs3TzVGa1JOTXNJdC9yeFFZQjdjVUZTc2FkRUZEWWg4bi95THF0UUtLNDZjNEI3L3gKSXFqMVRjRFh5ekFQWmVQQko1bm52aEN0TVc4QnhIUmNJOXVhZlFJREFRQUJBb0lCQURCTHVrTXNGSDlpdHZwRQpYbSs1VDRXMmxocXJ5Kyt0R2ZzVGMrS1QzYzdCSXBYaUhTbkpsYkhQL2txVVhIUXRqNkEzM1A4MlhUT09maklPCnNuVmJMZHkvWHNEbzB0RDA2bXpqOFl2L09LNVlJc21RTVFrYjB1dnVZR0RUOE5LbVpra211eHh3cHZ1MXZFNHUKTXhGQzRMNTR1RFRsNElpTHl5WVpQd09lb3JZazlYVi9LSkN4a2g1RnVmZzBublI5MjNXQ1lDZVNyaUVWRm9LbQovbzBKYmlVNE1MU3FxallRWnljRnFSbGM0Vy9sMVJuMldLbU1KZ29EVUE4eEZiOEtJYjk4bGpOR0F0Z2QyNFQwCmcxS1VnbDRNazlPOTEvUzdrbHc3L3dsaHBkY3g0eFJ2dEtBTWZiM0RBa1V4MmpFZDB2ckZvU3NseHM0NXJOc2QKM296ZDhFRUNnWUVBM1p2OGJZTDE0ZlU3c0ZnVXlXekl4ejA3WlJ5czFzZitESmVXRmNCOEZoa2Jpb3Q1T0dqZwp0RHZmQlcvOXliMmtPM3RRNlJxNkFMOFpKcGE1QjcyOVF2YUJ1bDlpRHladVZndC8xUnY1d290Smo1SGZQS25vCnFVNzh6NVdtQUR2VitmQTVXaW9ad0hBVzQ3RHFLUU5OdzYyNWZaZFV3NTFXblZOWHpBZWR2VkVDZ1lFQTl6TjgKU3JrOXlsUlBaZnQ4emgrK05OZndoOFgzRWlZR2JwUHNpWG4zTitxYnQ4eXJORFhNYXRId1NrS2dxWDdxU0twQQpDc3ZGeXRreDhBc2VMaDZhQzBMbXh1aVVtQW8yMnBBU21veDY3VFo1ditqeXdtNGt3TFFXdjh6R0ptMjhyUlRVClZkejMvZC9pTkJHZDlKaHB0dzY3REUvcENPTm9vVWhOOHFwbVQyMENnWUJSYm9vNWE1QVNzZHgzRmthOUpWNDUKNkVRMUNXNXhsaGZDWk1sZndOVllBVzNmWVJUd0o0bTZjTzJvdjloUUU0R1A0ZVovWWJUTHBXMEdnd2dHMGpBRAp0VFZDV041ZGxzK2dpcVUwbUEwVThiM2NKY3dVTEpNejg3UnVTeDB1cE00aUE2WHZmZHpzbThPdGMwcjRPeUNPCk1QNGlLa09aaGUxWDdsSXF4UG12b1FLQmdFdk45UUp4RmJxeTZmb3JDWlduOUVyK0lSdHhvSmRuSTdmTEV0RUIKbnNiOTRheVdUYlhmL1lTUVJuQnZTQmRSL1FRMWVSZ1didHdLaUo3RXVnZUlpTktGUElHb2x0Q2M2VDlTeVBHdAp2SkI3a1JCQm5oZnpjTC9MT2VLdEorSm02bUhsTGt2NlMrNEZOcmVpNDE0N1VzZTQ4N0VOM0RkR2pUSlFHdDhjClUrMXRBb0dCQU1JVzFrcHhGZ1NOUjJORGczdHlJWGNhVDJiQStPTWZrc25nNVdrQUdqb0xveS9laE5waWtJTHAKbHFVVG5oZENaMHBvV3d2MUkxdkZ5VVRJTTREUHd1WVNicnZQNjV2UkJua1M5RGlldVE5Q0FEbXRkT0h1WWR2VgpzSy90cmQ5RTNTdUNVNWNSdXJqVkFacGJoOVNIQzU3bk9rVTRJY2EzT0EvbGZsSmRvbUl0Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
```

我们解析下certificate-authority-data这个内容看看是不是CA的证书

```
[root@VM-4-3-centos kubernetes]# echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="|base64 -d
-----BEGIN CERTIFICATE-----
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIwMDgyMDAyMzAwNVoXDTMwMDgxODAyMzAwNVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJwh
l8eWyJPlIjpj9S7OUIdRMeqWC0og+7xPzbPd8sKcSzfjZ7GsKmQyoB8hBseiUCuL
j/myYyNM62LHkFJl271sEYWf8KbY/5cmtRacFyL8Jri3KB/uxvgfQ/1xL+g7RdAq
FBYVG3mi+5OZ+LLrfULQNjzkhMYehHCtsCFfI0ayjbibMTPbKOyhn5wpuLg8/9We
SSJr5Nkg+dtXrRgNXzSisRLAQy2gDs7Ni3tJIZ64nDgHjJrKmGGgjlIc7TEtaTug
rym+/vjEYgcqNXGjF6zBeOQW5nMuHt+ZeazagT2A3dP8FcyDYVkHRUwDDH0Y9Ves
NPP2fxTG5UfXV9EtYRMCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADj6KauPGgoVyFBgMS1YbUEEqGBj
7r0hnorSn9ZxvU1fC5RFtPGt8B4aN4OtLkTDRz9fgEsVbtWh1tWYDHYAz7aCbEYk
LE0+G6P2JqGN7HykwNAdZuA/zzhNuQJfxcdnjTyHEfWg+y8GuKbjIMPtRU9N9lZh
FSyLlb3ozKXmDC+dnHxG1xMnzB3NSA+Xxi7d5GjA1ze3aqqes6meN53XZqdx167H
/3A6Wz66xPOg8ylPSUktymMG51d91lu1bebXPLmvg4+pAxWadbFgmLGFtPMTEw+Z
HDvs+q640H9biz+OWdewHcQq4MohCWnl8aW5IaeRamfY/sg/1wSW2FmxebA=
-----END CERTIFICATE-----
[root@VM-4-3-centos kubernetes]# cat pki/ca.crt 
-----BEGIN CERTIFICATE-----
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIwMDgyMDAyMzAwNVoXDTMwMDgxODAyMzAwNVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJwh
l8eWyJPlIjpj9S7OUIdRMeqWC0og+7xPzbPd8sKcSzfjZ7GsKmQyoB8hBseiUCuL
j/myYyNM62LHkFJl271sEYWf8KbY/5cmtRacFyL8Jri3KB/uxvgfQ/1xL+g7RdAq
FBYVG3mi+5OZ+LLrfULQNjzkhMYehHCtsCFfI0ayjbibMTPbKOyhn5wpuLg8/9We
SSJr5Nkg+dtXrRgNXzSisRLAQy2gDs7Ni3tJIZ64nDgHjJrKmGGgjlIc7TEtaTug
rym+/vjEYgcqNXGjF6zBeOQW5nMuHt+ZeazagT2A3dP8FcyDYVkHRUwDDH0Y9Ves
NPP2fxTG5UfXV9EtYRMCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADj6KauPGgoVyFBgMS1YbUEEqGBj
7r0hnorSn9ZxvU1fC5RFtPGt8B4aN4OtLkTDRz9fgEsVbtWh1tWYDHYAz7aCbEYk
LE0+G6P2JqGN7HykwNAdZuA/zzhNuQJfxcdnjTyHEfWg+y8GuKbjIMPtRU9N9lZh
FSyLlb3ozKXmDC+dnHxG1xMnzB3NSA+Xxi7d5GjA1ze3aqqes6meN53XZqdx167H
/3A6Wz66xPOg8ylPSUktymMG51d91lu1bebXPLmvg4+pAxWadbFgmLGFtPMTEw+Z
HDvs+q640H9biz+OWdewHcQq4MohCWnl8aW5IaeRamfY/sg/1wSW2FmxebA=
-----END CERTIFICATE-----
```

从解码可以发现，kubeconfig配置的就是kubernates的CA证书，client-certificate-data和client-key-data就是controller-manager用来访问apiserver的客户端证书和秘钥，只不过kubeconfig对内容进行了base64编码。这个就是整个controller-manager和apiserver证书认证的方式。

## kube-scheduler

kube-scheduler也是同样的原理，也是在yaml中配置一个kubeconfig来进行访问apiserver

```
[root@VM-4-3-centos kubernetes]# cat scheduler.conf 
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://10.0.4.3:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:kube-scheduler
  name: system:kube-scheduler@kubernetes
current-context: system:kube-scheduler@kubernetes
kind: Config
preferences: {}
users:
- name: system:kube-scheduler
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzakNDQWNhZ0F3SUJBZ0lJVlUybER1V2Y1OHd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TURBNE1qQXdNak13TURWYUZ3MHlNVEE0TWpBd01qTXdNRGhhTUNBeApIakFjQmdOVkJBTVRGWE41YzNSbGJUcHJkV0psTFhOamFHVmtkV3hsY2pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU14SzJrNmZnWG50cHVNM2JPZ2ZUS0V4aVhsdzdMQVc2VHpUK2thcndVS2UKK2hKSExWSjF4OUphazlDajZ2VWRZdEdkRzMyd0V1R0VFa3ltN0dFZXlyeHJneGRsU3NyVmRqQkFTYnhwNndpZApvZ3dmL2xVa2kza2FPVUozVXd6bmFnWCt6ZUh1d2hVN0R3NkNuaUpkMy9SZW9hU0FjZitvbDl0TTRiazRldVRrCnRXaUE5SDk0VnlQam42SUpkUDdNb1h4TWpZN1c1UysxNy9aczBwbXJabHhuWFdqZjZESXdyNnplbStSNlF1YnAKeE5adEk1WWdsNDk2a09BaTZMVW5xemhCNHIzaDdDOUd0SjFnVDk1YmxiQ0VZNzRtNmVLREZpNXFwZ3JRZnA0YwoxMlhRYzNtcGQzY2IrZXlGUFNsYUVDUmRwS1BKazRpZXgxNnN4TmwzRmk4Q0F3RUFBYU1uTUNVd0RnWURWUjBQCkFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUNNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUIKQVFBWFovVTcxSnRqQXQ3MjJLeVl6Q1RDZlF1bHdMM2EySGN6NGw5NXVaMFNWVG5ncTNhWUJxeVdwQ2puM3VNaApTaGN5OUZ4ZC92am52YXVTWUdXY05abm84dEVNUFhTaitNNzI5bW1vTUNUa0xCUGJSVGZwRGt3aDNnRS9IRWtuCnN0emRoZTZ3dWp4OWduMXl5WTJSOTFTZ3U3cjdwZjlLM1hOeFh2SFo3Z0tDQnJIVisyMVlQTkNCaC8rYlVuZkcKY2pvNlNNZHphT0Y5SlJod2pUS0l5VTlkeXJkbFBLUlR0Q3NGVEttdy9HM1d4Z1gvbGRCZnNsZmNaVXR4TlpsYQpablBVNlYvK3gwelBTVG56RzRmYTQ3UkhlZmc3YzczQkZjL0ZiYW9obmhrZHNPMVBNWGdhSjQ1bGo1NVNPL1phCmlIbUphZUF3bnh5d0hMazFtclE1b0ttVAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBekVyYVRwK0JlZTJtNHpkczZCOU1vVEdKZVhEc3NCYnBQTlA2UnF2QlFwNzZFa2N0ClVuWEgwbHFUMEtQcTlSMWkwWjBiZmJBUzRZUVNUS2JzWVI3S3ZHdURGMlZLeXRWMk1FQkp2R25yQ0oyaURCLysKVlNTTGVSbzVRbmRURE9kcUJmN040ZTdDRlRzUERvS2VJbDNmOUY2aHBJQngvNmlYMjB6aHVUaDY1T1MxYUlEMApmM2hYSStPZm9nbDAvc3loZkV5Tmp0YmxMN1h2OW16U21hdG1YR2RkYU4vb01qQ3ZyTjZiNUhwQzV1bkUxbTBqCmxpQ1hqM3FRNENMb3RTZXJPRUhpdmVIc0wwYTBuV0JQM2x1VnNJUmp2aWJwNG9NV0xtcW1DdEIrbmh6WFpkQnoKZWFsM2R4djU3SVU5S1ZvUUpGMmtvOG1UaUo3SFhxekUyWGNXTHdJREFRQUJBb0lCQVFDbmJhVlRFSGlkdy82bApjMVJIUFBlaG1DYXlKN0ZqYzdOOWpjRXRVREJZZUZBODBLYTlVUmdPTnZ1ejM5TjlSYk1xVlpjbFFEdUpKYU9WCnZLdzN3SE9wVG5lbW9mWlZHL0w4QW9RcjdhYVpiZzlUM3BpamtRclptbnRaRk5BMDRDZk5lQkdsMi9hbVRidSsKU2FCdVMvOXltR2ZqbVAxVTZRaGp5N09uQ0RuNEFsMmw1SXJpUCtlSTgxS3ZjVjRWUWNhcmtQL0F5SityQm56SgpSZjNRSFBRL2pFaUVRMm9kKzQ4N1FzSWNZVlcwZ0FmQ3pKN05NMUJMeTE0Yy8zUFJRS3JLZTdXT3AwM2Z3QmkxCk5TRlc1dmxSUkpnTDduSHQ4TkdsSUhHRE5VT29KR0UvVnppekJUVFpQRS9nOEkxZ1FLYko5b3ZSdXJQa0J6VGsKRHNGQ281bEJBb0dCQU5qalNTVXIybFo0Z1B6MEZ6bnBMTEFBaVhBSDkwZFlBb1BQSUhoWDR2WmtRUjE4Ykx2NgoraXVSR2dMTkQyckV4dHk3dE1tcTQrZkt2S1VXRWorOVR0KzNqWTVmTFdCeWhNTk1uaXN2eDFsdkxlZnFybkRvCnlkODdPb0p3TnZZdit2YitQR3NGaU51SHdXUTR4Wit3WTFaYitCUnB5UVJNUEs1TnVEbDRFMXZQQW9HQkFQRWkKRitwS1VJSmE0NGZuWDk3L1lHalh6Z1lWTEQ4RkRRMTh4dmY3TG43UEhUNzJoK1VCaXJFV29uK2RmcDFBZS95UwpTMER6Q2ZLUDdiM0R3YkxPbmRKcHdLWnUrUjdBaEs5RGFlVEJ3Y2FkRDFpTzNoME5RSVFoVFJPaVJRclN4RWpmCllXVnRmUXFuSUJhS3pMSnRCakVtakRDcXJ2QjJ3QTRza1Z5d09WZWhBb0dBTWZWb3k5OG1FL1QrQVVaWWMwWjYKdksvaStLTmRHbG56ZWxranFaVFUrdHh0QTFXOTFpOGhvUmR6WG1ITncxSkFYR2dBWk5Pd1c1d2ZpQWRsZkxrbQppZkhGOFoySzNrU0N3Rm5OdFRUMFBtMlZyVzRwY0dpdTEzVFZMV2Fid21tYTdYbnlnTlJ0aWVQamNDcURteDBPClJMNDZqcmt2VElZakZDTmk1Qm44bTVFQ2dZQmNHdUs1cW1Nd041bGJpd1J5d0dkS0JNeDhSRkFmVGtXYkZrTkYKNjVycDh5Qy9zUmxkWHdaaitEcGZ0bi9yZnZzZEVhQlBFY2FGOFhZbEd3WDh6N0UyOHhBVVFxVkRtdFBUd2xOTApmcnNPcTJWMk5UUWdNclNuQTdWV1A1QlJ2d29jcjc2YktJUXZzb0N1TzV4T3R4ZzdZL2IraStQQWxBdHVIcFh6CnFwaHNvUUtCZ1FERkxITzFwTTNPNlRWN3cybThKWVI0WGxBUWtLZkRPMlFGaDB0bGM1bk1rZUdZbHZFUUlZdVMKS2liV3NJNHVwMHFRcFZjdHF2VU9wc2V1Rk5ZdGVRQzF6YncxNWp4a0xEMm9Gb2c1Yk9WRXk3ekZERU1kVmdpRwpEbjhkbHN3SWp0bUF1SDFGOWdBbGR1V1M0cXkyV0I0SlRPZjBlTDVOM1dTWkRzcm91anA5NlE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
```

同理，解析certificate-authority-data也是kubernates的CA证书，client-certificate-data和client-key-data就是kube-scheduler用来访问apiserver的客户端证书和秘钥

## kube-proxy

在这里我们并未发现kube-proxy的kubeconfig，kube-proxy也是需要访问apiserver的，那么是如何进行认证的。还是从yaml文件进行分析一下，如果需要认证，肯定会在yaml中配置对应的证书或者包含证书的文件或token

```
[root@VM-4-3-centos run]# kubectl get  pod kube-proxy-6bf2t -n kube-system -o yaml
.....
  containers:
  - command:
    - /usr/local/bin/kube-proxy
    - --config=/var/lib/kube-proxy/config.conf
    - --hostname-override=$(NODE_NAME)
    env:
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: spec.nodeName
    image: k8s.gcr.io/kube-proxy:v1.15.2
    imagePullPolicy: IfNotPresent
    name: kube-proxy
    resources: {}
    securityContext:
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/lib/kube-proxy
      name: kube-proxy
    - mountPath: /run/xtables.lock
      name: xtables-lock
    - mountPath: /lib/modules
      name: lib-modules
      readOnly: true
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-proxy-token-rd92l
      readOnly: true
  dnsPolicy: ClusterFirst
  .....
  volumes:
  - configMap:
      defaultMode: 420
      name: kube-proxy
    name: kube-proxy
  - hostPath:
      path: /run/xtables.lock
      type: FileOrCreate
    name: xtables-lock
  - hostPath:
      path: /lib/modules
      type: ""
    name: lib-modules
  - name: kube-proxy-token-rd92l
    secret:
      defaultMode: 420
      secretName: kube-proxy-token-rd92l
kube-proxy挂载一个secret，这边猜测这个就是用来进行认证的。我们查看下这个token
[root@VM-4-3-centos run]# kubectl get secret -n kube-system kube-proxy-token-rd92l -o yaml
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  namespace: a3ViZS1zeXN0ZW0=
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpSjkuZXlKcGMzTWlPaUpyZFdKbGNtNWxkR1Z6TDNObGNuWnBZMlZoWTJOdmRXNTBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5dVlXMWxjM0JoWTJVaU9pSnJkV0psTFhONWMzUmxiU0lzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVmpjbVYwTG01aGJXVWlPaUpyZFdKbExYQnliM2g1TFhSdmEyVnVMWEprT1RKc0lpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltdDFZbVV0Y0hKdmVIa2lMQ0pyZFdKbGNtNWxkR1Z6TG1sdkwzTmxjblpwWTJWaFkyTnZkVzUwTDNObGNuWnBZMlV0WVdOamIzVnVkQzUxYVdRaU9pSmhOemRrTjJKaE1TMW1Zek5pTFRRM1lUTXRZV00wWkMweVpXRmtPVEV6WkRVd09ESWlMQ0p6ZFdJaU9pSnplWE4wWlcwNmMyVnlkbWxqWldGalkyOTFiblE2YTNWaVpTMXplWE4wWlcwNmEzVmlaUzF3Y205NGVTSjkuSTRuR0UxOVhJakFPU0lKcWZyb3A2azhHcXBickxBeVFzQ3NoeFhxMEc3RklTZmJudS1TTW9xV1pHUjU0S2hwREdlaGd6WkQwMGVGZG14bEM1ZzBIc2ZzZE40V0tmVFI1ZjY1b3kzTnVvWUxxcUIzUzgySUxLelJHREVBNHpwWmFXeG1lRmtzdU1mdl9UWDRjSGdtYUI3V0ZQZzJ5RWtxV0VPa3kwT0hOWnIxNmd4Mzl3S1owWDRhQ29FOVd0cGlZU1BKYU5SdmtVbENfNTlPZHJTYnBCYnlkd2JOaWVaRjdhcWRBbFdWQ3JXQkRfWmlCaHNnZklVYUpEcVg5TWtRbUpjVS1Yb2pzWUpXNFpNejZ3OEZFTHY4THpCazRLTUc5V185aG5Jc3FfVlFUM2xDek5iSHlNSktWeXZ1VlVrblo5X3AwaTJGQlpDeGVVdlpVazdrd01R
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: kube-proxy
    kubernetes.io/service-account.uid: a77d7ba1-fc3b-47a3-ac4d-2ead913d5082
  creationTimestamp: "2020-08-20T02:30:48Z"
  name: kube-proxy-token-rd92l
  namespace: kube-system
  resourceVersion: "196"
  selfLink: /api/v1/namespaces/kube-system/secrets/kube-proxy-token-rd92l
  uid: c9ff07a0-4176-4053-a93c-11c7d0aff285
type: kubernetes.io/service-account-token
```

从上面token的内容，这个里面包含一个CA证书是kubernates查到ca证书，token就是用来和apiserver通讯的客户端证书和秘钥

## kubelet

kubelet和其他组件类似，用的kubeconfig来进行认证的，都是用kubernates的CA生成。

```
[root@VM-4-3-centos kubernetes]# cat kubelet.conf 
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://10.0.4.3:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:node:vm-4-3-centos
  name: system:node:vm-4-3-centos@kubernetes
current-context: system:node:vm-4-3-centos@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:vm-4-3-centos
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrVENDQWVHZ0F3SUJBZ0lJUW92eFg0d2RyTmd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TURBNE1qQXdNak13TURWYUZ3MHlNVEE0TWpBd01qTXdNRGRhTURzeApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFaU1DQUdBMVVFQXhNWmMzbHpkR1Z0T201dlpHVTZkbTB0Ck5DMHpMV05sYm5SdmN6Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1scld4Y3kKOEhyT0FPeXNGcjJxUXhOdGZ4QllBMWJYZ1BkRy95eW9rWjVLdm1nc0NjbGh2MWkyaWF2bzF6T1d4QkNZSHlJZQpyK0hSdDQ5TFFzcjZIU2k3QlZveEtuWm8rekpjSFBIVHk0VUtESVYrOUxUU29INDA3akhXeGFOWXc0RWw2QVB0Ck92UzcvcXVBb3BBOFNPVVU2YTdWcFc2OHRvNVJtRGNwcGVMZnlmL3dXdXRpZFZ0dTRmTE5RK3gwRnBGRWZXMisKY3ZHNWd2ZlZnaldaUnhzbXoyWlFESEo0NStSWldmVnF6bHdjYVlkMytROTFiK1BmUHhlVU81bmdRMGtpbTVTbAp6K1c4Q1p6Q01UOUF6QVU2USt1L1oyKzlsaU9xbzdxWnEySHZMMms5R05GNjVHcVVnWWpDT3hVRFpoanZ5RkxQCjNvdHR0ZTJQeHZkMXJRa0NBd0VBQWFNbk1DVXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0cKQ0NzR0FRVUZCd01DTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBcHpFNkxJU25jZFE1di9yZ2ZJbHRQV2l3Lwp4WjFQdHk2WWZTYmw2UXlNY2g1Qk1RNDhyL3o1ZXV5V21kVXhVUmFHaUlldUVaODAxQ2xteitoRjQ2RFRyZGlaCmZmTUNLcXE5Z3lKbkRBT1VYMkpkY3RZb2IvNWp0T0t0ejVYYmRrMzI5ZG01cUVEQ1gzRm44M3NWcWJPZnRXUnQKM2NvdFdUYjM4cmljQm5sRTRwZjBUdXk5RVZIZFBFazgyOUh0UVdDc3JDVFNQZXZ0S29oM01mdUZ0ek9leXJsMQpDSXFjNXVDM0k1dU1xS0tBeFNvSk1Pd1pVUklrRkpTQ09lRlRRVnd1ZE9vVi92STcrR0NBcVBObS9IUVRlNUROCkdPUXVWNTRMYWFkRzVRSHdDOEJhWDZTc0JQb09KOG5vWlM4cllSSERjMnFBcUYzMkhReHlXVHc1aE53dgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBeVd0YkZ6THdlczRBN0t3V3ZhcERFMjEvRUZnRFZ0ZUE5MGIvTEtpUm5rcSthQ3dKCnlXRy9XTGFKcStqWE01YkVFSmdmSWg2djRkRzNqMHRDeXZvZEtMc0ZXakVxZG1qN01sd2M4ZFBMaFFvTWhYNzAKdE5LZ2ZqVHVNZGJGbzFqRGdTWG9BKzA2OUx2K3E0Q2lrRHhJNVJUcHJ0V2xicnkyamxHWU55bWw0dC9KLy9CYQo2MkoxVzI3aDhzMUQ3SFFXa1VSOWJiNXk4Ym1DOTlXQ05abEhHeWJQWmxBTWNuam41RmxaOVdyT1hCeHBoM2Y1CkQzVnY0OTgvRjVRN21lQkRTU0tibEtYUDVid0puTUl4UDBETUJUcEQ2NzluYjcyV0k2cWp1cG1yWWU4dmFUMFkKMFhya2FwU0JpTUk3RlFObUdPL0lVcy9laTIyMTdZL0c5M1d0Q1FJREFRQUJBb0lCQUIwTXFycVIwalVqK09ZcAplNjRuSEQxMUVWcGVGejB6SDVxS1Zzc3VGTEpydlVKdzk0aGYzS1VDenFCSW1LRU1JWUx6TGFwU0dyUEs5MXBuClZGN0o2K0t2OW5tbmxhUTJSK1JmZkowMEdxbzVaTXpzSG9ibHlkZnA4bUNseFNObDdleDJkeHY1M3dMbENqbloKOTVndDJhV1FlcE9JcEs5djhEUmVlRUdjZEJ4Z1FMRm5nTU5SR3NzQ0lOSUt6Wkc5aDlwd2htZTRGUW9sRUlNMwp1ZkxkbzVZM0UyeFk3bkV4TTYvTnEvNUVQL1k2S1NGQlJwaXBxeWF2MERIaEVoaU9HNGtCS1l2ajZwdWdXcDgrCkNvcFZ0NWtNWjlwSWx3UElnT1UwQzB3L3Y5K0M2S0hIbzZUZExlTE9GeDlKMER4bXluMWJRMENvTGFTUXRiUUkKOG12UHFuRUNnWUVBOG92a0l1YjBUSHZlQVlSUG1hT1RMVFg4QkZiWWMwUEdhNDljMlNsaU95QmV1M0dxSEwzQgpWT3JKRXUwbVdQQU5pMDIrR0lpZG9MejFUTC82dlRmTkR6ajZRTWFRNUJsZVVKdUZlOGxuMEliUG5jbjZzN0MzCkp5eGZRMzA1emFONFJjWXQyQWZrZkg1bnZpK3d1YUpXbzAySUQzOXczdU1BMkNFZFdGbVdBMzhDZ1lFQTFKZDMKOUk0dktLZFFhNytKZkFLWG9mYWd2cUtWNnRHSkVPWjE2NXJNS1FTUmJvcDM3UXhmQ2dLRXpKMEI0U1p5eGFZYgpLdWJTZ1hjQXVrbER6TVpqc2ZsL0VEMmhDcFFQSGhnVUNNTGVQUkl3SDJWM2gvZk9nMlU3SEtFdHMvSXNsdlVRCk1JWDJpTnQ5MnUyU3AvUzBsMWowTitndjZ3M0R6S2xHS2dUWWMzY0NnWUVBcTJ3aGxtVmkvbmVCUmRNc3F5ckkKQjJrak1ESHRFekl3bDY2Z2NiOWs5T01BOFR2NWZnekRDbkJTSXJWSHFBNHBsRzRpejVZbXlnY2kyOWJIc1ZveAo3UE5aTTlUamJNTmRQRjFlcjBsK3ZRdTZ5d3VJeTkwMjVWSGdGb1A0Q1pYaW1IWGp5czV4TjJmampMQ0tGL2xiCmdGbDRzM05mNDdmT3pmSkJta0xlMnFNQ2dZRUFsdEtMRU41YXlLM0RHVjQrek5NTi9xTDVNYVlwVS9tcUUycGQKR0hTdkNSNnJpdEFEK3hIK3p4d3dXUFcrNHB3amF1UElmR3hieGV2R2dXTC9EZVZsejFzaGNVVTMza2hpWFVoWgoxa2xoMzlQcWZpdS9YS0JMUzk3aXpCSHhXYXVqUk1uQjNacjg1K1ZJYWF5SWtrM0NYV21IZ2E1aGFKSlFhZjloCnZ1ZkhKRXNDZ1lFQW9wSnQzSGdzS3R6VSs2YkMzdEJwQkEweXFZQ09yTkFQRjV5SWJzZnZXRmM1WENpc25xZE4KUG9hNzBJTVJwWFhCREo1V01lVWhiVVQzbHRtNWppV3dkVGp1WTJSYWorNVhZeHplN1NpWW9Dak9xckdua0NoLwppUG1pb21wNGViUU9DS3A3WTN5cG0rdy9RcENSN2ZBUEI1NkZVWnRZbktEbEs3RHlsWjFIQ0s0PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
```

这边我们会给每个节点生成一份客户端的证书和私钥，master上用的是kubelet.conf ，节点上的kubelet.conf如下，直接指向一个kubelet-client-current.pem文件，这里包含了证书和私钥，每一个节点都不一样。因此每个节点都会有一个自己的客户端证书和私钥。

```
[root@VM-4-9-centos ~]# cat /etc/kubernetes/kubelet.conf
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://10.0.4.3:6443
  name: default-cluster
contexts:
- context:
    cluster: default-cluster
    namespace: default
    user: default-auth
  name: default-context
current-context: default-context
kind: Config
preferences: {}
users:
- name: default-auth
  user:
    client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
    client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
[root@VM-4-9-centos pki]# cat /var/lib/kubelet/pki/kubelet-client-current.pem
-----BEGIN CERTIFICATE-----
MIICZzCCAU+gAwIBAgIUPrHB6WlowbhzImI5+NnT0Y4ZzlAwDQYJKoZIhvcNAQEL
BQAwFTETMBEGA1UEAxMKa3ViZXJuZXRlczAeFw0yMDA4MjAwMjI4MDBaFw0yMTA4
MjAwMjI4MDBaMDsxFTATBgNVBAoTDHN5c3RlbTpub2RlczEiMCAGA1UEAxMZc3lz
dGVtOm5vZGU6dm0tNC05LWNlbnRvczBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BJ1Qb3DwFRUjIYaaBxNGTieXObGKdGLG8/HVdwXNkVSIWLGBkz9QsFaOh1IsiQ6g
5FfxRBneWhyQTOgMmD0yvymjVDBSMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAK
BggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBR2QsIZ/qWdhOExDObO
wiBjcpbUMTANBgkqhkiG9w0BAQsFAAOCAQEATF/xpZD9kcCMFqFDlbo1Zn4DwXh6
X3s5T6r3QNtZQ1SeUHUhnL2Q1DrpICAEFxoqMdB75hxlYCs5UOP6YwBUX77qAVs9
QAXW7/sEhS5firGGP8pEQXgaUWwv6tu2V574JL7M9p+koHL/Fbev9fad8I71XIDQ
qkmnf892VCYnkvw1s7wNJENlxNQUQ1rw0wEccyKlYpxbqXCYStSloSaz6JCFnT06
+EXV5cr/G8UZnYRoMNu6jiajIxhFmYQqBNCqOlJo24TVjeLlNTL5AD8aSXcQ+O16
PWhBYNdEOulokdjg84gAg6jSqN2g+hi4+gHMG1Rw2h+9iu5E5txFjKGiMQ==
-----END CERTIFICATE-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIN75eP2QG76VLv/nRWiLzW9Fg9hCzeb33BrZ5n9PhwhToAoGCCqGSM49
AwEHoUQDQgAEnVBvcPAVFSMhhpoHE0ZOJ5c5sYp0Ysbz8dV3Bc2RVIhYsYGTP1Cw
Vo6HUiyJDqDkV/FEGd5aHJBM6AyYPTK/KQ==
-----END EC PRIVATE KEY-----
```

现在有一个问题就是，k8s中节点可能有几万个，那么是如何快速给节点自动生成客户端证书和秘钥然后配置给kubelet呢？

```
[root@VM-4-11-centos pki]# ps -ef | grep kubelet
root     14746     1  0 Aug20 ?        17:50:19 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.1
```

查看kubelet进程可以发现还有一个kubeconfig是bootstrap-kubelet.conf，我们来看看这个文件配置的是什么？

```
[root@VM-4-11-centos pki]# cat /etc/kubernetes/bootstrap-kubelet.conf
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EZ3lNREF5TXpBd05Wb1hEVE13TURneE9EQXlNekF3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSndoCmw4ZVd5SlBsSWpwajlTN09VSWRSTWVxV0Mwb2crN3hQemJQZDhzS2NTemZqWjdHc0ttUXlvQjhoQnNlaVVDdUwKai9teVl5Tk02MkxIa0ZKbDI3MXNFWVdmOEtiWS81Y210UmFjRnlMOEpyaTNLQi91eHZnZlEvMXhMK2c3UmRBcQpGQllWRzNtaSs1T1orTExyZlVMUU5qemtoTVllaEhDdHNDRmZJMGF5amJpYk1UUGJLT3lobjV3cHVMZzgvOVdlClNTSnI1TmtnK2R0WHJSZ05YelNpc1JMQVF5MmdEczdOaTN0SklaNjRuRGdIakpyS21HR2dqbEljN1RFdGFUdWcKcnltKy92akVZZ2NxTlhHakY2ekJlT1FXNW5NdUh0K1plYXphZ1QyQTNkUDhGY3lEWVZrSFJVd0RESDBZOVZlcwpOUFAyZnhURzVVZlhWOUV0WVJNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEajZLYXVQR2dvVnlGQmdNUzFZYlVFRXFHQmoKN3IwaG5vclNuOVp4dlUxZkM1UkZ0UEd0OEI0YU40T3RMa1REUno5ZmdFc1ZidFdoMXRXWURIWUF6N2FDYkVZawpMRTArRzZQMkpxR043SHlrd05BZFp1QS96emhOdVFKZnhjZG5qVHlIRWZXZyt5OEd1S2JqSU1QdFJVOU45bFpoCkZTeUxsYjNvektYbURDK2RuSHhHMXhNbnpCM05TQStYeGk3ZDVHakExemUzYXFxZXM2bWVONTNYWnFkeDE2N0gKLzNBNld6NjZ4UE9nOHlsUFNVa3R5bU1HNTFkOTFsdTFiZWJYUExtdmc0K3BBeFdhZGJGZ21MR0Z0UE1URXcrWgpIRHZzK3E2NDBIOWJpeitPV2Rld0hjUXE0TW9oQ1dubDhhVzVJYWVSYW1mWS9zZy8xd1NXMkZteGViQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    server: https://10.0.4.3:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: tls-bootstrap-token-user
  name: tls-bootstrap-token-user@kubernetes
current-context: tls-bootstrap-token-user@kubernetes
kind: Config
preferences: {}
users:
- name: tls-bootstrap-token-user
  user:
    token: qsciu5.l8b4382ue1m8e61x
```

Kubernetes 提供了 TLS bootstrapping 的方式来简化 Kubelet 证书的生成过程。其原理是预先提供一个 bootstrapping token，kubelet 通过该 kubelet 调用 kube-apiserver 的证书签发 API 来生成 自己需要的证书。要启用该功能，需要在 kube-apiserver 中启用 --enable-bootstrap-token-auth ，并创建一个 kubelet 访问 kube-apiserver 使用的 bootstrap token secret。如果使用 kubeadmin 安装，可以使用 kubeadm token create命令来创建 token。

采用TLS bootstrapping 生成证书的流程如下： 1. 调用 kube-apiserver 生成一个 bootstrap token。 将该 bootstrap token 写入到一个 kubeconfig 文件中，作为 kubelet 调用 kube-apiserver 的客户端验证方式。 2. 通过 --bootstrap-kubeconfig 启动参数将 bootstrap token 传递给 kubelet 进程。 3. Kubelet 采用 bootstrap token 调用 kube-apiserver API，生成自己所需的服务器和客户端证书。 4. 证书生成后，Kubelet 采用生成的证书和 kube-apiserver 进行通信，并删除本地的 kubeconfig 文件，以避免 bootstrap token 泄漏风险。

## Service Account 认证

前面我们有说在kube-apiserver 和 kube-controller-manager分配配置的sa的公钥和私钥，这里就是用来对service account来进行认证的，我们一般在RBAC中来限制service account的访问限制。kubernetes 会为该 service account 生成一个 JWT token，并使用 secret 将该 service account token 加载到 pod 上。pod 中的应用可以使用 service account token 来访问 api server。service account 证书被用于生成和验证 service account token。该证书的用法和前面介绍的其他证书不同，因为实际上使用的是其公钥和私钥，而并不需要对证书进行验证。下面是service account的认证方式

![upload-image](/files/-MN2J9UMHNXzlnXZUBTc)

这里参考大佬的文章根据集群的实际部署对k8s的证书进行了梳理，加深对这方面的知识了解，有什么问题请大家多多指正。

## 参考链接

<https://mp.weixin.qq.com/s/IbxRTQBOeKtL8AlC3R65Rw>

<https://kubernetes.io/zh/docs/setup/best-practices/certificates/>


# 如何阅读k8s源码

本篇文章主要介绍了如何在windows下通过goland阅读k8s源码。

## 下载及idea加载GO代码

这边大家可以百度一下，搜一下go的下载及破解版goland的下载，网上教程很多，按照顺序按照下载即可。我这里的版本如下

go: 1.15 goland: 2020.1

```
C:\Users\nieweixing>go version
go version go1.15 windows/amd64
```

![upload-image](/files/-MN2J7cgzHj7D9T76cJS)

### 从github上克隆源码到本地

首先创建一个gopath目录,我们这里用的是

```
D:\go-code
```

然后在go-code目录下创建一个src/k8s.io,最后我们存放代码的目录为

```
D:\go-code\src\k8s.io
```

我们切换到k8s.io目录，执行如下命令clone代码，注意需要加上core.symlinks=true这个参数，源码中有很多软连接，不加这个参数会找不到依赖。

```
# 克隆最新代码
git clone -c core.symlinks=true https://github.com/kubernetes/kubernetes

# 克隆某个分支代码
git clone -c core.symlinks=true -b xxxx  https://github.com/kubernetes/kubernetes
```

### goland查看源码并分析目录结构

我们打开goland后配置好$GOPATH,然后查看下对应源码即可。

![upload-image](/files/-MN2J7ciQEbXOfiWJF77)

下面我们简单介绍下k8s源码目录的作用

```
|--api  // 存放api规范相关的文档
|------api-rules  //已经存在的违反Api规范的api
|------openapi-spec  //OpenApi规范
|--build // 构建和测试脚本
|------run.sh  //在容器中运行该脚本，后面可接多个命令：make, make cross 等
|------copy-output.sh  //把容器中_output/dockerized/bin目录下的文件拷贝到本地目录
|------make-clean.sh  //清理容器中和本地的_output目录
|------shell.sh  // 容器中启动一个shell终端
|------......
|--cluster  // 自动创建和配置kubernetes集群的脚本，包括networking, DNS, nodes等
|--cmd  // 内部包含各个组件的入口，具体核心的实现部分在pkg目录下
|--hack  // 编译、构建及校验的工具类
|--logo // kubernetes的logo
|--pkg // 主要代码存放类，后面会详细补充该目录下内容
|------kubeapiserver
|------kubectl
|------kubelet
|------proxy
|------registry
|------scheduler
|------security
|------watch
|------......
|--plugin
|------pkg/admission  //认证
|------pkg/auth  //鉴权
|--staging  // 这里的代码都存放在独立的repo中，以引用包的方式添加到项目中
|------k8s.io/api
|------k8s.io/apiextensions-apiserver
|------k8s.io/apimachinery
|------k8s.io/apiserver
|------k8s.io/client-go
|------......
|--test  //测试代码
|--third_party  //第三方代码，protobuf、golang-reflect等
|--translations  //不同国家的语言包，使用poedit查看及编辑
```

## Kubernetes源码编译

我们知道k8s的运行访问有2种方式，一种是二进制，还有一种就是docker镜像的方式，下面我们来讲一下如何将源码编译成二进制和镜像的方式。

### 二进制编译kubernetes

#### 编译全部组件

这里我们以在centos7上作为示例

```
# yum install go -y
# mkdir -p /root/go/src/k8s.io
# git clone https://github.com/kubernetes/kubernetes
# cd /root/go/src/k8s.io/kubernetes
# KUBE_BUILD_PLATFORMS=linux/amd64 make
```

在这里我们编译apiserver的时候会出现一个错误，提示我们app\server.go:477:70: undefined: "k8s.io/kubernetes/pkg/generated/openapi".GetOpenAPIDefinitions。

在pkg/generated/openapi确实没有这个函数。这个函数是在zz\_generated.openapi.go，我们需要在/root/go/src/k8s.io/kubernetes下执行

```
# cd /root/go/src/k8s.io/kubernetes
# make generated_files
# ls -l pkg/generated/openapi
total 2384
-rw-r--r-- 1 root root    1216 Nov 23 11:23 BUILD
-rw-r--r-- 1 root root     621 Nov 23 11:23 doc.go
-rw-r--r-- 1 root root    1533 Nov 23 11:23 openapi_test.go
-rw-r--r-- 1 root root     131 Nov 23 11:23 OWNERS
-rw-r--r-- 1 root root 2423572 Nov 23 12:41 zz_generated.openapi.go
```

这里我们发现已经zz\_generated.openapi.go生成在pkg/generated/openapi目录下，如果你在windows也有代码，那么你可以把这个文件拷贝到对应目录下。

现在我们再执行编译命令，然后编译好的二进制文件在/root/go/src/k8s.io/kubernetes/\_output/bin这个目录下

```
[root@VM-0-13-centos kubernetes]# KUBE_BUILD_PLATFORMS=linux/amd64 make
+++ [1123 13:45:25] Building go targets for linux/amd64:
    cmd/kube-proxy
    cmd/kube-apiserver
    cmd/kube-controller-manager
    cmd/kubelet
    cmd/kubeadm
    cmd/kube-scheduler
    vendor/k8s.io/kube-aggregator
    vendor/k8s.io/apiextensions-apiserver
    cluster/gce/gci/mounter
    cmd/kubectl
    cmd/gendocs
    cmd/genkubedocs
    cmd/genman
    cmd/genyaml
    cmd/genswaggertypedocs
    cmd/linkcheck
    vendor/github.com/onsi/ginkgo/ginkgo
    test/e2e/e2e.test
    cluster/images/conformance/go-runner
    cmd/kubemark
    vendor/github.com/onsi/ginkgo/ginkgo
    test/e2e_node/e2e_node.test
[root@VM-0-13-centos kubernetes]# ls _output/bin
apiextensions-apiserver  e2e_node.test  genman              go2make     kube-aggregator          kubelet         linkcheck
conversion-gen           e2e.test       genswaggertypedocs  go-bindata  kube-apiserver           kubemark        mounter
deepcopy-gen             gendocs        genyaml             go-runner   kube-controller-manager  kube-proxy      openapi-gen
defaulter-gen            genkubedocs    ginkgo              kubeadm     kubectl                  kube-scheduler  prerelease-lifecycle-gen
```

#### 编译单个组件

我们可以只编译某一个组件，下面我们来演示如何只编译kubelet的二进制文件

在linux和window下都是同样的方式，只是编译的平台不一样，生成的二进制文件不一样，window下生产kubelet.exe,linux下生产kubelet这个二进制执行文件

```
# cd $GOPATH/src/k8s.io/kubernetes/cmd/kubelet
# go build -v
```

执行编译完成后，在kubelet目录下会存在可执行的kubelet二进制文件

![upload-image](/files/-MN2J7ckWTZiIkZgYMJG)

![upload-image](/files/-MN2J7clKgld-PBtSFn_)

### docker镜像编译kubernetes

我们这里下载的是最新的源码，如果拉取不到国外镜像，可以执行下面命令拉取镜像然后上传到本地机器

```
docker pull mirrorgooglecontainers/pause-amd64:3.1
docker pull zhuyuhua/kube-cross:v1.15.2-1
docker pull mirrorgooglecontainers/debian-base-amd64:0.4.0
docker pull mirrorgooglecontainers/debian-iptables-amd64:v11.0
docker pull mirrorgooglecontainers/debian-hyperkube-base-amd64:0.12.0
docker pull litmuschaos/go-runner:latest

docker tag mirrorgooglecontainers/pause-amd64:3.1 k8s.gcr.io/build-image/pause-amd64:3.1
docker tag zhuyuhua/kube-cross:v1.15.2-1 k8s.gcr.io/build-image/kube-cross:v1.15.2-1
docker tag mirrorgooglecontainers/debian-base-amd64:0.4.0 k8s.gcr.io/build-image/debian-base-amd64:0.4.0
docker tag mirrorgooglecontainers/debian-iptables-amd64:v11.0 k8s.gcr.io/build-image/debian-iptables:buster-v1.3.0 
docker tag mirrorgooglecontainers/debian-hyperkube-base-amd64:0.12.0 k8s.gcr.io/build-image/debian-hyperkube-base-amd64:0.12.0
docker tag litmuschaos/go-runner:latest k8s.gcr.io/build-image/go-runner:buster-v2.0.1
```

为了防止从远程拉取镜像，我们可以修改下$GOPATH\src\k8s.io\kubernetes\build\lib\release.sh的配置

```
"${DOCKER[@]}" build ${docker_build_opts:+"${docker_build_opts}"} -q -t "${docker_image_tag}" "${docker_build_path}" >/dev/null

改成如下

"${DOCKER[@]}" build -q -t "${docker_image_tag}" "${docker_build_path}" >/dev/null
```

然后执行如下命令进行打包

```
[root@VM-0-3-centos kubernetes]# KUBE_GIT_VERSION=v1.19.0-alpha.0 KUBE_FASTBUILD=true KUBE_BUILD_PULL_LATEST_IMAGES=n make release-images
+++ [1123 17:20:07] Verifying Prerequisites....
+++ [1123 17:20:07] Building Docker image kube-build:build-a3ee5ddec9-5-v1.15.2-1
+++ [1123 17:20:10] Syncing sources to container
+++ [1123 17:20:13] Running build command...
+++ [1123 17:21:01] Building go targets for linux/amd64:
    cmd/kube-apiserver
    cmd/kube-controller-manager
    cmd/kube-scheduler
    cmd/kube-proxy
    vendor/github.com/onsi/ginkgo/ginkgo
    test/e2e/e2e.test
    cluster/images/conformance/go-runner
    cmd/kubectl
+++ [1123 17:21:42] Syncing out of container
+++ [1123 17:21:51] Building images: linux-amd64
+++ [1123 17:21:51] Starting docker build for image: kube-apiserver-amd64
+++ [1123 17:21:51] Starting docker build for image: kube-controller-manager-amd64
+++ [1123 17:21:51] Starting docker build for image: kube-scheduler-amd64
+++ [1123 17:21:51] Starting docker build for image: kube-proxy-amd64
+++ [1123 17:21:51] Building conformance image for arch: amd64
+++ [1123 17:22:26] Deleting docker image k8s.gcr.io/kube-proxy-amd64:v1.19.0-alpha.0
+++ [1123 17:22:30] Deleting docker image k8s.gcr.io/kube-scheduler-amd64:v1.19.0-alpha.0
+++ [1123 17:22:37] Deleting docker image k8s.gcr.io/kube-apiserver-amd64:v1.19.0-alpha.0
+++ [1123 17:22:37] Deleting docker image k8s.gcr.io/kube-controller-manager-amd64:v1.19.0-alpha.0
+++ [1123 17:22:51] Deleting conformance image k8s.gcr.io/conformance-amd64:v1.19.0-alpha.0
+++ [1123 17:22:51] Docker builds done
```

编译完成后,可以到对应的目录下查找镜像，并通过docker load < *\** 来加载镜像,然后通过修改组件的yaml的镜像名称即可。

```
[root@VM-0-3-centos amd64]# ll
total 2101008
-rw-r--r-- 1 root root 298798080 Nov 23 17:22 conformance-amd64.tar
-rw------- 2 root root 619204608 Nov 23 17:22 kube-apiserver.tar
-rw------- 2 root root 612844544 Nov 23 17:22 kube-controller-manager.tar
-rw------- 2 root root 103392256 Nov 23 17:22 kube-proxy.tar
-rw------- 2 root root 517171712 Nov 23 17:22 kube-scheduler.tar
[root@VM-0-3-centos amd64]# pwd
/root/go/src/k8s.io/kubernetes/_output/release-images/amd64
[root@VM-0-3-centos amd64]# docker load < kube-apiserver.tar
5eebc1b5676b: Loading layer [==================================================>]  163.4MB/163.4MB
Loaded image: k8s.gcr.io/kube-apiserver-amd64:v1.19.0-alpha.0
[root@VM-0-3-centos amd64]# docker images|grep apis
k8s.gcr.io/kube-apiserver-amd64                            v1.19.0-alpha.0                c1ac6c895896        20 minutes ago      613MB
```


# k8s之securityContext

securityContext是什么呢，有什么作用呢，其实这个就是用来控制容器内的用户权限，你想用什么用户去执行程序或者执行操作等等。

## securityContext介绍

安全上下文（Security Context）定义 Pod 或 Container 的特权与访问控制设置。 安全上下文包括但不限于：

* 自主访问控制（Discretionary Access Control）：基于 用户 ID（UID）和组 ID（GID）. 来判定对对象（例如文件）的访问权限。
* 安全性增强的 Linux（SELinux）： 为对象赋予安全性标签。
* 以特权模式或者非特权模式运行。
* Linux 权能: 为进程赋予 root 用户的部分特权而非全部特权。
* AppArmor：使用程序框架来限制个别程序的权能。
* Seccomp：过滤进程的系统调用。
* AllowPrivilegeEscalation：控制进程是否可以获得超出其父进程的特权。 此布尔值直接控制是否为容器进程设置 no\_new\_privs标志。 当容器以特权模式运行或者具有 CAP\_SYS\_ADMIN 权能时，AllowPrivilegeEscalation 总是为 true。
* readOnlyRootFilesystem：以只读方式加载容器的根文件系统。

## 如何为 Pod 设置安全性上下文

要为 Pod 设置安全性设置，可在 Pod 规约中包含 securityContext 字段。securityContext 字段值是一个 PodSecurityContext 对象。你为 Pod 所设置的安全性配置会应用到 Pod 中所有 Container 上。 下面是一个 Pod 的配置文件，该 Pod 定义了 securityContext 和一个 emptyDir 卷

```
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false
```

下面我们来解析下securityContext中每个配置的含义然后到容器内进行验证下是否有生效：

* runAsUser: 1000字段指定 Pod 中的所有容器内的进程都使用用户 ID 1000 来运行。

进到容器中，我们发现执行任何命名也就是进程的用户都是1000

![upload-image](/files/-MN2J8aq3aEccLEdzmFe)

* runAsGroup: 3000字段指定所有容器中的进程都以主组 ID 3000 来运行。 如果忽略此字段，则容器的主组 ID 将是 root（0）。 当runAsGroup被设置时，所有创建的文件也会划归用户 1000 和组 3000。&#x20;

![upload-image](/files/-MN2J8asjawQLSjxbM-g)

* fsGroup: 2000被设置，容器中所有进程也会是附组 ID 2000 的一部分。 卷/data/demo及在该卷中创建的任何文件的属主都会是组 ID 2000。

![upload-image](/files/-MN2J8atrDIP9ZwZzyFG)

## 为 Container 设置安全性上下文

我们可以在pod层面和container层面设置上下文，但是如果2个同时配置了，那么哪个会生效呢？下面我们来测试一下

```
[root@VM-4-3-centos ~]# cat security-context-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-2
spec:
  securityContext:
    runAsUser: 1000
  containers:
  - name: sec-ctx-demo-2
    image: empiregeneral/node-hello:1.0
    securityContext:
      runAsUser: 2000
      allowPrivilegeEscalation: false
```

![upload-image](/files/-MN2J8aumH58hTEbNPE8)

从上图的结果发现，进程的用户都是2000，这里说明container的级别要高于pod的级别，也就是container会覆盖pod中的securityContext配置。

## pod的特权模式运行

Privileged-决定是否 Pod 中的某容器可以启用特权模式。 默认情况下，容器是不可以访问宿主上的任何设备的，不过一个“privileged（特权的）” 容器则被授权访问宿主上所有设备。 这种容器几乎享有宿主上运行的进程的所有访问权限。 对于需要使用 Linux 权能字（如操控网络堆栈和访问设备）的容器而言是有用的

```
        image: busybox:latest
        imagePullPolicy: Always
        name: security-context
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: true
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
```

在上下文配置上这个字段，后续pod就可以获取宿主机的访问权限了。

## 参考文档

<https://kubernetes.io/zh/docs/tasks/configure-pod-container/security-context/#%E4%B8%BA-pod-%E9%85%8D%E7%BD%AE%E5%8D%B7%E8%AE%BF%E9%97%AE%E6%9D%83%E9%99%90%E5%92%8C%E5%B1%9E%E4%B8%BB%E5%8F%98%E6%9B%B4%E7%AD%96%E7%95%A5>


# Kubernetes之多集群的访问

日常使用k8s的过程中，我们可能存在多个集群需要管理，那么我们用kubectl来进行集群的切换个访问呢，下面简单说一下操作方式。

## 多集群kubeconfig合并

```
# cd /root/.kube/
# touch new-config1    #将一个集群kubeconfig内容写入new-config1文件中
# touch new-config2    #将另一个集群kubeconfig内容写入new-config2文件中
# KUBECONFIG=new-config1:new-config2  kubectl config view --flatten > $HOME/.kube/config
# kubectl config get-contexts   #获取集群信息
# kubectl config use-context xxxx-context-default   #切换集群
```

## 多集群kubeconfig中user信息相同如何合并

有的时候我们的不同集群的kubeconfig,会存在user和name信息都是admin，因为集群一般默认是用admin用户作为kubectl的kubeconfig,当你执行命令进行合并的时候发现新的config文件只会存在一个集群信息可以操作，这是因为user和name信息相同导致合并后只取一个集群的信息。这个需要怎么处理呢，我们这边讲解下，下面文件的token和证书都是测试的，防止泄露集群，信息已被更改。

集群A的config1

```
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01URXlOVEF5TURRek9Wb1hEVE13TVRFeU16QXlNRFF6T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWYwCk5yWDFVYXU0d1ppdC9uKy9meWlydHNIUnlYMWJvRDE2bUU3elo3Y3kwSit2WjVTdkZHMW5xbksvMmNzTWF6clUKM0tVTUZpZTE1Z1hvOThwTFBiMnFYaWpnTlZYc1N4ZnhPVG9PajJVU09WV2FiSCswLytRbVdsQXhQa242MUsvZ
    server: https://cls-A.ccs.tencent-cloud.com
  name: local
contexts:
- context:
    cluster: local
    user: admin
  name: cls-A-context-default
current-context: cls-A-context-default
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: 98Kvn5zx
```

集群B的config2

```
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01URXlOVEF5TURRek9Wb1hEVE13TVRFeU16QXlNRFF6T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWYwCk5yWDFVYXU0d1ppdC9uKy9meWlydHNIUnlYMWJvRDE2bUU3elo3Y3kwSit2WjVTdkZHMW5xbksvMmNzTWF6clUKM0tVTUZpZTE1Z1hvOThwTFBiMnFYaWpnTlZYc1N4ZnhPVG9PajJVU09WV2FiSCswLytRbVdsQXhQa242MUsvZ
    server: https://cls-B.ccs.tencent-cloud.com
  name: local
contexts:
- context:
    cluster: local
    user: admin
  name: cls-B-context-default
current-context: cls-B-context-default
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: FDSFKvn5zx
```

执行下面命令会出现合并后可以切换集群，但是切换集群后，获取集群节点等信息都是一个集群的。

```
KUBECONFIG=new-config1:new-config2  kubectl config view --flatten > $HOME/.kube/config
```

那么我们需要如何修改呢，可以按照下面方式修改下

修改后集群A的config1

```
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01URXlOVEF5TURRek9Wb1hEVE13TVRFeU16QXlNRFF6T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWYwCk5yWDFVYXU0d1ppdC9uKy9meWlydHNIUnlYMWJvRDE2bUU3elo3Y3kwSit2WjVTdkZHMW5xbksvMmNzTWF6clUKM0tVTUZpZTE1Z1hvOThwTFBiMnFYaWpnTlZYc1N4ZnhPVG9PajJVU09WV2FiSCswLytRbVdsQXhQa242MUsvZ
    server: https://cls-A.ccs.tencent-cloud.com
  name: A
contexts:
- context:
    cluster: A
    user: A-admin
  name: cls-A-context-default
current-context: cls-A-context-default
kind: Config
preferences: {}
users:
- name: A-admin
  user:
    token: 98Kvn5zx
```

修改后集群B的config2

```
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01URXlOVEF5TURRek9Wb1hEVE13TVRFeU16QXlNRFF6T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWYwCk5yWDFVYXU0d1ppdC9uKy9meWlydHNIUnlYMWJvRDE2bUU3elo3Y3kwSit2WjVTdkZHMW5xbksvMmNzTWF6clUKM0tVTUZpZTE1Z1hvOThwTFBiMnFYaWpnTlZYc1N4ZnhPVG9PajJVU09WV2FiSCswLytRbVdsQXhQa242MUsvZ
    server: https://cls-B.ccs.tencent-cloud.com
  name: B
contexts:
- context:
    cluster: B
    user: B-admin
  name: cls-B-context-default
current-context: cls-B-context-default
kind: Config
preferences: {}
users:
- name: B-admin
  user:
    token: FDSFKvn5zx
```

然后再执行合并config命令,执行kubectl切换集群获取集群信息正常

```
KUBECONFIG=new-config1:new-config2  kubectl config view --flatten > $HOME/.kube/config
```

## kubectx命令切换集群

我们也可以通过kubectx这个小工具进行集群的切换

```
# yum install git -y
# git clone https://github.com/ahmetb/kubectx /opt/kubectx
# ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
# export KUBECONFIG=$KUBECONFIG:$HOME/.kube/config1:$HOME/.kube/config2
# kubectx
cls-hzywbn-context-default
cls-llbn4z-context-default
# kubectx cls-hzywbn88-context-default
Switched to context "cls-hzywbn-context-default".
```

## kubens工具快速切换命名空间

其实这个项目中还有一个小工具可以快速切换ns，这里我们简单讲解下如何使用

```
# ln -s /opt/kubectx/kubens /usr/local/bin/kubens
# kubens
default
gitlab
istio-system
kube-node-lease
kube-ops
kube-public
kube-system
log
monitor
tcr-assistant-system
test
treafik
```


# k8s之externaltrafficpolicy保留客户端源ip

使用k8s暴露我们的服务给外部访问的方式主要是有2种，一种就是nodeport类型，还有一种就是LoadBalancer，通常这2中都会做一个负载均衡，但是有一个问题就是服务端可能无法获取到客户端的真实ip，service.spec.externalTrafficPolicy这个字段就帮您解决了这个问题。

service.spec.externalTrafficPolicy - 表示此服务是否希望将外部流量路由到节点本地或集群范围的端点。 有两个可用选项：Cluster（默认）和 Local。 Cluster 隐藏了客户端源 IP，可能导致第二跳到另一个节点，但具有良好的整体负载分布。 Local 保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型服务的第二跳， 但存在潜在的不均衡流量传播风险。

externalTrafficPolicy存在2种模式，一种是默认的Cluster类型，一种是Local类型，Cluster类型就是service基本的模式负载均衡，今天我们来讲一下Local这种模式，为什么Local模式会出现负载不均衡。

首先我们创建一个externalTrafficPolicy为Cluster类型的service，大家都知道k8s中的网络策略都是通过kube-proxy来配置iptables规则来进行转发的，下面我们查看下externalTrafficPolicy为Cluster类型的service对应nodeport的iptables规则

```
[root@VM-0-3-centos ~]# kubectl get svc -n test
NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
go-test                       ClusterIP      172.16.46.161   <none>          80/TCP         4d16h
nginx                         ClusterIP      172.16.45.116   <none>          80/TCP         7d21h
nginx-externaltrafficpolicy   NodePort       172.16.22.159   <none>          80:31015/TCP   15h
nginx-log                     LoadBalancer   172.16.96.7     106.55.216.XX   80:31465/TCP   3d
springboot                    ClusterIP      172.16.77.157   <none>          8080/TCP       4d
[root@VM-0-3-centos ~]# iptables-save | grep 31465
-A KUBE-NODEPORTS -p tcp -m comment --comment "test/nginx-log:80-80-tcp" -m tcp --dport 31465 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "test/nginx-log:80-80-tcp" -m tcp --dport 31465 -j KUBE-SVC-4MORCIL57YHWCCJM
```

我们nignx-log这个service就是Cluster类型，我们发现节点上对应31465这个nodeport的iptables规则就是放通所有来源ip来进行访问这个端口。

下面我们再看看Local类型的service，对应的iptables规则是什么样，这里我们nginx-externaltrafficpolicy这个service就是Local类型，对应service的yaml文件如下。

```
apiVersion: v1
kind: Service
metadata:
  annotations:
    service.cloud.tencent.com/local-svc-weighted-balance: "false"
    service.kubernetes.io/local-svc-only-bind-node-with-pod: "false"
  creationTimestamp: "2020-11-26T10:11:47Z"
  managedFields:
  - apiVersion: v1
    manager: tke-apiserver
    operation: Update
    time: "2020-11-26T10:11:47Z"
  name: nginx-externaltrafficpolicy
  namespace: test
  resourceVersion: "2118736256"
  selfLink: /api/v1/namespaces/test/services/nginx-externaltrafficpolicy
  uid: 7255ae77-0d6c-45da-8171-60521535d020
spec:
  clusterIP: 172.16.22.159
  externalTrafficPolicy: Local
  ports:
  - name: 80-80-tcp
    nodePort: 31015
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    k8s-app: nginx-externaltrafficpolicy
    qcloud-app: nginx-externaltrafficpolicy
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}
```

```
[root@VM-0-3-centos ~]# kubectl get svc -n test | grep nginx-externaltrafficpolicy
nginx-externaltrafficpolicy   NodePort       172.16.22.159   <none>          80:31015/TCP   16h
[root@VM-0-3-centos ~]# iptables-save | grep 31015
-A KUBE-NODEPORTS -s 127.0.0.0/8 -p tcp -m comment --comment "test/nginx-externaltrafficpolicy:80-80-tcp" -m tcp --dport 31015 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "test/nginx-externaltrafficpolicy:80-80-tcp" -m tcp --dport 31015 -j KUBE-XLB-3LS5F4HE6J2O753K
```

:KUBE-MARK-MASQ - \[0:0] /*对于符合条件的包 set mark 0x4000, 有此标记的数据包会在KUBE-POSTROUTING chain中统一做MASQUERADE*/

对于KUBE-MARK-MASQ链中所有规则设置了kubernetes独有MARK标记，在KUBE-POSTROUTING链中对NODE节点上匹配kubernetes独有MARK标记的数据包，进行SNAT处理。

查看节点上nginx-externaltrafficpolicy的nodeport端口的iptables规则，细心的你肯定发现有个地方和Cluster类型的是不一样的，那就是第一条规则中加了一个源ip网段的访问限制，默认只有127.0.0.0/8才能访问这个，那么Local模式的原理这里就清楚了，其实就是这个iptables规则配置的源ip导致的。

这也就是为啥你访问对应的nodeport的时候，只有pod所在的节点才能访问通，我们在同一个vpc下非集群节点进行访问nginx-externaltrafficpolicy这个pod测试一下

```
[root@VM-0-3-centos ~]# kubectl get pod -n test -o wide | grep nginx-externaltrafficpolicy-6654865c87-25cnq
nginx-externaltrafficpolicy-6654865c87-25cnq   2/2     Running   0          16h     10.0.2.37    10.0.0.3    <none>           1/1
```

```
[root@VM-0-13-centos ~]# curl 10.0.0.3:31015
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@VM-0-13-centos ~]# curl 10.0.0.10:31015
```

我们发现nginx-externaltrafficpolicy这个pod部署在10.0.0.3这个node上，我们在10.0.0.13这个节点访问nodeport，只有10.0.0.3这个节点的31015这个nodeport可以通，这也是符合Local模式的。

有的时候我们在定位问题也可以注意一下这个配置，可能出现只能访问pod所在节点才能通，这里可以检查下这个配置是否配置成了Local。


# fluent-bit收集不同业务日志

我们将服务部署在k8s中，通常需要收集业务日志进行分析，那么现在常用的日志采集是怎么做的呢，一般来说是搭建EFK系统来采集日志，今天我们来说说如何采集k8s中的不同业务日志然后投递到es不同的索引，然后通过kibana进行分析和查看。

对于EFK的搭建，我们这里不做具体的讲解，大家可参考 <https://www.niewx.cn/tke/kubernetes/log/2020/06/10/TKE%E4%B8%8A%E6%90%AD%E5%BB%BAEFK%E6%97%A5%E5%BF%97%E9%87%87%E9%9B%86%E7%B3%BB%E7%BB%9F/> , 今天我们主要来讲一下如何采集不同业务日志进行分析。

这里我们一共采集3类日志，分别是nginx日志、golang的日志、以及k8s的系统日志，一般我们采集的业务日志都是将业务日志挂载到节点，然后fluent-bit去采集节点上的日志文件，输出到es，通过kibana展示和查询。

## fluent-bit的部署

我们这里简单说下fluent-bit的部署,我们通过helm部署。

```
$ helm install --name my-release stable/fluent-bit
```

## 部署应用

### 部署nginx

我们将nginx的日志挂载到节点的/data/nginx目录下，对应的yaml如下

```
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: "2020-11-24T02:08:08Z"
  generation: 1
  labels:
    k8s-app: nginx-log
    qcloud-app: nginx-log
  managedFields:
  - apiVersion: apps/v1
    manager: tke-apiserver
    operation: Update
    time: "2020-11-24T02:08:08Z"
  - apiVersion: apps/v1
    manager: kube-controller-manager
    operation: Update
    time: "2020-11-27T03:21:32Z"
  name: nginx-log
  namespace: test
  resourceVersion: "2156061896"
  selfLink: /apis/apps/v1/namespaces/test/deployments/nginx-log
  uid: eb42b8f7-97f7-405b-a750-d46df6bcd8c6
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: nginx-log
      qcloud-app: nginx-log
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: nginx-log
        qcloud-app: nginx-log
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx-log
        resources:
          limits:
            cpu: 500m
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 256Mi
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /etc/nginx/nginx.conf
          name: conf
          subPath: nginx.conf
        - mountPath: /var/log/nginx
          name: log
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: qcloudregistrykey
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          defaultMode: 420
          name: nginx-conf
        name: conf
      - hostPath:
          path: /data/nginx
          type: DirectoryOrCreate
        name: log
```

### 部署golang

我们将golang的日志文件挂载到节点的/data/go目录下，对应的yaml如下

```
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "11"
  creationTimestamp: "2020-11-22T09:30:22Z"
  generation: 11
  labels:
    k8s-app: go-test
    qcloud-app: go-test
  managedFields:
  - apiVersion: apps/v1
    manager: tke-apiserver
    operation: Update
    time: "2020-11-27T05:47:59Z"
  - apiVersion: apps/v1
    manager: kube-controller-manager
    operation: Update
    time: "2020-11-27T05:59:13Z"
  name: go-test
  namespace: test
  resourceVersion: "2161865161"
  selfLink: /apis/apps/v1/namespaces/test/deployments/go-test
  uid: b267fe91-c0c7-40a5-94e2-cd1d169576a3
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: go-test
      qcloud-app: go-test
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: go-test
        qcloud-app: go-test
    spec:
      containers:
      - image: nwx-test.tencentcloudcr.com/nwx/go-test:go-20201127
        imagePullPolicy: Always
        name: go-test
        resources:
          limits:
            cpu: 100m
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 256Mi
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /data/
          name: vol
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - hostPath:
          path: /data/go
          type: DirectoryOrCreate
        name: vol
```

## 修改fluent-bit的配置文件和yaml

### 修改fluent-bit.conf

这里我们将fluent-bit.conf分成多个配置文件进行配置，你也可以配置到一个fluent-bit.conf

#### fluent-bit.conf配置项介绍

```
#1）fluent-bit-configmap.yaml修改部分（红字）：
#红色部分修改成自己想要的index名字
  output-elasticsearch.conf: |
   [OUTPUT]
       Name            es
       Match           *
       Host            ${FLUENT_ELASTICSEARCH_HOST}
       Port            ${FLUENT_ELASTICSEARCH_PORT}
       Logstash_Format On
       Replace_Dots    On
       Retry_Limit     False
       Logstash_Prefix test_k8s
#2）fluent-bit-ds.yaml修改部分：
#将FLUENT_ELASTICSEARCH_HOST修改成自己的Elasticsearch地址
 env:
 - name: FLUENT_ELASTICSEARCH_HOST
   value: "es.haha.com"
 - name: FLUENT_ELASTICSEARCH_PORT
   value: "80"

#1）fluent-bit-configmap.yaml修改部分（红字）：
#红色部分修改成自己想要的index名字
  output-elasticsearch.conf: |
   [OUTPUT]
       Name            kafka
       Match           *
       Brokers         ${FLUENT_KAFKA_ADDR}
       Topics          ${FLUENT_KAFKA_TOPIC}
       Timestamp_Key  @timestamp
       Retry_Limit    false
       # hides errors "Receive failed: Disconnected" when kafka kills idle connections
       rdkafka.log.connection.close false
       # producer buffer is not included in http://fluentbit.io/documentation/0.12/configuration/memory_usage.html#estimating
       rdkafka.queue.buffering.max.kbytes 10240
       # for logs you'll probably want this ot be 0 or 1, not more
       rdkafka.request.required.acks 1
#2）fluent-bit-ds.yaml修改部分：
#将FLUENT_KAFKA_ADDR修改成自己的Kafka地址
 env:
 - name: FLUENT_KAFKA_ADDR
   value: "es.haha.com"
 - name: FLUENT_KAFKA_TOPIC
   value: "test_k8s"  

[SERVICE]
    Flush         1 #buffer里的数据每隔1秒写到output插件里，这里写到ES里。
    Log_Level     info #fluent-bit的日志级别
    Daemon        off
    Parsers_File  parsers.conf #指向了另外一个配置文件，里面配置所有的parser。
    HTTP_Server   On
    HTTP_Listen   0.0.0.0
    HTTP_Port     2020

[INPUT]
    Name              tail #指定了input插件的类型，这里是tail类型
    Tag              {{$Host}}_{{$ESIndex}}_{{.ContainerName}}_{{$i}} #给采集的日志打个标签，后面filter和output根据这个标签选择数据源
    Path              /var/log/pods/${POD_UID}/{{.ContainerName}}/*.log
    Path_Key          filename
    Parser            docker
    DB                /var/log/pods/${POD_UID}/{{.ContainerName}}/flb_std.db#记录哪个文件采集到哪一行
    DB.Sync           Full #internal SQLite engine用哪种方法同步数据到磁盘，full为安全优先Key
    Mem_Buf_Limit     5MB #一旦buffer里的数据超过Mem_buf_limit，tail就会暂停采集，直到buffer数据被flush到output
    Skip_Long_Lines   Off #跳过长度大于Buffer_Max_Size的行,默认On,不关闭会出现have long lines.Skipping long lines的错误      
    Buffer_Chunk_Size 32k #tail命令的buffer初始大小，具体作用和对性能的影响还需进一步研究
    Buffer_Max_Size   32k #tail命令的buffer最大值，具体作用和对性能的影响还需进一步研究
    Refresh_Interval  10 #定时扫描磁盘上的新文件的间隔
    Rotate_Wait        5 #文件rotate后的等待一段时间后再继续监控这个文件，以防flush一些pending data
    Ignore_Older       10d #忽略近十天来未更改的文件
[FILTER]
    Name                modify #插件的类型
    Match               * #匹配到任何数据源
    Add node_name ${NODE_NAME}
    Add node_ip ${NODE_IP}
    Add pod_name ${POD_NAME}

[OUTPUT]
    Name   es #插件的类型
    Match  {{.Host}}_{{.ESIndex}}* #匹配到tag为{{.Host}}_{{.ESIndex}}*的数据源
    Host   {{.HostName}} #es的hostname 可以是域名和ip
    Port   {{.Port}} #es的端口
    Index {{.ESIndex}} 
    HTTP_User {{.UserName}}
    HTTP_Passwd {{.Password}}
    Pipeline #不要用
    Logstash_Format  On #是否采用类似logstash的index，可以根据时间设置index名字
    Logstash_Prefix logstash #索引名称的前缀
    Logstash_DateFormat %Y.%m.%d #名称后缀格式
    Time_Key  tail-time #Logstash_Format enabled的时候，每条记录会新产生一个时间戳
    Time_Key_Format %Y-%m-%dT%H:%M:%S #新时间戳的格式
    Generate_ID  On #对记录去重，可能有性能消耗。
    Trace_Output Off #打印elasticsearch API calls 调试的时候用。
    Logstash_Prefix_Key ttt #
    Retry_Limit     5 #传输失败后重试次数，默认为2，设置为False时，无限次重试
```

#### fluent-bit-filter.conf

日志过滤配置

```
[FILTER]
    Name                kubernetes
    Match               kube.*
    Kube_Tag_Prefix     kube.var.log.containers.
    Kube_URL            https://kubernetes.default.svc:443
    Kube_CA_File        /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    Kube_Token_File     /var/run/secrets/kubernetes.io/serviceaccount/token
    Merge_Log           On
    K8S-Logging.Parser  On
    K8S-Logging.Exclude On
```

#### fluent-bit-input.conf

日志源输入配置

```
[INPUT]
    Name              tail
    Path              /var/log/containers/*.log
    Parser            docker
    Tag               kube.*
    Refresh_Interval  5
    Mem_Buf_Limit     5MB
    Skip_Long_Lines   On

[INPUT]
    Name              tail
    Path               /data/nginx/*.log
    Tag               nginx.*
    Refresh_Interval  5
    Mem_Buf_Limit     5MB
    Skip_Long_Lines   On

[INPUT]
    Name              tail
    Path               /data/go/*.log
    Tag               go.*
    Refresh_Interval  5
    Mem_Buf_Limit     5MB
    Skip_Long_Lines   On
```

#### fluent-bit-output.conf

日志投递输出配置

```
[OUTPUT]
     Name            es
     Match           nginx.*
     Host            elasticsearch-master
     Port            9200
     Logstash_Format  On
     Logstash_Prefix  nginx
     Replace_Dots    On
     Retry_Limit     False

[OUTPUT]
     Name            es
     Match           kube.*
     Host            elasticsearch-master
     Port            9200
     Logstash_Format  On
     Logstash_Prefix  k8s
     Replace_Dots    On
     Retry_Limit     False


[OUTPUT]
     Name            es
     Match           go.*
     Host            elasticsearch-master
     Port            9200
     Logstash_Format  On
     Logstash_Prefix  go
     Replace_Dots    On
     Retry_Limit     False
```

#### fluent-bit-service.conf

日志读取配置

```
[SERVICE]
    Flush        1
    Daemon       Off
    Log_Level    info
    Parsers_File parsers.conf
```

#### fluent-bit.conf

fluent-bit的配置

```
@INCLUDE fluent-bit-service.conf
@INCLUDE fluent-bit-input.conf
@INCLUDE fluent-bit-filter.conf
@INCLUDE fluent-bit-output.conf
```

#### parsers.conf

日志转换配置,这里暂未配置

```
空
```

### 修改fluent-bit的daemonset的yaml

我们在fluent-bit的配置文件中指定了日志源的的采集和输出，但是fluent-bit pod是无法获取节点的日志文件，我们需要将节点的文件挂载到pod进行采集。修改的挂载yaml文件如下,其实就是把/data/nginx和/data/go这2个目录挂载到fluent-bit的pod中，当然还有默认的k8s日志采集目录/var/lib/docker/containers和系统日志目录/var/log，只是这2个目录不用我们自己手动修改，默认yaml会配置这2个。

```
apiVersion: apps/v1
kind: DaemonSet
metadata:
  annotations:
    deprecated.daemonset.template.generation: "3"
  creationTimestamp: "2020-11-24T05:12:12Z"
  generation: 3
  labels:
    app: fluent-bit
    chart: fluent-bit-2.8.16
    heritage: Helm
    release: nwx-fluent
  managedFields:
  - apiVersion: apps/v1
    manager: Go-http-client
    operation: Update
    time: "2020-11-24T05:12:12Z"
  - apiVersion: apps/v1
    manager: tke-apiserver
    operation: Update
    time: "2020-11-27T06:28:28Z"
  - apiVersion: apps/v1
    manager: kube-controller-manager
    operation: Update
    time: "2020-11-27T06:29:01Z"
  name: nwx-fluent-fluent-bit
  namespace: log
  resourceVersion: "2162960999"
  selfLink: /apis/apps/v1/namespaces/log/daemonsets/nwx-fluent-fluent-bit
  uid: 62891a61-809c-4045-9f34-4b742cd8cc7c
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: fluent-bit
      release: nwx-fluent
  template:
    metadata:
      annotations:
        checksum/config: a3e666db2b70ae3c1b8de003ce768a3ed14ff02dd643df16d2d6d2be111d3910
      creationTimestamp: null
      labels:
        app: fluent-bit
        release: nwx-fluent
    spec:
      containers:
      - image: fluent/fluent-bit:1.3.7
        imagePullPolicy: Always
        name: fluent-bit
        resources: {}
        securityContext:
          privileged: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/log
          name: varlog
        - mountPath: /var/lib/docker/containers
          name: varlibdockercontainers
          readOnly: true
        - mountPath: /fluent-bit/etc/fluent-bit.conf
          name: config
          subPath: fluent-bit.conf
        - mountPath: /fluent-bit/etc/fluent-bit-service.conf
          name: config
          subPath: fluent-bit-service.conf
        - mountPath: /fluent-bit/etc/fluent-bit-input.conf
          name: config
          subPath: fluent-bit-input.conf
        - mountPath: /fluent-bit/etc/fluent-bit-filter.conf
          name: config
          subPath: fluent-bit-filter.conf
        - mountPath: /fluent-bit/etc/fluent-bit-output.conf
          name: config
          subPath: fluent-bit-output.conf
        - mountPath: /data/nginx
          name: nginx
        - mountPath: /data/go
          name: go
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: nwx-fluent-fluent-bit
      serviceAccountName: nwx-fluent-fluent-bit
      terminationGracePeriodSeconds: 10
      volumes:
      - hostPath:
          path: /var/log
          type: ""
        name: varlog
      - hostPath:
          path: /var/lib/docker/containers
          type: ""
        name: varlibdockercontainers
      - configMap:
          defaultMode: 420
          name: nwx-fluent-fluent-bit-config
        name: config
      - hostPath:
          path: /data/nginx
          type: DirectoryOrCreate
        name: nginx
      - hostPath:
          path: /data/go
          type: DirectoryOrCreate
        name: go
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 1
    type: RollingUpdate
```

## 在kibana上创建索引检索日志

将上面的修改和部署都执行后，我们可以到对应的kibana上创建索引去搜索日志，这里我们已经对golang和nignx服务进行访问了，生成了一些日志

```
# 访问golang
for i in {1..100}; do curl 172.16.46.161:3000/api/v1; done
for i in {1..1000}; do curl 172.16.46.161:3000/; done

# 访问nginx
for i in {1..1000}; do curl 106.55.216.xx; done
```

我们先查看下es的索引，看看nignx和golang的日志索引在不在

```
[root@VM-0-3-centos go]# curl 172.16.111.218:9200/_cat/indices
green open k8s-2020.11.27   nOs7_AthSIGY6j_tODcdfA 1 1 1393033 0   632mb   316mb
green open k8s-2020.11.19   vUvvsjSSQy6r8VQeDSKZgw 1 1    6732 0   5.6mb   2.8mb
green open nginx-2020.11.27 LGjCjAW4Q3yJnGd-Y-_E8w 1 1   12172 0   4.4mb   2.2mb
green open k8s-2020.11.20   nQhDsyryS8aMfMwJCRAEKA 1 1  103524 0    34mb    17mb
green open k8s-2020.11.21   GHlg75nhRYqZTF3Z3p-D2Q 1 1  121832 0  37.5mb  18.9mb
green open go-2020.11.27    -jrQRrQOTGWxuiibaA-Fjg 1 1    5101 0 404.3kb 203.2kb
green open k8s-2020.11.22   B0QA301jRsu-Vmc6N4AD3Q 1 1   95367 0  29.1mb  14.5mb
green open k8s-2020.11.23   fPlbbGFBR3qiZGn4sAdDYQ 1 1   67689 0  20.8mb  10.3mb
green open .kibana_1        6DW_R-VvTwOlEvp8gISckA 1 1       4 0  71.2kb  35.6kb
green open k8s-2020.11.24   JHKRDHVWQqmIkvpxMFTa-w 1 1  124659 0  43.2mb  21.7mb
green open k8s-2020.11.25   JZ8WQTFnSSeOJRcW6mDl8w 1 1   66867 0  21.3mb  10.6mb
green open k8s-2020.11.26   FEvX7ZJlRammlS3xrqnqTQ 1 1  259974 0 175.9mb  87.9mb
```

这里查看到是有nginx和golang的日志索引的，下面我们去kibana创建一下index

![upload-image](/files/-MN7tJsfLxfuFxfR1Wqx)

我们分别创建nginx-*、go-*、k8s-\*分配来查询对应的业务日志

我们golang日志主要是存在2条类型日志“我访问了/api/v1这个路径”和“我访问了Hello World这个路径”，下面我们去检索下，看看能不能搜到

![upload-image](/files/-MN7tJsjS_vFnyY_MnQu)

![upload-image](/files/-MN7tJsnPafwY87LXsbE)

生成的日志我们都是可以搜索到的。

下面我们在49.235.179.157这个机器上对nginx的service进行访问，看看我们在日志能不能获取到49.235.179.157个客户端ip

```
for i in {1..1000}; do curl 106.55.216.xx; done
```

![upload-image](/files/-MN7tJspKSIyBVK2QRs6)

刚刚访问的记录我们也可以早上面搜索到

k8s的一些系统日志和其他pod日志，我们可以在k8s-\*这个索引下进行检索，这里我们搜一下我们部署的es的pod日志

![upload-image](/files/-MN7tJsqD--zjGhGKFDA)

es的日志我们也可以在kibana上查找到

到这里，对于日志的采集，我们基本上就结束了，这里我们只要按照上面步骤进行挂载和配置，你就可以通过EFK采集到不同的业务日志，然后可以在kibana进行图标的制作和展示。


