# 尚硅谷k8s课堂随笔

## 第一部分 K8S基本概念

### 1. K8S概述和特性

* K8S是谷歌在2014年开业的容器化集群管理系统
* 使用k8s进行容器化应用部署
* 使用k8s利于应用扩展
* k8s目标实施让部署容器化应用更加简洁和高效

### 2. K8S架构组件

​ **Master**（主控节点）和 **node** （工作节点）

1. master 组件
   * apiserver ---> 集群统一入口，以 **restfull** 方式，交给 etcd 存储
   * scheduler ---> 节点调度，选择node节点应用部署
   * controller-manager ---> 处理集群中常规后台任务，一个资源对应一个控制器
   * etcd ---> 存储系统，保存集群相关的数据
2. node组件
   * kubelet ---> master 派到 node 节点代理，管理本机容器
   * kube-proxy ---> 提供网络代理，负载均衡等操作

### 3. K8S核心概念

\- Pod

* 最小的部署单元
* 一组容器的集合
* 共享网络
* 生命周期是短暂的

\- controller

* 确保预期的pod副本数量
* 无状态应用部署
* 有状态应用部署
* 确保所有的 node 运行同一个 pod
* 一次性任务和定时任务

\- Service

* 定义一组 pod 的访问规则

### 4. 集群 YML 文件详解

#### 4.1 YAML 基本语法

* 使用空格做为缩进
* 缩进的空格数目不重要，只要相同层级的元素左侧对齐即可
* 低版本缩进时不允许使用 Tab 键，只允许使用空格
* 使用#标识注释，从这个字符一直到行尾，都会被解释器忽略

#### 4.2 支持的数据结构

* 对象; 键值对集合 -----( 映射/哈希/字典)

  ```yaml
  name: Tom
  age: 18                  ===   hash: {name: Tom, age: 19}
  ```
* **数组**; 序列 ？列表

  ```yaml
  People
  - Tom
  - Jack                    ===  People: [Tom, Jack]
  ```
* 纯量( scalars ), 不可分的值

  ```yaml
  number: 12.30       浮点数
  isSet: true         布尔值
  parent: ~           null 用 ~ 表示
  iso8601: 2001-12-14t21:59:43.10-05:00    时间采用 iso8601 格式
  date: 1976-07-31    日期
  字符串可以写成多行，第二行需有一个空格缩减
  ```

#### 4.3 资源清单表述方法

**4.3-1 YAML 必须存在的属性**

| 参数名                      | 字段类型   | 说明                                 |
| ------------------------ | ------ | ---------------------------------- |
| version                  | String | api版本，目前v1, 可用kubect api-version查看 |
| kind                     | String | 定的资源类型和角色： 比如 Pod                  |
| metadata                 | Object | 元数据对象，固定值 metadata                 |
| metadata.name            | String | 元数据对象名字                            |
| metadata.namespace       | String | 元数据命名空间                            |
| Spec                     | Object | 详细定义对象，固定值写Spec                    |
| spec.containers\[]       | list   | 这是Spec对象的容器列表定义，是个列表               |
| spec.containers\[].name  | String | 定义容器的名字                            |
| spec.containers\[].image | String | 定义要用到的镜像名称                         |

**4.3-2 YAML Spec 主要对象**

| 参数名                                          | 字段类型   | 说明                                                                   |
| -------------------------------------------- | ------ | -------------------------------------------------------------------- |
| spec.containers\[].imagePullPolicy           | String | 默认 Always; 定义镜像拉取策略，有Always(总是)、Never(使用本地)、IfNotPresent(如果本地没有就拉取); |
| spec.containers\[].command\[]                | List   | 指定容器启动命令，可指定多个，不指定默认使用容器打包的启动命令                                      |
| spec.containers\[].args\[]                   | List   | 指定容器启动参数，可指定多个                                                       |
| spec.containers\[].workingDir                | List   | 指定容器的工作目录                                                            |
| spec.containers\[].volumeMounts\[]           | List   | 指定容器内部的存储卷配置                                                         |
| spec.containers\[].valumeMounts\[].name      | String | 指定可以被容器挂载的存储卷名称                                                      |
| spec.containers\[].valumeMounts\[].mountPath | String | 指定可以被容器挂载的容器卷的路径                                                     |
| spec.containers\[].valumeMounts\[].readOnly  | String | 设置存储卷路径的读写模式，true或者false,默认为读写模式                                     |
| spec.containers\[].ports\[]                  | List   | 指定容器需要用到的端口列表                                                        |
| spec.containers\[].ports\[].name             | String | 指定端口名称                                                               |
| spec.containers\[].ports\[].containerPort    | String | 指定容器需要监听的端口号                                                         |
| spec.containers\[].ports.hostPort            | String | 指定容器所在主机需要监听的端口号，默认和 containerPort相同，如果设置了hostport，同一台主机无法启动相同副本，会冲突 |
| spec.containers\[].ports\[].protocol         | String | 指定端口协议，TCP/UDP，默认TCP                                                 |
| spec.containers\[].env\[]                    | List   | 指定容器运行时需设置的环境变量列表                                                    |
| spec.containers\[].env\[].name               | String | 指定环境变量名称                                                             |
| spec.containers\[].env\[].value              | String | 指定环境变量值                                                              |
| spec.containers\[].resources                 | Object | 指定资源限制和资源请求的值(资源上限)                                                  |
| spec.containers\[].resources.limits          | Object | 指定设置容器运行时资源的运行上限                                                     |
| spec.containers\[].resources.limits.cpu      | String | 指定CPU限制，单位 core 数，将用于 --cpu-shares 参数                                |
| spec.containers\[].resources.limits.memory   | String | 指定MEM内存限制，单位 MIB，GIB                                                 |
| spec.containers\[].resources.requests        | String | 指定容器启动和调度室的限制设置                                                      |
| spec.containers\[].resources.requests.cpu    | String | CPU请求，单位 core 数，容器启动时初始化可用数量                                         |
| spec.containers\[].resources.requests.memory | String | 内存请求，单位MIB,GIB,容器启动时初始化可用数量                                          |

**4.3-3 YAML 额外的参数**

| 参数名                   | 字段类型    | 说明                                                                                                                                               |
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| spec.restarPolicy     | String  | 定义Pod重启策略，可以选择值为Always、OnFailure,默认为Always. Always: Pod一旦终止运行，将重启它。 2.OnFilure ,只有Pod以非零退出码终止时，重启，否则，不重启。 3. Never: Pod终止后，将退出码报告给Mater，不会重启改Pod |
| spec.nodeSelector     | Object  | 定义Node的Label过滤标签，以key:value格式指定                                                                                                                  |
| spec.imagePullSecrets | Object  | 定义pull镜像是使用secrets名称，以name:secretKey格式指定                                                                                                         |
| spec.hostNetwork      | Boolean | 定义是否使用主机忘了默认，默认值false,设置true表示使用宿主机忘了，不适用docker网桥，同时设置true将无法在一台宿主机上启动第二个副本                                                                      |

**4.3-4 举例说明**

```yaml
# 创建一个命名空间 namespace, 创建一个Pod
apiVersion: v1
kind: Namespace
metadata:
    name: test
---
apiVersion: v1
kind: Pod
metadata:
    name: pod1
spec:
    containers:
    - name: ningx-containers
      image: nginx:latest
```

```shell
kubectl get namespaces      # 查看命名空间
kubectl get pods            # 查看pod
kubectl describe pods/pod1  # 查看指定pod
```

### 5. kubectl 命令集合

#### 5.1 基础命令解释

| create | 通过文件名或标准输入创建资源             |
| ------ | -------------------------- |
| expose | 将一个资源公开为一个新的 Service       |
| run    | 在集群中运行一个特定的镜像              |
| set    | 在对象上设置特定的功能                |
| get    | 显示一个或多个资源                  |
| expian | 文档参考资料                     |
| edit   | 使用默认的编辑器编辑一个资源             |
| delete | 通过文件名、标准输入、资源名称或标签选择器来删除资源 |

#### 5.2 部署和集群管理命令解释

| rollout        | 管理资源的发布                                 |
| -------------- | --------------------------------------- |
| rolling-update | 对给定的复制控制器滚动更新                           |
| scale          | 扩容或缩容Pod数量，Deployment、ReplicaSet、RC或Job |
| autoscale      | 创建一个自动选择扩容和缩容并设置Pod数量                   |
| certificate    | 修改证书资源                                  |
| cluster-info   | 显示集群信息                                  |
| top            | 显示资源(CPU/Memory/Storage)使用              |
| cordon         | 标记节点不可调度                                |
| uncordon       | 标记节点可调度                                 |
| drain          | 驱逐节点上的应用，准备下线维护                         |
| taint          | 修改节点 taint 标记                           |

#### 5.3 故障和调试命令解释

| describe     | 显示特定资源或资源组的详细信息                      |
| ------------ | ------------------------------------ |
| logs         | 在一个Pod中打印一个容器日志。如果Pod只有一个容器，容器名称是可选的 |
| attach       | 附加到一个运行的容器                           |
| exec         | 执行命令到容器                              |
| port-forward | 转发一个或多个本地端口到一个pod                    |
| cp           | 拷贝文件或目录到容器中                          |
| auth         | 检查授权                                 |

#### 5.4 其他命令解释

| apply       | 通过文件名或标准输入对资源应用配置                |
| ----------- | -------------------------------- |
| patch       | 使用补丁修改、更新资源的字段                   |
| replace     | 通过文件名或标准输入替换一个资源                 |
| convert     | 不同的API版本直接转换配置文件                 |
| label       | 更新资源上的标签                         |
| annotate    | 更新资源上的注释                         |
| completion  | 用于实现kubectl工具自动补全                |
| api-version | 打印受支持的API版本                      |
| config      | 修改kubeconfig文件(用户访问API，比如配置认证信息) |
| plugin      | 运行一个命令行插件                        |
| version     | 打印客户端和服务版本信息                     |
| help        | 帮助                               |

#### 5.5 如何跨省编写 yaml 文件

1、第一种 使用 **kubectl create** 命令生成 yaml 文件

```shell
kubectl create deploy web --image=nginx -o yaml --dry-run>my.yaml
```

2、第二中 使用 \*\*kubectl get \*\* 命令导出yaml文件 ; <适用于已有部署集>

```shell
kubectl get deploy <部署集名字> -o yaml --export > my.yaml
```

```shell
# create 语法： kubectl create -f FILANEM [flags]  
  - kubectl create -f xx.yaml -f xx.yaml       # 创建资源 或 多个资源
  - kubectl create -f ./dir                    # 使用目录下所有 yaml 文件创建资源
  - kubectl create -f http://baidu.com/xxx.yaml# 使用 url 创建资源
  - kubectl create namespace myspace           # 创建名字为 myspace 的命名空间
  - kubectl delete namespace myspace           # 删除名字为 myspace 的命名空间

# expose 将副本控制器、服务或 pod 作为新的 Kubernetes 服务暴露 ，语法：
kubectl expose (-f FILENAME | TYPE NAME | TYPE/NAME) [–port=port] [–protocol=TCP|UDP] [–target-port=number-or-name] [–name=name] [–external-ip=external-ip-of-service] [–type=type] [flags]
  - kubect expose rc nginx --port=80 --tarport=8000     # 为副本控制器(rc)的nginx创建service，并通过service的80端口转发到容器的8000端口上
  - kubect expose -f xxx.yml       # 由xxx.yml中指定的type和name表示的RC创建Service，并通过service的80端口转发到容器的8000端口

# run 
kubectl run nginx --image=nginx                 # 启动nginx实例
kubectl run nginx --image=nginx --port=80       #　启动nginx实例，并暴露容器80端口
kubectl run nginx --image=nginx --replicas=5    # 启动nginx实例，设置副本数5
kubectl run nginx --image=nginx --env="DNS_DOMAIN=cluster" --env="POD_NAMESPACE=default" # 启动nginx实例，并设置变量


```

```shell
kubectl get namespaces                       # 查看所有命名空间及状态, 查看某一个，后面加上命名空间名字
kubectl describe namespaces <命名空间>        # 查看命名空间信息
kubectl create namespace <命名空间>           # 创建命名空间
kubectl create deploy <部署集名字> -image=nginx  --namespace=<名称空间名字>  # 在指定命名空间上部署应用
kubectl get deploy -n <命名空间>              # 查看命名空间下的部署集
kubectl delete deploy <部署集名字>            # 直接删除pod会触发部署集，重新创建pod，所以直接删除部署集
kubectl delete namespace <命名空间>           # 此操作会删除该命名空间下的所有资源
kubectl describe pods <pod名字> -n <命名空间> # 查看某个命名空间下某个pod的详细信息
kubectl logs <pod> -n <命名空间>              # 查看pod日志 
```

## 第二部分 搭建K8S集群

### 方法一：使用 **kubeadm**方式搭建k8s集群

#### 1. 三台 **Centos7.x** 服务器

| 服务器        | IP(需联网)       |
| ---------- | ------------- |
| k8s-master | 192.168.1.134 |
| k8s-node01 | 192.168.1.135 |
| k8s-node02 | 192.168.1.136 |

#### 2. 所有节点初始化操作

```shell
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 关闭selinux   # 永久    # 临时
sed -i 's/enforcing/disabled/' /etc/selinux/config
setenforce 0

# 关闭swap   # 临时    # 永久
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab

# 根据规划设置主机名
hostnamectl set-hostname <hostname>

# 只在master添加hosts
cat >> /etc/hosts << EOF
192.168.44.134 k8s-master
192.168.44.135 k8s-node01
192.168.44.136 k8s-node02
EOF

# 将桥接的IPv4流量传递到iptables的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system  # 生效

# 时间同步
yum install ntpdate -y
ntpdate time.windows.com
```

#### 3. 所有节点安装程序包

> 所有节点：**docker 、kubelet 、kubeam 、kubctl**

**3.1 安装 Docker**

```shell
yum install wget -y
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-18.06.1.ce-3.el7
systemctl enable docker && systemctl start docker
docker --version
# 显示： Docker version 18.06.1-ce, build e68fc7a
# 由于国内服务器，拉取国外镜像过慢，配置镜像加速
cat > /etc/docker/daemon.json << EOF
{
  "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF
```

**3.2 添加阿里云YUM软件源**

```shell
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
```

**3.3 安装kubeadm，kubelet和kubectl**

```shell
# 由于版本更新频繁，这里指定版本号部署：
yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0
systemctl enable kubelet
```

#### 4. Master 集群初始化

> kubeadm init命令

```shell
# Master(192.168.1.134) 上执行, 其他ip端只要不冲突即可
kubeadm init \
  --apiserver-advertise-address=192.168.1.134 \
  --image-repository registry.aliyuncs.com/google_containers \
  --kubernetes-version v1.18.0 \
  --service-cidr=10.96.0.0/12 \
  --pod-network-cidr=10.244.0.0/16
# 初始化成功后，依据提示执行
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes
```

#### 5. Node 节点添加到当前集群中

```python
kubeadm join 192.168.1.134:6443 --token pvx7z8.ubvh64gsfv5l5tw0 \
    --discovery-token-ca-cert-hash sha256:c65c8e0f8e4f897bfdbe9160f69e380942e4d6e38e6690e435b5d2a4bd0d3294
```

#### 6.所有节点配置网络插件

```shell
# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

kubectl get pods -n kube-system
NAME                          READY   STATUS    RESTARTS   AGE
kube-flannel-ds-amd64-2pc95   1/1     Running   0          72s
```

#### 7. 测试kubernetes集群

```shell
# 在Master节点，在Kubernetes集群中创建一个pod，验证是否正常运行：
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort
kubectl get pod,svc

访问地址：http://NodeIP:Port  
```

### 方法二：二进制方式

#### 1. 三台 **Centos7.x** 服务器

| 服务器        | IP(需联网)       |
| ---------- | ------------- |
| k8s-master | 192.168.1.134 |
| k8s-node01 | 192.168.1.135 |
| k8s-node02 | 192.168.1.136 |

#### 2. 所有节点初始化操作

```shell
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 关闭selinux   # 永久    # 临时
sed -i 's/enforcing/disabled/' /etc/selinux/config
setenforce 0

# 关闭swap   # 临时    # 永久
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab

# 根据规划设置主机名
hostnamectl set-hostname <hostname>

# 只在master添加hosts
cat >> /etc/hosts << EOF
192.168.1.134 k8s-master
192.168.1.135 k8s-node01
192.168.1.136 k8s-node02
EOF

# 将桥接的IPv4流量传递到iptables的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system  # 生效

# 时间同步
yum install ntpdate -y
ntpdate time.windows.com
```

#### 3. 准备 cfssl 证书生成工具

```shell
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
```

**3.1 创建工作目录**

```shell
mkdir -p ~/TLS/{etcd,k8s}
cd TLS/etcd
```

**3.2 创建生成 Etcd Ca 证书配置**

```shell
# 自签证书
cat << EOF | tee ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "www": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

cat << EOF | tee ca-csr.json
{
    "CN": "etcd CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF
```

**3.3 创建 ETCD Server申请文件**

```shell
cat << EOF | tee server-csr.json
{
    "CN": "etcd",
    "hosts": [
    "192.168.1.134",
    "192.168.1.135",
    "192.168.1.136"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

# 注：上述文件 hosts 字段中 IP 为所有 etcd 节点的集群内部通信 IP，一个都不能少！为了 方便后期扩容可以多写几个预留的 IP。
```

**3.4 生成 Etcd HTTPS证书**

```shell
# 生成自签Etcd CA 证书， 并使用自签 CA 签发 ETCD 证书
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 ca*pem && ls server*pem
```

**3.5 创建生成 kube-apiserver 配置**

```shell
cd ~/TLS/k8s/

# 自签证书颁发机构（CA）
cat << EOF | tee ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF


cat << EOF | tee ca-csr.json
{
    "CN": "kubernetes",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}
EOF
```

**3.6 创建 kube-apiserver 证书申请文件**

```shell
cat << EOF | tee server-csr.json
{
    "CN": "kubernetes",
    "hosts": [
      "10.0.0.1",
      "127.0.0.1",
      "192.168.1.134",
      "192.168.1.135",
      "192.168.1.136",
      "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
```

**3.7 生成 kube-apiserver HTTPS 证书**

```shell
# 生成CA证书, 然后使用自签 CA 签发 kube-apiserver HTTPS 证书
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes server-csr.json | cfssljson -bare server
ls ca*pem && ls server*pem
```

#### 4. 部署**Etcd集群**

**4.1 下载安装包**

```shell
cd ~/TLS/etcd
wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz
mkdir /opt/etcd/{bin,cfg,ssl} -p
tar zxvf etcd-v3.4.9-linux-amd64.tar.gz
mv etcd-v3.4.9-linux-amd64/{etcd,etcdctl} /opt/etcd/bin/
```

**4.2 拷贝刚才生成的证书**

```shell
cp ~/TLS/etcd/ca*pem ~/TLS/etcd/server*pem /opt/etcd/ssl/
```

**4.3 创建 etcd配置文件**

```shell
cat << EOF | tee /opt/etcd/cfg/etcd.conf
#[Member]
ETCD_NAME="etcd-1"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.1.134:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.1.134:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.1.134:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.1.134:2379"
ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.1.134:2380,etcd-2=https://192.168.1.135:2380,etcd-3=https://192.168.1.136:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
EOF
```

**4.4 使用Systemd管理 etcd**

```shell
cat << EOF |tee /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.conf
ExecStart=/opt/etcd/bin/etcd \
--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 \
--logger=zap
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF
```

**4.5 拷贝文件到 Node 节点**

```shell
# 注意复制过去后，修改下 etcd.conf 中IP地址，为节点的IP
scp -r /opt/etcd/ root@192.168.1.135:/opt/
scp -r /opt/etcd/ root@192.168.1.136:/opt/
scp /usr/lib/systemd/system/etcd.service root@192.168.1.135:/usr/lib/systemd/system/
scp /usr/lib/systemd/system/etcd.service root@192.168.1.136:/usr/lib/systemd/system/
```

**4.6 启动 etcd**

```shell
# 启动ETCD集群同时启动二个节点，单节点是无法正常启动的。
systemctl daemon-reload && systemctl start etcd && systemctl enable etcd
```

**4.7 检查服务是否正常**

```shell
ETCDCTL_API=3 /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.1.134:2379,https://192.168.1.135:2379,https://192.168.1.136:2379" endpoint health

https://192.168.1.134:2379 is healthy: successfully committed proposal: took = 9.978053ms
https://192.168.1.136:2379 is healthy: successfully committed proposal: took = 13.282176ms
https://192.168.1.135:2379 is healthy: successfully committed proposal: took = 12.787146ms
# 显示结果如上。如果不正常，查看日志：　/var/log/message  或者 journalctl -u etcd
```

#### 5. 安装 Docker

> 所有节点上

**5.1 下载源码包**

```shell
wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz
tar zxvf docker-19.03.9.tgz 
mv docker/* /usr/bin
```

**5.2 Systemd 管理 docker**

```shell
cat <<EOF |tee /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 
ExecStart=/usr/bin/dockerd 
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
```

**5.3 加速镜像**

```shell
mkdir /etc/docker 
cat > /etc/docker/daemon.json << EOF 
{
	"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF
```

**5.4 开机自启**

```shell
systemctl daemon-reload && systemctl start docker && systemctl enable docker
```

#### 6. **部署 Master Node**

> 下载地址： <https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.18.md>
>
> 这里使用版本： v1.18.3 , 下载 server 包，里面啥都有
>
> 安装 master 必须组件

**6.1 下载二进制包**

```shell
wget https://dl.k8s.io/v1.18.13/kubernetes-server-linux-amd64.tar.gz
mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs}
tar zxvf kubernetes-server-linux-amd64.tar.gz
cd kubernetes/server/bin
cp kube-apiserver kube-scheduler kube-controller-manager /opt/kubernetes/bin
cp kubectl /usr/bin/
```

```shell
# 把刚才生成的证书拷贝到配置文件中的路径
cp ~/TLS/k8s/ca*pem ~/TLS/k8s/server*pem /opt/kubernetes/ssl/
```

**6.2 启用 TLS Bootstrapping 机制**

```shell
# token 生成  ====>  head -c 16 /dev/urandom | od -An -t x | tr -d ' '
# 格式：token，用户名，UID，用户组
cat > /opt/kubernetes/cfg/token.csv << EOF
c47ffb939f5ca36231d9e3121a252940,kubelet-bootstrap,10001,"system:node-bootstrapper"
EOF
```

**6.3 部署 kube-apiserver**

```shell
# kube-apiserver 配置文件
cat > /opt/kubernetes/cfg/kube-apiserver.conf << EOF
KUBE_APISERVER_OPTS="--logtostderr=false \\
--v=2 \\
--log-dir=/opt/kubernetes/logs \\
--etcd-servers=https://192.168.1.134:2379,https://192.168.1.135:2379,https://192.168.1.136:2379 \\
--bind-address=192.168.1.134 \\
--secure-port=6443 \\
--advertise-address=192.168.1.134 \\
--allow-privileged=true \\
--service-cluster-ip-range=10.0.0.0/24 \\
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \\
--authorization-mode=RBAC,Node \\
--enable-bootstrap-token-auth=true \\
--token-auth-file=/opt/kubernetes/cfg/token.csv \\
--service-node-port-range=30000-32767 \\
--kubelet-client-certificate=/opt/kubernetes/ssl/server.pem \\
--kubelet-client-key=/opt/kubernetes/ssl/server-key.pem \\
--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 \\
--audit-log-maxage=30 \\
--audit-log-maxbackup=3 \\
--audit-log-maxsize=100 \\
--audit-log-path=/opt/kubernetes/logs/k8s-audit.log"
EOF
```

```shell
# systemd 管理
cat > /usr/lib/systemd/system/kube-apiserver.service << EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-apiserver.conf
ExecStart=/opt/kubernetes/bin/kube-apiserver \$KUBE_APISERVER_OPTS
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload && systemctl start kube-apiserver && systemctl enable kube-apiserver && systemctl status kube-apiserver
```

```shell
# 授权 kubelet-bootstrap 用户允许请求证书
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap
```

> 参数详解： –logtostderr：启用日志 —v：日志等级 –log-dir：日志目录 –etcd-servers：etcd集群地址 –bind-address：监听地址 –secure-port：https安全端口 –advertise-address：集群通告地址 –allow-privileged：启用授权 –service-cluster-ip-range：Service虚拟IP地址段 –enable-admission-plugins：准入控制模块 –authorization-mode：认证授权，启用RBAC授权和节点自管理 –enable-bootstrap-token-auth：启用TLS bootstrap机制 –token-auth-file：bootstrap token文件 –service-node-port-range：Service nodeport类型默认分配端口范围 –kubelet-client-xxx：apiserver访问kubelet客户端证书 –tls-xxx-file：apiserver https证书 –etcd-xxxfile：连接Etcd集群证书 –audit-log-xxx：审计日志

**6.4 部署kube-controller-manager**

```shell
cat > /opt/kubernetes/cfg/kube-controller-manager.conf << EOF
KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=false \\
--v=2 \\
--log-dir=/opt/kubernetes/logs \\
--leader-elect=true \\
--master=127.0.0.1:8080 \\
--bind-address=127.0.0.1 \\
--allocate-node-cidrs=true \\
--cluster-cidr=10.244.0.0/16 \\
--service-cluster-ip-range=10.0.0.0/24 \\
--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 \\
--experimental-cluster-signing-duration=87600h0m0s"
EOF


cat > /usr/lib/systemd/system/kube-controller-manager.service << EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-controller-manager.conf
ExecStart=/opt/kubernetes/bin/kube-controller-manager \$KUBE_CONTROLLER_MANAGER_OPTS
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF



systemctl daemon-reload && systemctl start kube-controller-manager && systemctl enable kube-controller-manager && systemctl status kube-controller-manager
```

> –master：通过本地非安全本地端口8080连接apiserver。 –leader-elect：当该组件启动多个时，自动选举（HA） –cluster-signing-cert-file/–cluster-signing-key-file：自动为kubelet颁发证书的CA，与apiserver保持一致

**6.5 部署kube-scheduler**

```shell
cat > /opt/kubernetes/cfg/kube-scheduler.conf << EOF
KUBE_SCHEDULER_OPTS="--logtostderr=false \
--v=2 \
--log-dir=/opt/kubernetes/logs \
--leader-elect \
--master=127.0.0.1:8080 \
--bind-address=127.0.0.1"
EOF



cat > /usr/lib/systemd/system/kube-scheduler.service << EOF
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-scheduler.conf
ExecStart=/opt/kubernetes/bin/kube-scheduler \$KUBE_SCHEDULER_OPTS
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF


systemctl daemon-reload && systemctl start kube-scheduler && systemctl enable kube-scheduler && systemctl status kube-scheduler
#### 查看集群状态
[root@k8s-master ~]# kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok                  
controller-manager   Healthy   ok                  
etcd-2               Healthy   {"health":"true"}   
etcd-0               Healthy   {"health":"true"}   
etcd-1               Healthy   {"health":"true"} 
```

#### 7. **部署Worker Node**

> **下面还是在 Master 上操作，即同时作为 Worker Node**

**7.1 创建工作目录并拷贝二进制文件**

```shell
mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs}
# 之前下载的 k8s server 包
cd kubernetes/server/bin
cp kubelet kube-proxy /opt/kubernetes/bin

```

**7.2 部署 kubelet**

```shell
# 1. 创建配置文件
cat > /opt/kubernetes/cfg/kubelet.conf << EOF
KUBELET_OPTS="--logtostderr=false \\
--v=2 \\
--log-dir=/opt/kubernetes/logs \\
--hostname-override=k8s-master \\
--network-plugin=cni \\
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \\
--bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \\
--config=/opt/kubernetes/cfg/kubelet-config.yml \\
--cert-dir=/opt/kubernetes/ssl \\
--pod-infra-container-image=lizhenliang/pause-amd64:3.0"
EOF

# 2. 设置配置参数文件
cat > /opt/kubernetes/cfg/kubelet-config.yml << EOF
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 0.0.0.0
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS:
- 10.0.0.2
clusterDomain: cluster.local 
failSwapOn: false
authentication:
  anonymous:
    enabled: false
  webhook:
    cacheTTL: 2m0s
    enabled: true
  x509:
    clientCAFile: /opt/kubernetes/ssl/ca.pem 
authorization:
  mode: Webhook
  webhook:
    cacheAuthorizedTTL: 5m0s
    cacheUnauthorizedTTL: 30s
evictionHard:
  imagefs.available: 15%
  memory.available: 100Mi
  nodefs.available: 10%
  nodefs.inodesFree: 5%
maxOpenFiles: 1000000
maxPods: 110
EOF


# 3. 生成bootstrap.kubeconfig文件
KUBE_APISERVER="https://192.168.1.134:6443"
TOKEN="c47ffb939f5ca36231d9e3121a252940"

kubectl config set-cluster kubernetes \
  --certificate-authority=/opt/kubernetes/ssl/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=bootstrap.kubeconfig
kubectl config set-credentials "kubelet-bootstrap" \
  --token=${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

# 4. 拷贝到配置文件路径
cp bootstrap.kubeconfig /opt/kubernetes/cfg
```

```shell
cat > /usr/lib/systemd/system/kubelet.service << EOF
[Unit]
Description=Kubernetes Kubelet
After=docker.service
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kubelet.conf
ExecStart=/opt/kubernetes/bin/kubelet \$KUBELET_OPTS
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload && systemctl start kubelet && systemctl enable kubelet && systemctl status kubelet
```

```shell
# 查看kubelet证书请求
kubectl get csr
NAME                                                   AGE    SIGNERNAME                                    REQUESTOR           CONDITION
node-csr-uCEGPOIiDdlLODKts8J658HrFq9CZ--K6M4G7bjhk8A   6m3s   kubernetes.io/kube-apiserver-client-kubelet   kubelet-bootstrap   Pending

# 批准申请
kubectl certificate approve node-csr-uCEGPOIiDdlLODKts8J658HrFq9CZ--K6M4G7bjhk8A

# 查看节点
kubectl get node
NAME         STATUS     ROLES    AGE   VERSION
k8s-master   NotReady   <none>   7s    v1.18.3     # 注：由于网络插件还没有部署，节点会没有准备就绪 NotReady
```

**7.3 部署kube-proxy**

```shell
# 1. 创建配置文件
cat > /opt/kubernetes/cfg/kube-proxy.conf << EOF
KUBE_PROXY_OPTS="--logtostderr=false \\
--v=2 \\
--log-dir=/opt/kubernetes/logs \\
--config=/opt/kubernetes/cfg/kube-proxy-config.yml"
EOF

# 2. 配置文件参数
cat > /opt/kubernetes/cfg/kube-proxy-config.yml << EOF
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
metricsBindAddress: 0.0.0.0:10249
clientConnection:
  kubeconfig: /opt/kubernetes/cfg/kube-proxy.kubeconfig
hostnameOverride: k8s-master
clusterCIDR: 10.0.0.0/24
EOF

# 3. 创建证书请求文件
cd ~/TLS/k8s
cat > kube-proxy-csr.json << EOF
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "L": "BeiJing",
      "ST": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
EOF

# 4. 生成证书
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 kube-proxy*pem


# 5. 生成kube-proxy.kubeconfig文件
KUBE_APISERVER="https://192.168.1.134:6443"

kubectl config set-cluster kubernetes \
  --certificate-authority=/opt/kubernetes/ssl/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

# 6. 拷贝到配置文件指定路径
cp kube-proxy.kubeconfig /opt/kubernetes/cfg/
```

```shell
cat > /usr/lib/systemd/system/kube-proxy.service << EOF
[Unit]
Description=Kubernetes Proxy
After=network.target
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-proxy.conf
ExecStart=/opt/kubernetes/bin/kube-proxy \$KUBE_PROXY_OPTS
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload && systemctl start kube-proxy && systemctl enable kube-proxy && systemctl status kube-proxy
```

**7.4 部署CNI网络**

**部署**

```shell
# 必须是 /opt/cni/bin 路径，不然 node 不是 Ready , https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz
mkdir /opt/cni/bin -p
tar zxvf cni-plugins-linux-amd64-v0.8.6.tgz -C /opt/cni/bin
# flannel 地址：https://github.com/flannel-io/flannel
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 查看是否在安装
kubectl get pods -n kube-system
NAME                          READY   STATUS    RESTARTS   AGE
kube-flannel-ds-amd64-2pc95   1/1     Running   0          72s

# 查看是否已经准备就绪
kubectl get node
NAME         STATUS   ROLES    AGE   VERSION
k8s-master   Ready    <none>   41m   v1.18.3
```

**授权 apiserver 访问 kubelet**

```shell
cat > apiserver-to-kubelet-rbac.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
      - pods/log
    verbs:
      - "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
EOF
```

**7.5 增加Worker Node**

```shell
# 1. 在 master 节点将 Worker Node 涉及文件拷贝到新节点
scp -r /opt/kubernetes root@192.168.1.135:/opt/
scp -r /opt/kubernetes root@192.168.1.136:/opt/

scp -r /usr/lib/systemd/system/{kubelet,kube-proxy}.service root@192.168.1.135:/usr/lib/systemd/system
scp -r /usr/lib/systemd/system/{kubelet,kube-proxy}.service root@192.168.1.136:/usr/lib/systemd/system

scp -r /opt/cni/ root@192.168.1.135:/opt/
scp -r /opt/cni/ root@192.168.1.136:/opt/


scp /opt/kubernetes/ssl/ca.pem root@192.168.1.135:/opt/kubernetes/ssl
scp /opt/kubernetes/ssl/ca.pem root@192.168.1.136:/opt/kubernetes/ssl

# 2. 在新节点上删除kubelet证书和kubeconfig文件;  注：这几个文件是证书申请审批后自动生成的，每个Node不同，必须删除重新生成。
rm -f /opt/kubernetes/cfg/kubelet.kubeconfig 
rm -f /opt/kubernetes/ssl/kubelet*

# 3. 在新节点上修改主机名；  kebelet、 kube-proxy
vi /opt/kubernetes/cfg/kubelet.conf
--hostname-override=k8s-node01

vi /opt/kubernetes/cfg/kube-proxy-config.yml
hostnameOverride: k8s-node01

# 4. 在新节点上启动并设置开机启动
systemctl daemon-reload
systemctl start kubelet kube-proxy
systemctl enable kubelet kube-proxy
systemctl status kubelet kube-proxy

# 5. 回到 Master 上批准新Node kubelet证书申请
kubectl get csr
NAME                                                   AGE   SIGNERNAME                                    REQUESTOR           CONDITION
node-csr-4zTjsaVSrhuyhIGqsefxzVoZDCNKei-aE2jyTP81Uro   89s   kubernetes.io/kube-apiserver-client-kubelet   kubelet-bootstrap   Pending

kubectl certificate approve node-csr-4zTjsaVSrhuyhIGqsefxzVoZDCNKei-aE2jyTP81Uro

# 6. 查看Node状态
kubectl get node
```

## 第三部分 K8S核心技术

* Pod
* 控制器
* 储存
* Sservice
* 调度器
* 安全机制RBAC
* 包管理工具 Helm等等

### 1. Pod

#### 1.1 Pod 基本概念

* 最小部署单元
* 包含多个容器(一组容器的集合)
* 一个pod中容器共享网络命名空间
* pod是短暂的

#### 1.2 Pod 实现机制

> * 共享网络： 通过Pause容器，把其他业务容器加入到Pansu容器里，让所有业务容器在同一个命名空间中，实现网络共享
> * 共享存储： 引入数据卷 Volumn,使用数据卷进行持久化存储

| Pod 特性 | 说明                                             |
| ------ | ---------------------------------------------- |
| 资源共享   | 一个pod中多个容器共享存储和网络                              |
| 生命周期短暂 | 若Pod节点发生故障,那么该Pod会调度到其他节点，是一个全新的Pod，跟之前的pod无关联 |
| 平坦的网络  | 所有Pod都在同一共享网络地址空间中，每个Pod可以通过其他Pod的IP进行访问       |

#### 1.3 Pod 生命周期和重启策略

| Pod 状态    | 说明                                            |
| --------- | --------------------------------------------- |
| Pending   | API Server已经创建该Pod,但Pod中的一个或多个容器还没创建，包括镜像下载过程 |
| Running   | Pod内错有容器已创建，且至少一个容器处于运行状态、正在启动或正在重启           |
| Completed | Pod内所有容器均成功执行退出，且不会重启                         |
| Faild     | Pod内所有容器均已退出，但至少一个容器退出失败                      |
| Unknow    | 由于某种原因无法获取Pod状态，例如网络通信不畅                      |

| Pod 重启策略  | 说明                             |
| --------- | ------------------------------ |
| Always    | 当容器失效时，由kubelet自动重启该容器         |
| OnFailure | 当容器终止运行且退出码不为0，由kubelet自动重启该容器 |
| Never     | 不论容器运行状态如何， kublete都不会重启该容器    |

| Pod包含的容器数 | Pod当前的状态 | 发生事件     | Pod的结果状态             |                         |                     |
| --------- | -------- | -------- | -------------------- | ----------------------- | ------------------- |
|           |          |          | RestartPolicy=Always | RestartPolicy=OnFailure | RestartPolicy=Never |
| 包含一个容器    | Running  | 容器成功退出   | Running              | Successded              | Successded          |
| 包含一个容器    | Running  | 容器失败退出   | Running              | Running                 | Failure             |
| 包含两个容器    | Running  | 1个容器失败退出 | Running              | Running                 | Running             |
| 包含两个容器    | Running  | 容器被OOM杀掉 | Running              | Running                 | Failure             |

#### 1.4 Pod 资源配置

> * requests:需求，最低保障；
> * limits：限制，硬限制； 超过被杀死

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    image: nginx
    resources:
      requests:
        cpu: "200m"
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"
# 上述代码表明 Nginx 容器申请最少 0.2 个 CPU 以及 128MiB 内存，在运行过程中容器所能使 用的资源配额为 0.5 个 CPU 以及 512MiB 内存。
```

#### 1.5 Pod 健康检查

* livenessProbe （存活检查）

  > **如果检查失败，将杀死容器，根据Pod的 restartPolicy 来操作**
* readlinessProbe (就绪检查)

  > **如果检查失败， Kubernetes会把Pod从 service endpoint中剔除**

| Probe 支持是三种检查方式 | 说明                         |
| --------------- | -------------------------- |
| httpget         | 发送HTTP请求，返回200·400范围状态码为成功 |
| exec            | 执行Shell命令返回状态码是0为成功        |
| tcpSocket       | 发起 Tcp Socket 建立成功         |

#### 1.6 **Pod 创建流程**

​ - 1、 kubect 向 Apiserver 发送创建Pod请求 ​ - 2、Api server 收到请求，生成 YAML 文件，并把YAML信息写入 Etcd 中 ​ - 3、 Scheduler 调度器通过Apiserver 读取 Etcd 信息进行判断，如果是NUll,使用调度算法，分配到资源利用少的节点上，并把更新信息写入 Etcd内， ​ - 4、Node 节点上 的 kubelet 会不停的请求 apiserver，读取Etcd数据，如果编号和自己的相同，则调用 dcokerApi，创建容器

#### 1.7 Pod 调度(节点亲和)

​ - 硬亲和性: 约束条件必须满足 ​ - 软亲和性: 尝试满足，不保证

> 支持常用操作符： In NotIn Exists Gt Lt DoesNotExists

* 影响调度的属性
  * Pod 资源限制对Pod调用产生影响 ( 设置资源限制过大，会找不到足够资源的node节点)
  * 节点选择器标签影响 Pod 调度 (只可调用到指定标签的节点)

#### 1.8 **Pod 调度(节点污点)**

* 使用场景
  * 专用节点
  * 配置特点、硬件特点
  * 基于 **Taint** 驱逐
* 污点值 <常用于master节点> - Noschedule 一定不被调度 - PreferNoSchedule 尽量不被调度 - NoExecute 不会调度，并且驱逐已有的 Pod

```shell
#  查看污点
kubectl describe node <节点名字> |grep -i taint
#　为节点添加污点
kubectl taint node <节点名称> key=value:<污点值>
# 删除污点，注意后面要加  - 
kubectl taint node <节点名称> key=value:<污点值>-
```

* 污点容忍

  > 比如某个节点节点被标记为了污点节点，要想 pod 能够调度到 master 节点去，这个该怎么办？\`\`\`

```yaml
tolerations:
- key: "key"
  operator: "Exists"
  value: 'Equal'
  effect: "NoSchedule"
# 或者
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"
  
# 如果 operator 的值是 Exists，则 value 属性可省略
# 如果 operator 的值是 Equal，则表示其 key 与 value 之间的关系是 equal(等于)
# 如果不指定 operator 属性，则默认值为 Equal
```

### 2. Controller

#### 2.1 什么是 Controller？

> **在集群上管理和运行容器的对象，俗称控制器**

#### 2.2 Pod 与 Controller 关系？

> **Pod 是通过 Controller 实现应用的运维，比如伸缩、滚动升级等等**

#### 2.3 Deployment 控制器应用场景

* 部署无状态应用
* 管理 Pod 与 ReplicaSet
* 部署，滚动升级等功能

  \------- 主要应用场景： web服务、微服务

#### 2.4 Yaml 文件字段说明

```yaml
# 使用 deployment 部署应用(上下选择标签要相同)
··· 
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
```

#### 2.5 Deployment 控制器部署应用

```shell
# 1. 导出 Yaml 文件 <主要是为了后面重新生成>
kubectl create deploy web --image=nginx --dry-run=client -o yaml > web.yaml
# 2. 部署应用
kubectl apply -f web.yaml
# 3. 对外发布 (暴露对外端口号，这种方法，一个端口只能用于这一个应用)
kubectl expose deploy web --port=80 --target-port=80 --type=NodePort --name=web1 -o yaml > web1.yaml
kubectl apply -f web1.yaml
kubectl get svc    ----> web1         NodePort    10.0.0.202   <none>        80:31281/TCP   2m56s
# 测试端口访问
curl localhost:31281
```

#### 2.6 应用升级回滚和弹性伸缩

```shell
# 应用升级版本
kubectl set image deploy web nginx=nginx:1.15
# 查看升级的状态
kubectl rollout status deploy web
# 查看升级的版本
kubectl rollout history deploy web


# 新版本不好用，再切回来老版本；回滚
kubectl rollout undo deploy web

# 回滚到指定的版本
kubectl rollout undo deploy web --to-revision=2


# 手动弹性伸缩
kubectl scale deploy web --replicas=5
```

### 3. Service

> 　服务发现

#### 3.1 Service 存在意义

* 防止与 Pod 的是失联
* 定义一组Pod 的访问策略 （相当于负载均衡器）

#### 3.2 Service 和 Pod 关系

​ - Pod 类似架构中的 后端服务器， Service 相当于架构中的虚拟 VIP，通过Service实现负载均衡

​ - Pod 与 Service 根据 Label 和 selector 标签选择器建立关联

#### 3.3 Servier 类型

* 1、ClusterIP: 集群内部使用
* 2、NodePort: 对外访问应用
* 3、LoadBalancor: 对外访问应用使用，公有云

### 4. 无状态和有状态

#### 4-1 无状态

* 认为Pod 都是一样的
* 没有顺序要求
* 不用考虑在哪个 node 运行
* 随意进行伸缩和扩展

#### 4-2 有状态

* 用于无状态的特点
* 会让每个Pod独立，保持 Pod 启动顺序和唯一性
* 唯一的网络标识符，持久存储
* 有序，比如mysql主从

#### 4-3 部署有状态应用

* **无头Service**

  > ClusterIP: none # 通过YAML文件设置 ClusterIP 为 None
* SatefulSet 部署有状态应用

  > kind: StatefulSet # 通过YAML文件设置 kind 为 StatefulSet
  >
  > \- 能保证每个 Pod 名称唯一性

**注意：** deployment 和 SatefulSet 区别：<唯一标识不同>

> deployment ： 根据主机名 + 安装一定规则生成域名 SatefulSet: 根据主机名称.service名称.名称空间.svc.cluster.local

**3.4-4 DaemonSet 守护进程**

* 在每个node上运行一个pod， 新加入的 node 也同样运行这个pod
* 例如： 在每个 node 节点上安装数据采集工具

### 5 计划任务

#### 5.1 一次性任务

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  # 同时启动两个任务
  #parallelism: 2  
  template:
    metadata:
      name: myjb
    spec:
      containers:
      - name: hello
        image: busybox
        command: ["echo","hello k8s job"]
      restartPolicy: Never
      
# 常用命令
kubectl get jobs
kubectl delete jobs xxx
```

#### 5.2 定时任务

```yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: mycronjob
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          name: mycronjob
        spec:
          containers:
          - name: hello
            image: busybox
            command: ["echo","hello k8s job"]
          restartPolicy: OnFailure
          
# 常用命令
kubectl get cronjob
kubectl delete cronjob  xxx
```

### 6. Secret

#### 6.1 Secret存在意义

> Secret 解决了密码、token、密钥等敏感数据的配置问题，而不需要把这些敏感数据暴露 到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用
>
> 作用： 加密数据存在 etcd 里面， 让 Pod 容器以挂载 Volume 方式镜像访问
>
> 场景： 凭证

#### 6.2 **Secret 有三种类型**

* Service Account :用来访问 Kubernetes API,由 Kubernetes 自动创建，并且会自动挂载到Pod 的 /run/secrets/kubernetes.io/serviceaccount 目录中
* Opaque : base64 编码格式的 Secret,用来存储密码、密钥等
* kubernetes.io/dockerconfigjson ：用来存储私有 docker registry 的认证信息

**6.2-1 Service Account**

```shell
kubectl run nginx --image nginx
kubectl exec nginx ls /var/run/secrets/kubernetes.io/serviceaccount
> ca.crt  namespace  token
```

**6.2-2 Opaque Secret**

**1. 编写一个 secret 对象**

```shell
echo -n "admin" | base64           ---> YWRtaW4=
echo -n "1f2d1e2e67df" | base64    ---> MWYyZDFlMmU2N2Rm:

# cat secrets.yaml
apiVersion: v1 
kind: Secret 
metadata: 
  name: mysecret 
type: Opaque 
data: 
  password: MWYyZDFlMmU2N2Rm 
  username: YWRtaW4=
  
kubectl apply -f secrets.yaml
kubectl get secret
> mysecret              Opaque                                2      32s
```

**2. 将 Secret 挂载到Volume中**

```yaml
# cat pod1.yml 
apiVersion: v1
kind: Pod
metadata:
  name: mysecret
spec:
  containers:
  - name: demo
    image: myapp:v1
    volumeMounts:
    - name: secrets
      mountPath: "/secret"
      readOnly: true
  volumes:
  - name: secrets
    secret:
      secretName: mysecret
      #items:       向指定路径映射 secret 密钥
      #- key: username
      #  path: my-group/my-username  #相对路径
      
kubectl apply -f pod1.yml
kubectl exec mysecret --  ls /secret

```

**3. 将 Secret 设置为环境变量**

> 　缺点：　环境变量读取　Secret　很方便，但**无法支撑Secret动态更新。**

```yaml
# cat pod2.yml 
apiVersion: v1
kind: Pod
metadata:
  name: secret-env
spec:
  containers:
  - name: nginx
    image: myapp:v1
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password

kubectl apply -f pods.yml
kubectl exec secret-env -- env
> SECRET_USERNAME=admin
> SECRET_PASSWORD=1f2d1e2e67df
```

**6.2-3 dockerconfigjson**

> **kubernetes.io/dockerconfigjson 用于存储 docker registry 的认证信息.**

```shell
# 使用 Kuberctl 创建 docker registry 认证的 secret
kubectl create secret docker-registry myregistrykey --docker- server=DOCKER_REGISTRY_SERVER -- docker-username=DOCKER_USER --docker- password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
> secret "myregistrykey" created.

# cat pod3.yml  在创建 Pod 的时候，通过 imagePullSecrets 来引用刚创建的'myregistrykey
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: mario
      image: reg.westos.org/westos/mario
  imagePullSecrets:
    - name: myregistrykey    ##没有则无法访问私有仓库

```

#### 6.3 ConfigMap

> **作用： 存储不加密数据到 etcd， 让Pod以变量或者Volume挂载到容器中** **场景： 配置文件**

**6.3-1 Pod 使用 ConfigMap 方式？**

* 将ConfigMap中的数据设置为环境变量
* 将ConfigMap中的数据设置为命令行参数
* 使用Volume将ConfigMap作为文件或目录挂载

> 注意： - ConfigMap必须在Pod使用它之前创建 - 使用envFrom时，将会自动忽略无效的键 - Pod只能使用同一个命名空间的ConfigMap

**6.3-2 创建ConfigMap两种方式**

**方式(1)：命令行方式**

```shell
kubectl create configmap nginxconfig --from-file /server/yaml/nginx/nginx.conf #通过命令直接创建
kubectl get configmap nginxconfig -o yaml #查看文件
```

**方式(2)：YMAL文件方式**

```yaml
cat nginx-configmap.yaml 
#-----------------------------创建命名空间----------------------------
apiVersion: v1
kind: Namespace                 
metadata:
  name: mt-math                  #创建的命名空间名称为mt-math
---
#-----------------------------创建configmap文件-----------------------
#创建configmap有多种方式,可以通过命令如:
#kubectl create configmap nginxconfig --from-file /server/yaml/nginx/nginx.conf
#查看创建的yaml文件内容:kubectl get configmap nginxconfig -o yaml
#这里采用标准的yaml文件格式进行创建:更新时采用:kubectl  replace -f nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginxconfig
  namespace: mt-math
data:
  nginx.conf: |
    #########wyl530
    user nginx;
    worker_processes auto;
    error_log /etc/nginx/error.log;
    pid /run/nginx.pid;
    include /usr/share/nginx/modules/*.conf;
    events {
        worker_connections 1024;
    }
    http {
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
        server_tokens     off;
        access_log        /usr/share/nginx/html/access.log  main;
        sendfile            on;
        tcp_nopush          on;
        tcp_nodelay         on;
        keepalive_timeout   65;
        types_hash_max_size 2048;
        include             /etc/nginx/mime.types;
        default_type        application/octet-stream;
        include /etc/nginx/conf.d/*.conf;
        server {
            listen       80 default_server;
            listen       [::]:80 default_server;
            server_name  _;
            root         /usr/share/nginx/html;
            include /etc/nginx/default.d/*.conf;
            location / {
            }
            error_page 404 /404.html;
                location = /40x.html {
            }
            error_page 500 502 503 504 /50x.html;
                location = /50x.html {
            }
        }

    }
```

**创建pod yaml文件**

```yaml
cat deployment.yaml 
#----------------------------创建pv---------------------------------
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-pv01             #创建的pv名称可创建多个.
  namespace: mt-math            #属于的命名空间
  labels:
    pv: pv-nfs-01               #定义pv标签,后续通过pvc绑定特定的pv标签。通常如果不写标签则默认通过访问方式和storage大小进行批量绑定。(重要)
spec:
  capacity:
    storage: 1Gi                 #创建的pv-nfs-pv01大小为1G
  accessModes:
  - ReadWriteMany
  nfs:                           #创建的pv数据来源
    path: /NFS/pv01              #数据源目录
    server: 192.168.0.14         #数据源ip
#--------------------------------创建pvc--------------------------------
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nfs-data-pvc              #创建的pvc名称
  namespace: mt-math              #属于的命名空间 
spec:
  accessModes:
    - ReadWriteMany                       
  resources:
    requests:
      storage: 1Gi               #请求大小为1G
  selector:                      #定义标签选择器,此时k8s会根据标签,storage,访问方式 进行匹配。三者同时满足才会绑定。如果不定义,则系统会根据storage的大小和访问方式进行匹配绑定.
    matchLabels:
      pv: pv-nfs-01              #定义请求标签为pv-nfs-pv01的pv且大小为1G
#--------------------------------创建pod------------------------------------
---
apiVersion: extensions/v1beta1   #接口版本
kind: Deployment                 #接口类型
metadata:                        #基础标签名称信息
  name: wyl-nginx                #创建的pod名称
  namespace: mt-math             #创建的pod属于mt-math命名空间
spec:                            #详细参数设置
  replicas: 3                    #副本数量
  selector:                      #标签选择器
    matchLabels:
      app: wyl-nginx             #正对标签为wyl-nginx的pod进行副本的创建
  strategy:
    rollingUpdate:               #由于replicas为3,则整个升级,pod个数在2-4个之间
      maxSurge: 1                #滚动升级时会先启动1个pod
      maxUnavailable: 1          #滚动升级时允许的最大Unavailable的pod个数
  template:                      #模板
    metadata:
      labels:
        app: wyl-nginx           #模板pod名称必填
    spec:                        #定义容器模板信息，该模板可以包含多个容器
      containers:
        - name:  nginx
          image: nginx:latest
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
          volumeMounts:
          - name: nginx-nfs
            mountPath: /usr/share/nginx/html
          - name: nginx-pvc
            mountPath: /var/log/nginx
            subPath: nginx.conf
          - name: nginx-etc       #挂载数据节点名称
            mountPath: /etc/nginx/nginx.conf #挂载此目录
            subPath: nginx.conf
      volumes:                    #设置挂载
      - name: nginx-nfs           #挂载的数据节点名称
        nfs:                      #挂载的服务类型
          server: 192.168.0.14    #服务Ip
          path: /NFS/wwwroot      #挂载数据目录
      - name: nginx-pvc           #挂载数据节点名称
        persistentVolumeClaim:    #服务类型
          claimName: nfs-data-pvc #数据源名称
      - name: nginx-etc           #挂载数据节点名称
        configMap:
         name: nginxconfig        #指定创建configMap的名称
         items:
          - key: nginx.conf       #key为文件名称
            path: nginx.conf      #文件路径内容
---
#-----------------------创建server-------------------------------------------
kind: Service
apiVersion: v1
metadata:
  name: wyl-nginx             
  namespace: mt-math             #属于的命名空间
spec:
  selector:
    app: wyl-nginx               #针对标签为wyl-nginx的标签进行负载
  type: NodePort                 #正对Node节点进行端口暴露
  ports:                       
    - protocol: TCP              #使用端口的协议
      port: 3017                 #供内网访问暴露的端口
      targetPort: 80             #目标pod的端口
      nodePort: 33333            #node节点暴露的端口
#简单说明-------------------------------------------------------------------
#1.创建了mt-math命名空间,删除命名空间默认删除该命名空间下的所有资源服务
#2.创建了pv,pv不属于任何命名空间
#3.创建了pvc，pvc属于mt-math命名空间
#4.创建了pod属于mt-math命名空间
#5.创建了server属于mt-math命名空间
#6.建议将pv单独写到一个文件中.否则在我们删除改命名空间下的所有服务后,利用该文件再次创建时会报pv已经创建.
#---------------------------------------------------------------------------
```

**创建资源服务**

```
kubectl create -f /server/yaml/nginx/nginx-configmap.yaml 
kubectl create -f /server/yaml/nginx/deployment.yaml 
kubectl exec -it wyl-nginx-d6f68d775-l4rkb --namespace=mt-math -- cat /etc/nginx/nginx.conf |head -1
```

### 7. 集群安全机制

#### 7.1 概述

RBAC 实现机制：基于角色的访问控制

> 大白话： 白名单机制，想让他具备什么权限，就先创建某个角色Role，然后定制rules，再将某个用户user与role绑定，bind得到这种绑定关系为RoleBind

> \- Role **权限的集合** - ClusterRole **集群角色** - RoleBinding **角色绑定** - ClusterRoleBinding **集群角色绑定**

### 8. helm

#### 8.1 Helm 安装<包管理工具>

* helm：一个命令行客户端工具，主要用于 Kubernetes 应用 chart 的创建、打包、发 布和管理。
* Chart：应用描述，一系列用于描述 k8s 资源相关文件的集合。
* Release：基于 Chart 的部署实体，一个 chart 被 Helm 运行后将会生成对应的一个 release；将在 k8s 中创建出真实运行的资源对象。

```shell
# Helm 客户端下载地址：https://github.com/helm/helm/releases
wget https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz
tar fx helm-v3.2.1-linux-amd64.tar.gz
mv linux-amd64/helm /usr/bin/

# 添加存储库
helm repo add stable http://mirror.azure.cn/kubernetes/charts 
helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts 
helm repo update

# 查看配置的存储库
helm repo list 
helm search repo stable


# 删除某个存储库，比如aliyun
helm repo remove aliyun
```

> helm 配置国内 chart 仓库 微软仓库: <http://mirror.azure.cn/kubernetes/charts> 阿里云仓库: <https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts> 官方仓库: <https://hub.kubeapps.com/charts/incubator>

| 命令         | 描述                                                          |
| ---------- | ----------------------------------------------------------- |
| create     | 创建一个chart 并指定名字                                             |
| dependency | 管理 chart 依赖                                                 |
| get        | 下载一个release.可用子命令： all 、hooks、manifest、notes、values         |
| history    | 获取 release 历史                                               |
| install    | 安装一个 chart                                                  |
| list       | 列出 release                                                  |
| package    | 将 chart 目录打包到 chart 存档文件中                                   |
| pull       | 从远程仓库下载 chart 并解压到本地                                        |
| repo       | 添加，列出，移除，更新和索引chart仓库：可用子命令add, index, list, remove, update |
| rollback   | 从之前版本回滚                                                     |
| search     | 根据关键字搜索chart。可用子命令： all, repo                               |
| show       | 查看chart想想信息，可用子命令：all,chart,readme,values                   |
| status     | 显示已命名版本的状态                                                  |
| template   | 本地呈现摸版                                                      |
| uninstall  | 卸载一个 release                                                |
| upgrade    | 更新一个 release                                                |
| version    | 查看helm客户端版本                                                 |

#### 8.2 使用 chart 部署一个应用

```shell
helm search repo weave
helm install ui stable/weave-scope
helm list
# 源码安装： 提示错误： Error: Kubernetes cluster unreachable
# export KUBERNETES_MASTER=http://127.0.0.1:8080  只需要指定一下apiserver的地址和端口号即可 
# 修改 service Type: NodePort 即可访问 ui
```

#### 9. Ingress

**9.1 概述**

```yaml
在Kubernetes中，服务和Pod的IP地址仅可以在集群网络内部使用，对于集群外的应用是不可见的。为了使外部的应用能够访问集群内的服务，在Kubernetes 目前 提供了以下几种方案：
 - NodePort
 - LoadBalancer
 - Ingress
 
# 组成
ingress controller
　　将新加入的Ingress转化成Nginx的配置文件并使之生效
ingress服务
　　将Nginx的配置抽象成一个Ingress对象，每添加一个新的服务只需写一个新的Ingress的yaml文件即可
```

**9.2 工作原理**

```shell
1. ingress controller通过和kubernetes api交互，动态的去感知集群中ingress规则变化
2. 然后读取它，按照自定义的规则，规则里写明了哪个域名对应哪个service，生成一段nginx配置，
3. 再写到nginx-ingress-control的pod里，这个Ingress controller的pod里运行着一个Nginx服务，控制器会把生成的nginx配置写入/etc/nginx.conf文件中
4. 然后reload一下使配置生效。以此达到域名分配置和动态更新的问题。 
```

**9.3 部署Ingress 两种方式**

**下载部署文件**

```shell
# Github 地址: https://github.com/kubernetes/ingress-nginx
wget https://github.com/kubernetes/ingress-nginx/blob/nginx-0.30.0/deploy/static/mandatory.yaml

1.namespace.yaml 
创建一个独立的命名空间 ingress-nginx
2.configmap.yaml 
ConfigMap是存储通用的配置变量的，类似于配置文件，使用户可以将分布式系统中用于不同模块的环境变量统一到一个对象中管理；而它与配置文件的区别在于它是存在集群的“环境”中的，并且支持K8S集群中所有通用的操作调用方式。
从数据角度来看，ConfigMap的类型只是键值组，用于存储被Pod或者其他资源对象（如RC）访问的信息。这与secret的设计理念有异曲同工之妙，主要区别在于ConfigMap通常不用于存储敏感信息，而只存储简单的文本信息。
ConfigMap可以保存环境变量的属性，也可以保存配置文件。
创建pod时，对configmap进行绑定，pod内的应用可以直接引用ConfigMap的配置。相当于configmap为应用/运行环境封装配置。
pod使用ConfigMap，通常用于：设置环境变量的值、设置命令行参数、创建配置文件。

3.default-backend.yaml 
如果外界访问的域名不存在的话，则默认转发到default-http-backend这个Service，其会直接返回404：

4.rbac.yaml 
负责Ingress的RBAC授权的控制，其创建了Ingress用到的ServiceAccount、ClusterRole、Role、RoleBinding、ClusterRoleBinding

5.with-rbac.yaml 
是Ingress的核心，用于创建ingress-controller。前面提到过，ingress-controller的作用是将新加入的Ingress进行转化为Nginx的配置
```

**方式一：Nodeport方式**

> mandatory.yaml 中的镜像可修改成国内镜像： 例如： - registry.cn-qingdao.aliyuncs.com/kubernetes\_xingej/defaultbackend-amd64 - registry.cn-qingdao.aliyuncs.com/kubernetes\_xingej/nginx-ingress-controller

```shell
# 下载yaml文件并更新mandatory.yaml中的镜像地址（master上）若国内服务器： 需替换下镜像地址
mkdir ingress-nginx && cd ingress-nginx
wget https://github.com/kubernetes/ingress-nginx/blob/nginx-0.30.0/deploy/static/mandatory.yaml
wget https://github.com/deploy/static/provider/baremetal/service-nodeport.yaml  # nodeport 方式需要额外使用这个文件

# 编辑 service-nodeport.yaml， 添加两行，分别在 http、https下，意思是暴露对外端口
nodePort: 32080  #http
nodePort: 32443  #https

# 运行部署上面两个文件
kubectl apply -f mandatory.yaml
kubectl apply -f service-nodeport.yaml
```

**helm 创建工作环境**

```shell
[root@k8s-master]# helm create mychart
[root@k8s-master]# rm -f mychart/templates/*
[root@k8s-master mychart]# tree
.
├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
└── values.yaml  <暂没写变量，测试使用>
```

**depoyment.yaml**

```yaml
### cat deployment.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mytest

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: mytest
  labels:
    app: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: nginx
          ports:
            - containerPort: 80
              name: http
              protocol: TCP
```

**service.yaml**

```yaml
# cat service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  namespace: mytest
spec:
  selector:
    app: myapp
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
```

**Ingress.yaml**

```yaml
# cat ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: mytest
spec:
  rules:
    - host: myapp.com
      http:
        paths:
          - path: /
            backend:
              serviceName: myapp-svc
              servicePort: 80
```

**helm 部署**

```shell
helm install ./mychart --generate-name
访问： myapp.com:32080
```

**方式二：deamonset方式**

> deamonset方式与nodeport方式只有一处不同，只修改mandatory.yaml，而且，service-nodeport.yaml也不用配置，其他不用修改，访问的时候，直接域名访问，不用加nodeport。

### 9. Yaml格式的pod定义文件完整内容

```yaml
apiVersion: v1          #必选，版本号，例如v1
kind: Pod               #必选，Pod
metadata:               #必选，元数据
  name: string          #必选，Pod名称
  namespace: string     #必选，Pod所属的命名空间
  labels:               #自定义标签
    - name: string      #自定义标签名字
  annotations:          #自定义注释列表
    - name: string
spec:                   #必选，Pod中容器的详细定义
  containers:           #必选，Pod中容器列表
  - name: string        #必选，容器名称
    image: string       #必选，容器的镜像名称
    imagePullPolicy: [Always | Never | IfNotPresent] #获取镜像的策略 Alawys表示下载镜像 IfnotPresent表示优先使用本地镜像，否则下载镜像，Nerver表示仅使用本地镜像
    command: [string]    #容器的启动命令列表，如不指定，使用打包时使用的启动命令
    args: [string]       #容器的启动命令参数列表
    workingDir: string   #容器的工作目录
    volumeMounts:        #挂载到容器内部的存储卷配置
    - name: string       #引用pod定义的共享存储卷的名称，需用volumes[]部分定义的的卷名
      mountPath: string  #存储卷在容器内mount的绝对路径，应少于512字符
      readOnly: boolean  #是否为只读模式
    ports:               #需要暴露的端口库号列表
    - name: string       #端口号名称
      containerPort: int #容器需要监听的端口号
      hostPort: int      #容器所在主机需要监听的端口号，默认与Container相同
      protocol: string   #端口协议，支持TCP和UDP，默认TCP
    env:                 #容器运行前需设置的环境变量列表
    - name: string       #环境变量名称
      value: string      #环境变量的值
    resources:           #资源限制和请求的设置
      limits:            #资源限制的设置
        cpu: string      #Cpu的限制，单位为core数，将用于docker run --cpu-shares参数
        memory: string   #内存限制，单位可以为Mib/Gib，将用于docker run --memory参数
      requests:          #资源请求的设置
        cpu: string      #Cpu请求，容器启动的初始可用数量
        memory: string   #内存清楚，容器启动的初始可用数量
    livenessProbe:       #对Pod内个容器健康检查的设置，当探测无响应几次后将自动重启该容器，检查方法有exec、httpGet和tcpSocket，对一个容器只需设置其中一种方法即可
      exec:              #对Pod容器内检查方式设置为exec方式
        command: [string] #exec方式需要制定的命令或脚本
      httpGet:           #对Pod内个容器健康检查方法设置为HttpGet，需要制定Path、port
        path: string
        port: number
        host: string
        scheme: string
        HttpHeaders:
        - name: string
          value: string
      tcpSocket:           #对Pod内个容器健康检查方式设置为tcpSocket方式
         port: number
       initialDelaySeconds: 0  #容器启动完成后首次探测的时间，单位为秒
       timeoutSeconds: 0   #对容器健康检查探测等待响应的超时时间，单位秒，默认1秒
       periodSeconds: 0    #对容器监控检查的定期探测时间设置，单位秒，默认10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged:false
    restartPolicy: [Always | Never | OnFailure]#Pod的重启策略，Always表示一旦不管以何种方式终止运行，kubelet都将重启，OnFailure表示只有Pod以非0退出码退出才重启，Nerver表示不再重启该Pod
    nodeSelector: obeject  #设置NodeSelector表示将该Pod调度到包含这个label的node上，以key：value的格式指定
    imagePullSecrets:      #Pull镜像时使用的secret名称，以key：secretkey格式指定
    - name: string
    hostNetwork:false      #是否使用主机网络模式，默认为false，如果设置为true，表示使用宿主机网络
    volumes:               #在该pod上定义共享存储卷列表
    - name: string         #共享存储卷名称 （volumes类型有很多种）
      emptyDir: {}         #类型为emtyDir的存储卷，与Pod同生命周期的一个临时目录。为空值
      hostPath: string     #类型为hostPath的存储卷，表示挂载Pod所在宿主机的目录
        path: string       #Pod所在宿主机的目录，将被用于同期中mount的目录
      secret:              #类型为secret的存储卷，挂载集群与定义的secre对象到容器内部
        scretname: string  
        items:     
        - key: string
          path: string
      configMap:           #类型为configMap的存储卷，挂载预定义的configMap对象到容器内部
        name: string
        items:
        - key: string
```

### 10. NFS

#### 10.1 服务端

```shell
yum -y install nfs-utils rpcbind
systemctl start rpcbind nfs
systemctl enable rpcbind nfs
rpcinfo -p localhost    # 查看nfs服务是否安装正常

# 比如我们在服务端的共享目录为/nfs-share，接着输入以下命令配置共享目录, 并使共享目录生效
echo "/nfs-share *(rw,async,no_root_squash)" >> /etc/exports
exportfs -r
systemctl restart rpcbind nfs

# 测试， 若不在一个VPC下，需开放： 111(TCP|UDP) 、20048(TCP|UDP) 、2049(TCP)
showmount -e localhost  或者  showmount -e 公网ip
```

#### 10.2 客户端

```shell
yum install nfs-utils rpcbind -y
systemctl start rpcbind nfs
systemctl enable rpcbind nfs

# 命令挂载
mount -t nfs IP:/nfs-share /client-dir
# /etc/fstab
IP:/nfs-share      /client-dir       nfs     default 0       0
```

### 11. StorageClass

#### 11.1 流程

1. 创建一个可用的NFS Serve
2. 创建Service Account.这是用来管控NFS provisioner在k8s集群中运行的权限
3. 创建StorageClass.负责建立PVC并调用NFS provisioner进行预定的工作,并让PV与PVC建立管理
4. 创建NFS provisioner.有两个功能,一个是在NFS共享目录下创建挂载点(volume),另一个则是建了PV并将PV与NFS的挂载点建立关联

#### 11.2 访问模式和回收策略

* 访问模式

  ```shell
  RWO  ReadWriteOnce -- 该volume只能被单个节点以读写的方式映射
  ROX  ReadOnlyMany -- 该volume可以被多个节点以只读方式映射
  RWX  ReadWriteMany -- 该volume可以被多个节点以读写的方式映射
  ```
* 回收策略

  ```shell
  Retain：保留，需要手动回收 < 推荐这种模式 >
  Recycle：回收，自动删除卷中数据
  Delete：删除，相关联的存储资产，如AWS EBS，GCE PD，Azure Disk，or OpenStack Cinder卷都会被删除
  ```
* 状态

  ```shell
  Available：  空闲的资源，未绑定给PVC
  Bound：      绑定给了某个PVC
  Released：   PVC已经删除了，但是PV还没有被集群回收
  Failed：     PV在自动回收中失败了
  命令行可以显示PV绑定的PVC名称。
  ```

#### 11.3 NFS 静态方式 PV

> 集群管理员创建多个PV，它们携带着真实存储的详细信息，这些存储对于集群用户是可用的。它们存在于Kubernetes API中，并可用于存储使用。

```yaml
# 环境： kubernetes 1.18.3, 例如文件： test.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mytest
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv                      # PV 不需要绑定名称空间
  labels:
    pv: nfs-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany                # 访问模式
  persistentVolumeReclaimPolicy: Retain   # 回收策略
  storageClassName: nfs            # storageClassName 定义的名字，为了pvc匹配
  nfs:
    path: /nfs/data                # NFS 共享目录
    server: 192.168.1.33           # NFS 共享地址

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
  namespace: mytest
spec:
  storageClassName: nfs              # 对应上面创建 PV 的 storageClassName名字
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi


---
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  namespace: mytest
spec:
  containers:
    - name: nginx
      image: nginx
      volumeMounts:
      - mountPath: /usr/share/nginx/html
        name: nfs-pv                        # 对应上面创建 PV 的名字
  volumes:
    - name: nfs-pv                          # 对应上面创建 PV 的名字
      persistentVolumeClaim:
        claimName: nfs-pvc                  # 对应上面创建 PVC 的名字
```

````shell
kubectl apply -f test.yaml
kubectl get pv,pvc,pods -n mytest
​```
NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
persistentvolume/nfs-pv   1Gi        RWX            Retain           Bound    mytest/nfs-pvc   nfs                     16m

NAME                            STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/nfs-pvc   Bound    nfs-pv   1Gi        RWX            nfs            16m

NAME        READY   STATUS    RESTARTS   AGE
pod/mypod   1/1     Running   0          16m

​```
# 共享目录中创建 index.html 文件，查看 Pod 中是否存在
kubectl exec -it mypod -n mytest -- more /usr/share/nginx/html/index.html
````

#### 11.4 NFS 动态方式 PV

> 当管理员创建的静态PV都不匹配用户的PVC时，集群可能会尝试专门地供给volume给PVC。这种供给基于StorageClass。
>
> **参考： <https://www.jianshu.com/p/15260a66cd41>**

```shell
# 所有work节点安装 nfs-utils rpcbind
yum install nfs-utils rpcbind -y
systemctl start nfs rpcbind
systemctl enable nfs rpcbind
```

**11.4-1 创建RBAC授权**

```shell
wget https://raw.githubusercontent.com/kubernetes-incubator/external-storage/master/nfs-client/deploy/rbac.yaml
kubectl apply -f rbac.yaml
```

**11.4-2 创建 Storageclass**

```yaml
# cat class.yaml  ---> kubectl apply -f storageclass-nfs.yaml 

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"
```

**11.4-3 创建nfs-client-provisioner自动配置程序，以便自动创建持久卷(PV)**

> 自动创建的 PV 以 {namespace}-${pvcName}-${pvName} 的命名格式创建在 NFS 上 当这个 PV 被回收后会以 archieved-{namespace}-${pvcName}-${pvName} 的命名格式存在 NFS 服务器上

```yaml
# cat deployment.yaml  ---> kubectl apply -f deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  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: 192.168.1.33
            - name: NFS_PATH
              value: /nfs/data
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.1.33
            path: /nfs/data
```

**11.4-4 创建一个有状态应用**

```yaml
# cat statefulset-nfs.yaml    ---> kubectl apply -f statefulset-nfs.yaml

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: nfs-web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nfs-web # has to match .spec.template.metadata.labels
  template:
    metadata:
      labels:
        app: nfs-web
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
      annotations:
        volume.beta.kubernetes.io/storage-class: managed-nfs-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
```

* 查看 Pod/PV/PVC

  ```shell
  kubectl get pods
  kubectl get pvc,pv
  # 查看 nfs server 目录中信息，同时各子目录中内容为空
  ls -l /data/nfs/
  ```

**11.4-5 破坏性测试**

```shell
# 在每个 pod 中写入内容
for i in 0 1 2; do kubectl exec nfs-web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done

# 远程nfs各子目录中不再为空，出现了内容
ls /data/nfs/default-www-nfs-web-0-pvc-62f4868f-c6f7-459e-a280-26010c3a5849/

# 查看每个容器中内容，均为各自主机名
for i in 0 1 2; do kubectl exec -it nfs-web-$i -- cat /usr/share/nginx/html/index.html; done
nfs-web-0
nfs-web-1
nfs-web-2

# 删除对应 pod
kubectl get pod -l app=nfs-web
kubectl delete pod -l app=nfs-web

# 再次查看，会发现会自动创建
kubectl get pod -l app=nfs-web   

# 再次查看每个pod中内容，可以看到文件内容没有变化
for i in 0 1 2; do kubectl exec -it nfs-web-$i -- cat /usr/share/nginx/html/index.html; done
nfs-web-0
nfs-web-1
nfs-web-2
```

**11.4-6 结束语**

> **可以看到， statefulset 控制器通过固定的 pod 创建顺序可以确保 pod 之间的拓扑关系一直处于稳定不变的状态，通过 nfs-client-provisioner 自动创建和每个 pod 有固定对应关系的远程存储卷，确保 pod 重建后数据不会丢失。**

## 第四部分 部署统一日志管理

## 第五部分 部署性能监控平台

## 第六部分 搭建高可用的K8S集群

## 第七部分 K8S集群部署项目

## 第八部分 Ceph 集群

| IP             | 主机名        |
| -------------- | ---------- |
| 192.168.110.59 | cephnode01 |
| 192.168.110.62 | cephnode02 |
| 192.168.110.64 | cephnode03 |

### 1、Cepy-deploy 部署

1、初始化(所有主机)

```shell
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
setenforce 0
systemctl disable firewalld
systemctl stop firewalld
yum -y install ntp
systemctl enable ntpd
systemctl start ntpd
systemctl disable chronyd
ntpq -p


yum install wget -y
mkdir /tmp/repo.bak && mv /etc/yum.repos.d/*.repo /tmp/repo.bak
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -P /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

cat > /etc/yum.repos.d/ceph.repo << EOF
[ceph]
name=ceph
baseurl=http://mirrors.aliyun.com/ceph/rpm-luminous/el7/x86_64/
gpgcheck=0

[ceph-noarch]
name=cephnoarch
baseurl=http://mirrors.aliyun.com/ceph/rpm-luminous/el7/noarch/
gpgcheck=0

[ceph-source]
name=ceph-source
baseurl=http://mirrors.aliyun.com/ceph/rpm-luminous/el7/SRPMS/
gpgcheck=0
EOF

yum clean all
yum makecache
yum -y install python-setuptools
```

2、主机免密

```shell
# cephnode01 执行，作为管理控制节点
ssh-keygen -t rsa -P ""
ssh-copy-id 192.168.110.59
ssh-copy-id 192.168.110.62
ssh-copy-id 192.168.110.64
# 设置 hostname
hostnamectl set-hostname <hostname>
# 拷贝 hosts 到远程
echo "192.168.110.59 cephnode01" >> /etc/hosts
echo "192.168.110.62 cephnode02" >> /etc/hosts
echo "192.168.110.64 cephnode03" >> /etc/hosts

yum -y install ceph-deploy
ceph-deploy --version (必须是 2.0.1 + )
```

3、创建一个my-cluster目录，所有命令在此目录下进行（文件位置和名字可以随意）

```shell
mkdir /my-cluster
cd /my-cluster
```

4、创建一个Ceph集群，（hosts里的解析名和主机名一定要想相同，否则报错）

```shell
ceph-deploy new cephnode01 cephnode02 cephnode03 
```

5、安装Ceph软件（每个节点执行）

```shell
yum -y install epel-release
yum install -y ceph
# 或者管理节点执行： ceph-deploy install cephnode01 cephnode02 cephnode03 
```

6、初始化，并生成monitor检测集群所使用的的秘钥

```shell
ceph-deploy mon create-initial
```

7、安装Ceph CLI，方便执行一些管理命令

```shell
ceph-deploy admin cephnode01 cephnode02 cephnode03
```

8、配置mgr，用于管理集群

```shell
ceph-deploy mgr create cephnode01 cephnode02 cephnode03
```

9、部署rgw

```shell
yum install -y ceph-radosgw
ceph-deploy rgw create cephnode01
```

10、部署MDS（CephFS）

```shell
ceph-deploy mds create cephnode01 cephnode02 cephnode03 
```

11、添加osd

```shell
ceph-deploy osd create --data /dev/sdb node1
ceph-deploy osd create --data /dev/sdb node2
ceph-deploy osd create --data /dev/sdb node3
# 注意 如果要在LVM卷上创建OSD，则参数 --data 必须是volume_group/lv_name，而不是卷的块设备的路径。
```

12、检查集群健康（node1）

```shell
[root@localhost my-cluster]# ceph health
HEALTH_OK

[root@localhost my-cluster]# ceph -s
  cluster:
    id:     0e3f5736-d67b-47af-bac7-7146f7c7a8b5
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum cephnode03,cephnode02,cephnode01
    mgr: cephnode01(active), standbys: cephnode02, cephnode03
    osd: 3 osds: 3 up, 3 in
    rgw: 1 daemon active
 
  data:
    pools:   4 pools, 32 pgs
    objects: 219 objects, 1.09KiB
    usage:   3.00GiB used, 27.0GiB / 30.0GiB avail
    pgs:     32 active+clean
 
```

### 2、ceph.conf

1、该配置文件采用init文件语法，#和;为注释，ceph集群在启动的时候会按照顺序加载所有的conf配置文件。 配置文件分为以下几大块配置。

```shell
global：全局配置。
osd：osd专用配置，可以使用osd.N，来表示某一个OSD专用配置，N为osd的编号，如0、2、1等。
mon：mon专用配置，也可以使用mon.A来为某一个monitor节点做专用配置，其中A为该节点的名称，ceph-monitor-2、ceph-monitor-1等。使用命令 ceph mon dump可以获取节点的名称。
client：客户端专用配置。
```

2、配置文件可以从多个地方进行顺序加载，如果冲突将使用最新加载的配置，其加载顺序为。

```shell
$CEPH_CONF环境变量
-c 指定的位置
/etc/ceph/ceph.conf
~/.ceph/ceph.conf
./ceph.conf
```

3、配置文件还可以使用一些元变量应用到配置文件，如。

```shell
$cluster：当前集群名。
$type：当前服务类型。
$id：进程的标识符。
$host：守护进程所在的主机名。
$name：值为$type.$id。
```

4、ceph.conf详细参数

```shell
[global]#全局设置
fsid = xxxxxxxxxxxxxxx                           #集群标识ID 
mon host = 10.0.1.1,10.0.1.2,10.0.1.3            #monitor IP 地址
auth cluster required = cephx                    #集群认证
auth service required = cephx                           #服务认证
auth client required = cephx                            #客户端认证
osd pool default size = 3                             #最小副本数 默认是3
osd pool default min size = 1                           #PG 处于 degraded 状态不影响其 IO 能力,min_size是一个PG能接受IO的最小副本数
public network = 10.0.1.0/24                            #公共网络(monitorIP段) 
cluster network = 10.0.2.0/24                           #集群网络
max open files = 131072                                 #默认0#如果设置了该选项，Ceph会设置系统的max open fds
mon initial members = node1, node2, node3               #初始monitor (由创建monitor命令而定)
##############################################################
[mon]
mon data = /var/lib/ceph/mon/ceph-$id
mon clock drift allowed = 1                             #默认值0.05#monitor间的clock drift
mon osd min down reporters = 13                         #默认值1#向monitor报告down的最小OSD数
mon osd down out interval = 600      #默认值300      #标记一个OSD状态为down和out之前ceph等待的秒数
##############################################################
[osd]
osd data = /var/lib/ceph/osd/ceph-$id
osd mkfs type = xfs                                     #格式化系统类型
osd max write size = 512 #默认值90                   #OSD一次可写入的最大值(MB)
osd client message size cap = 2147483648 #默认值100    #客户端允许在内存中的最大数据(bytes)
osd deep scrub stride = 131072 #默认值524288         #在Deep Scrub时候允许读取的字节数(bytes)
osd op threads = 16 #默认值2                         #并发文件系统操作数
osd disk threads = 4 #默认值1                        #OSD密集型操作例如恢复和Scrubbing时的线程
osd map cache size = 1024 #默认值500                 #保留OSD Map的缓存(MB)
osd map cache bl size = 128 #默认值50                #OSD进程在内存中的OSD Map缓存(MB)
osd mount options xfs = "rw,noexec,nodev,noatime,nodiratime,nobarrier" #默认值rw,noatime,inode64  #Ceph OSD xfs Mount选项
osd recovery op priority = 2 #默认值10              #恢复操作优先级，取值1-63，值越高占用资源越高
osd recovery max active = 10 #默认值15              #同一时间内活跃的恢复请求数 
osd max backfills = 4  #默认值10                  #一个OSD允许的最大backfills数
osd min pg log entries = 30000 #默认值3000           #修建PGLog是保留的最大PGLog数
osd max pg log entries = 100000 #默认值10000         #修建PGLog是保留的最大PGLog数
osd mon heartbeat interval = 40 #默认值30            #OSD ping一个monitor的时间间隔（默认30s）
ms dispatch throttle bytes = 1048576000 #默认值 104857600 #等待派遣的最大消息数
objecter inflight ops = 819200 #默认值1024           #客户端流控，允许的最大未发送io请求数，超过阀值会堵塞应用io，为0表示不受限
osd op log threshold = 50 #默认值5                  #一次显示多少操作的log
osd crush chooseleaf type = 0 #默认值为1              #CRUSH规则用到chooseleaf时的bucket的类型
##############################################################
[client]
rbd cache = true #默认值 true      #RBD缓存
rbd cache size = 335544320 #默认值33554432           #RBD缓存大小(bytes)
rbd cache max dirty = 134217728 #默认值25165824      #缓存为write-back时允许的最大dirty字节数(bytes)，如果为0，使用write-through
rbd cache max dirty age = 30 #默认值1                #在被刷新到存储盘前dirty数据存在缓存的时间(seconds)
rbd cache writethrough until flush = false #默认值true  #该选项是为了兼容linux-2.6.32之前的virtio驱动，避免因为不发送flush请求，数据不回写
              #设置该参数后，librbd会以writethrough的方式执行io，直到收到第一个flush请求，才切换为writeback方式。
rbd cache max dirty object = 2 #默认值0              #最大的Object对象数，默认为0，表示通过rbd cache size计算得到，librbd默认以4MB为单位对磁盘Image进行逻辑切分
      #每个chunk对象抽象为一个Object；librbd中以Object为单位来管理缓存，增大该值可以提升性能
rbd cache target dirty = 235544320 #默认值16777216    #开始执行回写过程的脏数据大小，不能超过 rbd_cache_max_dirty
```

### 扩展群集

> 启动并运行基本群集后，下一步是展开群集。添加Ceph元数据服务器node1。然后添加Ceph Monitor和Ceph Manager node2，node3以提高可靠性和可用性。添加元数据服务器：配置MDS服务（NODE1）要使用CephFS，您至少需要一个元数据服务器。执行以下操作以创建元数据服务器：

```shell
ceph-deploy mds create node1
```

### 添加监视器

> Ceph存储集群需要至少运行一个Ceph Monitor和Ceph Manager。为了实现高可用性，Ceph存储集群通常运行多个Ceph监视器，因此单个Ceph监视器的故障不会导致Ceph存储集群崩溃。Ceph使用Paxos算法，该算法需要大多数监视器（即，大于N / 2，其中N是监视器的数量）才能形成法定人数。虽然这不是必需的，但监视器的数量往往更好。

```shell
# 将两个Ceph监视器添加到您的群集：
ceph-deploy mon add node2 node3

# 一旦你添加了新的Ceph监视器，Ceph将开始同步监视器并形成一个法定人数。您可以通过执行以下操作来检查仲裁状态：
ceph quorum_status --format json-pretty

# 注意： 当您使用多个监视器运行Ceph时，您应该在每个监视器主机上安装和配置NTP， 确保监视器是NTP对等方。
```

### 添加管理员

> Ceph Manager守护进程以活动/备用模式运行。部署其他管理器守护程序可确保在一个守护程序或主机发生故障时，另一个守护程序或主机可以在不中断服务的情况下接管。

```shell
# 要部署其他管理器守护程序：
ceph-deploy mgr create node2 node3

# 删除OSD（node1）
ceph auth del osd.0
ceph osd rm 0
```

### 配置DashBoard

```shell
# 创建域管理秘钥（node1）
ceph auth get-or-create mgr.node1 mon 'allow profile mgr' osd 'allow *' mds 'allow *'

# 开启ceph-mgr管理域（node1）
ceph-mgr -i node1

# 打开dashboard模块（node1）
ceph mgr module enable dashboard

# 绑定开启dashboard模块的ceph-mgr节点的ip地址（node1主机）
ceph config-key set mgr/dashboard/node1/server_addr 192.168.110.59

# 访问Dashboard (任意主机)
http://192.168.110.59:7000
```

### 常用命令整理

#### 创建 Pool 池

```shell
# 注意：创建了池后，无法减少PG的数量，只能增加
[root@localhost ~]# ceph osd pool create testPool 16 16
pool 'testPool' created
```

#### 为 池 启用ceph应用,并指定池类型

```shell
# <rbd(块) |cephfs(文件) |object(对象)>
[root@localhost ~]# ceph osd pool application enable testPool rbd
enabled application 'testPool' on pool 'testPool'
```

#### 查看所有池， 或者命令： rados lspools

```shell
[root@localhost ~]# ceph osd pool ls                                           
.rgw.root
default.rgw.control
default.rgw.meta
default.rgw.log
pool01
testPool
```

#### 查看所有池的详细信息

```shell
[root@localhost ~]# ceph osd pool ls detail
pool 1 '.rgw.root' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 8 pgp_num 8 last_change 3 flags hashpspool stripe_width 0 application rgw
pool 2 'default.rgw.control' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 8 pgp_num 8 last_change 17 flags hashpspool stripe_width 0 application rgw
pool 3 'default.rgw.meta' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 8 pgp_num 8 last_change 19 flags hashpspool stripe_width 0 application rgw
pool 4 'default.rgw.log' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 8 pgp_num 8 last_change 21 flags hashpspool stripe_width 0 application rgw
pool 9 'pool01' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 16 pgp_num 16 last_change 57 flags hashpspool stripe_width 0 application pool01
        removed_snaps [1~3]
pool 11 'testPool' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 16 pgp_num 16 last_change 68 flags hashpspool stripe_width 0 application rbd

```

#### 修改池名字

```shell
[root@localhost ~]# ceph osd pool rename testPool testPool1
pool 'testPool' renamed to 'testPool1'
```

#### 设置 池 配额，限制对象数量

```shell
[root@localhost ~]# ceph osd pool set-quota testPool max_objects 1024
set-quota max_objects = 1024 for pool testPool
```

#### 设置 池 配额，限制容量数量

```shell
[root@localhost ~]# ceph osd pool set-quota testPool max_bytes 1024
set-quota max_bytes = 1024 for pool testPool
```

#### 取消配额，max\_objects 一样

```shell
[root@localhost ~]# ceph osd pool set-quota testPool max_bytes 0              
set-quota max_bytes = 0 for pool testPool
```

#### 查看池 状态，

```shell
[root@localhost ~]# ceph osd pool stats                                       
pool .rgw.root id 1
  nothing is going on
  
查看某个池： ceph osd pool stats testPool
```

#### 创建一个块设备

```shell
[root@localhost ~]# rbd create --size 1024 block -p testPool 
```

#### 查看池中，有哪些块设备

```shell
[root@localhost ~]# rbd ls -p testPool                                         
block
```

#### 查看块设备详细信息

```shell
[root@localhost ~]# rbd info block -p testPool                                 
rbd image 'block':
        size 2GiB in 512 objects
        order 22 (4MiB objects)
        block_name_prefix: rbd_data.122616b8b4567
        format: 2
        features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
        flags: 
        create_timestamp: Sat Apr 17 22:08:50 2021
```

#### 查看池使用情况

```shell
[root@localhost ~]# ceph df                                                   
GLOBAL:
    SIZE        AVAIL       RAW USED     %RAW USED 
    30.0GiB     27.0GiB      3.01GiB         10.03 
POOLS:
    NAME                    ID     USED        %USED     MAX AVAIL     OBJECTS 
    .rgw.root               1      1.09KiB         0       8.49GiB           4 
    default.rgw.control     2           0B         0       8.49GiB           8 
    default.rgw.meta        3           0B         0       8.49GiB           0 
    default.rgw.log         4           0B         0       8.49GiB         207 
    pool01                  9         232B         0       8.49GiB           6 
    testPool                10        134B         0       8.49GiB           5 
```

#### 查看集群空间使用情况

```shell
[root@localhost ~]# ceph osd df 
ID CLASS WEIGHT  REWEIGHT SIZE    USE     DATA    OMAP META AVAIL   %USE  VAR  PGS 
 0   ssd 0.00980  1.00000 10.0GiB 1.00GiB 2.48MiB   0B 1GiB 8.99GiB 10.03 1.00  64 
 1   ssd 0.00980  1.00000 10.0GiB 1.00GiB 2.48MiB   0B 1GiB 8.99GiB 10.03 1.00  64 
 2   ssd 0.00980  1.00000 10.0GiB 1.00GiB 2.48MiB   0B 1GiB 8.99GiB 10.03 1.00  64 
                    TOTAL 30.0GiB 3.01GiB 7.45MiB   0B 3GiB 27.0GiB 10.03          
MIN/MAX VAR: 1.00/1.00  STDDEV: 0
```

\###　查看池属性

```shell
[root@localhost ~]# ceph osd pool get testPool all                           
size: 3
min_size: 2
crash_replay_interval: 0
pg_num: 16
pgp_num: 16
crush_rule: replicated_rule
hashpspool: true
nodelete: false
nopgchange: false
nosizechange: false
write_fadvise_dontneed: false
noscrub: false
nodeep-scrub: false
use_gmt_hitset: 1
auid: 0
fast_read: 0
```

### 对接 k8s 集群：Rbd 持久化存储 <已验证>

> **准备工作： 创建pod时，kubelet需要使用rbd命令去检测和挂载pv对应的ceph image，所以要在所有的worker节点安装ceph客户端ceph-common。 将ceph的ceph.client.admin.keyring和ceph.conf文件拷贝到master的/etc/ceph目录下**

```shell
yum -y install ceph-common
```

1、Ceph集群中，创建一个 pool 池，为后面k8s接入使用

```shell
ceph osd pool create kube 128 128 
ceph osd pool ls
```

2、首先得在kubernetes集群中安装 **rbd-provisioner**，github仓库链接<https://github.com/kubernetes-incubator/external-storage>

```shell
# 根据自己需要，修改rbd-provisioner的namespace；
yum install git -y
git clone https://github.com/kubernetes-retired/external-storage.git
cd external-storage/ceph/rbd/deploy
export NAMESPACE=kube-system
sed -r -i "s/namespace: [^ ]+/namespace: $NAMESPACE/g" ./rbac/clusterrolebinding.yaml ./rbac/rolebinding.yaml
kubectl -n $NAMESPACE apply -f ./rbac

# 部署完成后检查rbd-provisioner deployment，确保已经正常部署；
kubectl get deploy -n kube-system
kubectl describe deployments.apps -n kube-system rbd-provisioner
```

3、创建storageclass

> **创建secret保存client.admin和client.kube用户的key，client.admin和client.kube用户的secret可以放在kube-system namespace，但如果其他namespace需要使用ceph rbd的dynamic provisioning功能的话，要在相应的namespace创建secret来保存client.kube用户key信息**
>
> ```shell
> # 部署完rbd-provisioner，还需要创建StorageClass。创建SC前，我们还需要创建相关用户的secret；
> [root@k8s01 ~]# vi secrets.yaml
> apiVersion: v1
> kind: Secret
> metadata:
>  name: ceph-admin-secret
>  namespace: kube-system
> type: "kubernetes.io/rbd"
> data:
>  # ceph auth get-key client.admin | base64
>  key: QVFCdng4QmJKQkFsSFJBQWl1c1o0TGdOV250NlpKQ1BSMHFCa1E9PQ==
> ---
> apiVersion: v1
> kind: Secret
> metadata:
>  name: ceph-secret
>  namespace: kube-system
> type: "kubernetes.io/rbd"
> data:
>  # ceph auth add client.kube mon 'allow r' osd 'allow rwx pool=kube'
>  # ceph auth get-key client.kube | base64
>  key: QVFCTHdNRmJueFZ4TUJBQTZjd1MybEJ2Q0JUcmZhRk4yL2tJQVE9PQ==
>
> [root@k8s01 ~]#  kubectl create -f secrets.yaml
>
> [root@k8s01 ~]# vi secrets-default.yaml
> apiVersion: v1
> kind: Secret
> metadata:
>   name: ceph-secret
> type: "kubernetes.io/rbd"
> data:
>   # ceph auth add client.kube mon 'allow r' osd 'allow rwx pool=kube'
>   # ceph auth get-key client.kube | base64
>   key: QVFCTHdNRmJueFZ4TUJBQTZjd1MybEJ2Q0JUcmZhRk4yL2tJQVE9PQ==
> ```
>
> * 其他设置和普通的ceph rbd StorageClass一致，但provisioner需要设置为\*\*`ceph.com/rbd`\*\*，不是默认的`kubernetes.io/rbd`，这样rbd的请求将由rbd-provisioner来处理；
> * 考虑到兼容性，建议尽量关闭rbd image feature，并且kubelet节点的ceph-common版本尽量和ceph服务器端保持一致；
>
> ```yaml
> [root@k8s01 ~]# vi ceph-rbd-sc.yaml
> apiVersion: storage.k8s.io/v1beta1
> kind: StorageClass
> metadata:
>   name: ceph-rbd
>   annotations:
>      storageclass.beta.kubernetes.io/is-default-class: "true"
> provisioner: ceph.com/rbd
> parameters:
>   monitors: 10.10.181.249:6789,10.10.181.248:6789,10.10.181.247:6789
>   adminId: admin
>   adminSecretName: ceph-admin-secret
>   adminSecretNamespace: kube-system
>   pool: kube
>   userId: kube
>   userSecretName: ceph-secret
>   fsType: ext4
>   imageFormat: "2"
>   imageFeatures: "layering"
>
> [root@k8s01 ~]#  kubectl create -f  ceph-rbd-sc.yaml
> ```

4、测试ceph rbd自动分配

> **在kube-system和default namespace分别创建pod，通过启动一个busybox实例，将ceph rbd镜像挂载到`/usr/share/busybox`；**
>
> ```yaml
> [root@k8s01 ~]# vi test-pod.yaml
> apiVersion: v1
> kind: Pod
> metadata:
>   name: ceph-pod1
> spec:
>   containers:
>   - name: ceph-busybox
>     image: busybox
>     command: ["sleep", "60000"]
>     volumeMounts:
>       - name: ceph-vol1
>         mountPath: /usr/share/busybox
>         readOnly: false
>   volumes:
>   - name: ceph-vol1
>     persistentVolumeClaim:
>       claimName: ceph-claim
> ---
> kind: PersistentVolumeClaim
> apiVersion: v1
> metadata:
>   name: ceph-claim
> spec:
>   accessModes:  
>     - ReadWriteOnce
>   resources:
>     requests:
>       storage: 2Gi
>
> [root@k8s01 ~]# kubectl create -f test-pod.yaml -n kube-system
> pod/ceph-pod1 created
> persistentvolumeclaim/ceph-claim created
>
> # < 使用默认空间，会使用刚才创建的 secrets-default.yaml，惹其他空间，则创建其他空间秘钥 >
> [root@k8s01 ~]# kubectl create -f test-pod.yaml -n default 
> pod/ceph-pod1 created
> persistentvolumeclaim/ceph-claim created
> ```
>
> #### 检查pv和pvc的创建状态，是否都已经创建；
>
> ```shell
> [root@k8s01 ~]# kubectl get pvc,pv -n kube-system
> NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
> ceph-claim   Bound    pvc-ee0f1c35-cef7-11e8-8484-005056a33f16   2Gi        RWO            ceph-rbd       25s
>
> NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   REASON   AGE
> pvc-ea377cad-cef7-11e8-8484-005056a33f16   2Gi        RWO            Delete           Bound    kube-system/ceph-claim   ceph-rbd                40s
>
> ```
>
> #### 在ceph服务器上，检查rbd镜像创建情况和镜像的信息；
>
> ```shell
> [root@k8s01 ~]# rbd ls --pool kube
> kubernetes-dynamic-pvc-ea390cbf-cef7-11e8-aa22-0a580af40202
>
> [root@k8s01 ~]# rbd info kube/kubernetes-dynamic-pvc-ea390cbf-cef7-11e8-aa22-0a580af40202
> rbd image 'kubernetes-dynamic-pvc-ea390cbf-cef7-11e8-aa22-0a580af40202':
>     size 2048 MB in 512 objects
>     order 22 (4096 kB objects)
>     block_name_prefix: rbd_data.456876b8b4567
>     format: 2
>     features: layering
>     flags:
>     create_timestamp: Sat Oct 13 22:54:41 2018
> ```
>
> #### 检查busybox内的文件系统挂载和使用情况，确认能正常工作；
>
> ```shell
> [root@k8s01 ~]# kubectl exec eph-pod1 -- mount |grep rbd
> /dev/rbd0 on /usr/share/busybox type ext4 (rw,seclabel,relatime,stripe=1024,data=ordered)
>
> [root@k8s01 ~]# kubectl exec -it ceph-pod1 df |grep rbd
> /dev/rbd0              1998672      6144   1976144   0% /usr/share/busybox
> ```
>
> #### 测试删除pod能否自动删除pv和pvc，生产环境中谨慎，设置好回收策略；
>
> ```shell
> [root@k8s01 ~]# kubectl delete -f test-pod.yaml
> pod "ceph-pod1" deleted
> persistentvolumeclaim "ceph-claim" deleted
> [root@k8s01 ~]# kubectl get pv
> No resources found.
> [root@k8s01 ~]# kubectl get pvc
> No resources found.
>
> ```
>
> #### ceph服务器上的rbd image也已清除，自动回收成功；
>
> ```bash
> rbd ls -p kube
> ```
>
> #### 总结
>
> 大部分情况下，我们无需使用rbd provisioner来提供ceph rbd的dynamic provisioning能力。经测试，在OpenShift、Rancher、SUSE CaaS以及本Handbook的二进制文件方式部署，在安装好ceph-common软件包的情况下，定义StorageClass时使用`kubernetes.io/rbd`即可正常使用ceph rbd provisioning功能。
>
> 参考： <https://jimmysong.io/kubernetes-handbook/practice/rbd-provisioner.html>

### 对接k8s集群： Cephfs 持久化存储 <已验证>

> 准备工作所以work节点：
>
> ```shell
> yum -y install epel-release
> yum -y install ceph-common ceph-fuse
> ```
>
> \# **官方没有cephfs动态卷支持, 使用社区提供的cephfs-provisioner**
>
> ```shell
> kubectl create ns cephfs
> git clone https://github.com/kubernetes-retired/external-storage.git
> cd external-storage/ceph/cephfs/deploy
> NAMESPACE=cephfs
> sed -r -i "s/namespace: [^ ]+/namespace: $NAMESPACE/g" ./rbac/*.yaml
> sed -r -i "N;s/(name: PROVISIONER_SECRET_NAMESPACE.*\n[[:space:]]*)value:.*/\1value: $NAMESPACE/" ./rbac/deployment.yaml
> kubectl -n $NAMESPACE apply -f ./rbac
>
> #检查pod 的状态是否正常
> kubectl get pods -n cephfs
>      NAME                                  READY   STATUS    RESTARTS   AGE
>      cephfs-provisioner-7bf7d44886-wvjz8   1/1     Running   0          4h13m
> ```
>
> 配置 **storageclass** 及 **Pod 测试**
>
> ```yaml
> [root@k8s-master3 deploy]# cat storageclass-cephfs.yaml 
> apiVersion: v1
> kind: Secret
> metadata:
>   name: ceph-secret-fs
>   namespace: cephfs
> type: kubernetes.io/rbd
> data:
>   # ceph auth get-key client.admin | base64 || sed -n  '/key/p' ceph.client.admin.keyring |awk '{print $3}' |base64
>   key: QVFEenkzbGcyYlhrTHhBQVgvUlNPOEJXUldxQisxT1Q1MlIra2c9PQo=
> ---
> kind: StorageClass
> apiVersion: storage.k8s.io/v1
> metadata:
>   name: dynamic-cephfs
>   namespace: cephfs
> provisioner: ceph.com/cephfs
> parameters:
>     monitors: 10.10.181.249:6789,10.10.181.248:6789,10.10.181.247:6789
>     adminId: admin
>     adminSecretName: ceph-secret-fs
>     adminSecretNamespace: cephfs
> #    claimRoot: /volumes/kubernetes
> ---
> apiVersion: v1
> kind: PersistentVolumeClaim
> metadata:
>   name: cephfs-claim
>   namespace: cephfs
> spec:
>   accessModes:
>     - ReadWriteMany
>   storageClassName: dynamic-cephfs
>   resources:
>     requests:
>       storage: 1Gi
> ---
> kind: Pod
> apiVersion: v1
> metadata:
>   name: test-pod
>   namespace: cephfs
> spec:
>   containers:
>   - name: test-pod
>     image: ikubernetes/myapp:v4
>     volumeMounts:
>       - name: pvc
>         mountPath: "/data/cephfs"
>   volumes:
>     - name: pvc
>       persistentVolumeClaim:
>         claimName: cephfs-claim
> ```
>
> 查看是否挂载：
>
> ```shell
> [root@k8s-master3 deploy]# kubectl exec -it test-pod -n cephfs -- /bin/sh -c 'df -h |grep ceph-fuse'
> ceph-fuse                 3.6G         0      3.6G   0% /data/cephfs
> ```
>
> 验证：
>
> ```shell
> [root@k8s-master3 deploy]# kubectl exec -it test-pod -n cephfs -- /bin/sh -c 'echo cephfs-test > /data/cephfs/test.txt && more /data/cephfs/test.txt'
> cephfs-test
> ```
>
> ​ - provisioner 该字段指定使用存储卷类型为 kubernetes.io/rbd，注意 kubernetes.io/ 开头为 k8s 内部支持的存储提供者，不同的存储卷提供者类型这里要修改成对应的值。 ​ - adminId | userId 这里需要指定两种 Ceph 角色 admin 和其他 user，admin 角色默认已经有了，其他 user 可以去 Ceph 集群创建一个并赋对应权限值，如果不创建，也可以都指定为 admin。 ​ - adminSecretName 为上边创建的 Ceph 管理员 admin 使用的 ceph-secret-admin。 ​ - adminSecretNamespace 管理员 secret 使用的命名空间，默认 default，如果修改为其他的话，需要修改 ceph-secret- admin.yaml 增加 namespace: other-namespace。

## 第九部分 常见问题

### 技巧：无法删除 PV，PVC ？

```shell
# 系统内有一个已经不再使用的 PV ，已经删除了与其关联的 Pod 及 PVC ，并对其执行了删除命令，但是无法正常删除，一直出于如下状态：
kubectl get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM                                    STORAGECLASS          REASON   AGE
pv-nfs-gysl   1Gi        RWO            Recycle          Terminating   default/www-vct-statefulset-pvc-gysl-0   managed-nfs-storage            22h


###  解决方法: 更新一个资源的 field(s)
kubectl patch pv pv-nfs-gysl -p '{"metadata":{"finalizers":null}}'
persistentvolume/pv-nfs-gysl patched
kubectl get pv

```

### 技巧： 无法删除 Pod ？

```shell
kubectl delete pod <Podname> -n <namespace> --force --grace-period=0

# 暴力删除
  # 删除default namespace下的pod名为pod-to-be-deleted-0
    ETCDCTL_API=3 etcdctl del /registry/pods/default/pod-to-be-deleted-0
    # 删除需要删除的NAMESPACE
    etcdctl del /registry/namespaces/NAMESPACENAME</pre>
```

### 技巧： kubeadm 如何删除节点？

```shell
# 1）假设我们需要删除 k8s-node1 这个节点，首先在 master 节点上依次执行以下两个命令：
kubectl drain k8s-node1 --delete-local-data --force --ignore-daemonsets
kubectl delete node  k8s-node1
# 执行后通过 kubectl get node 命令可以看到 k8s-node1 已被成功删除：
# 2）接着在 k8s-node1 这个 Node 节点上执行如下命令，这样该节点即完全从 Cluster 中脱离开来：
kubeadm reset

```

### 技巧：如何重启Pod？

#### Deployment 方式下重启 Pod

```shell
kubectl delete pod {podname} -n {namespace}        # 直接删除pod，让k8s去完成 pod 重建
# 但是如果ReplicaSet 管理的 Pod 对象很多的话，那么要一个个手动删除，会很麻烦，所以可以使用命令来删除 ReplicaSet，
kubectl delete replicaset {rs_name} -n {namespace}

# 也可以通过调整 ReplicaSet 的数量来实现重启，比如先 scale 到 0，然后再 scale 到 1，那么 Pod 也不得不重启，
kubectl scale deployment {deployment} --replicas=0 -n {namespace}
kubectl scale deployment {deployment} --replicas=1 -n {namespace}
```

#### 使用 YAML 文件重启Pod

```shell
# 当我们有 Pod 的 yaml 文件是，可以直接使用 replace 命令，来强制替换 Pod 的API对象，从而实现重启的效果：
kubectl replace --force -f youpod.yaml

# 如果你手头没有 yaml 文件，直接使用的 Pod 对象，我们可以稍微调整一下上面的命令，如下：
kubectl get pod {podname} -n {namespace} -o yaml | kubectl replace --force -f -

```

### 莫名被打上污点？

此外,kubernetes 1.6引入了对节点问题的展示.也就是说当满足了特定条件,节点控制器会自动为符合条件的节点添加`taint`,以下是一些内置的`taint`

* **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**

### kubeadm安装的K8s忘记了token或者token过期了怎么办？

> 例如： 主机名：k8s-master IP地址： 192.168.1.136 端口号： 6443

#### k8s忘记token？

```bash
# 列出token ---> 获取CA公钥的哈希值 ---> 加入集群
TOKEN=$(kubeadm token list | awk -F" " '{print $1}' |tail -n 1)
HASH=$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //')
kubeadm join 192.168.1.136:6443 --token ${TOKEN} --discovery-token-ca-cert-hash sha256:${HASH}

```

#### token过期怎么加入k8s集群?

```bash
# 重新生成
kubeadm token create --print-join-command
kubeadm token list
```

### k8s.v1.20 NFS 无法动态创建pvc，Pending状态

> Using Kubernetes v1.20.0, getting "unexpected error getting claim reference: selfLink was empty, can't make reference" #25
>
> 首先nfs正常状态下，并且可以挂载

```yaml
# 测试创建pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  storageClassName: nfs-provisioner
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 20Gi
```

必须添加 `--feature-gates=RemoveSelfLink=false` 在 `/etc/kubernetes/manifests/kube-apiserver.yaml` 文件内

```yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --feature-gates=RemoveSelfLink=false
```
