Kubernets集群部署(二进制包编译方式)

参考文章:https://www.cnblogs.com/chiangchou/p/k8s-1.html

一、简介

1、Kubernetes 是什么

Kubernetes 是一个全新的基于容器技术的分布式架构解决方案,是 Google 开源的一个容器集群管理系统,Kubernetes 简称 K8S。

Kubernetes 是一个一站式的完备的分布式系统开发和支撑平台,更是一个开放平台,对现有的编程语言、编程框架、中间件没有任何侵入性。

Kubernetes 提供了完善的管理工具,这些工具涵盖了开发、部署测试、运维监控在内的各个环节。

Kubernetes 具有完备的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制、多粒度的资源配额管理能力。

Kubernetes 官方文档:https://kubernetes.io/zh/

2、Kubernetes 特性

① 自我修复

在节点故障时,重新启动失败的容器,替换和重新部署,保证预期的副本数量;杀死健康检查失败的容器,并且在未准备好之前不会处理用户的请求,确保线上服务不中断。

② 弹性伸缩

使用命令、UI或者基于CPU使用情况自动快速扩容和缩容应用程序实例,保证应用业务高峰并发时的高可用性;业务低峰时回收资源,以最小成本运行服务。

③ 自动部署和回滚

K8S采用滚动更新策略更新应用,一次更新一个Pod,而不是同时删除所有Pod,如果更新过程中出现问题,将回滚更改,确保升级不影响业务。

④ 服务发现和负载均衡

K8S为多个容器提供一个统一访问入口(内部IP地址和一个DNS名称),并且负载均衡关联的所有容器,使得用户无需考虑容器IP问题。

⑤ 机密和配置管理

管理机密数据和应用程序配置,而不需要把敏感数据暴露在镜像里,提高敏感数据安全性。并可以将一些常用的配置存储在K8S中,方便应用程序使用。

⑥ 存储编排

挂载外部存储系统,无论是来自本地存储,公有云,还是网络存储,都作为集群资源的一部分使用,极大提高存储使用灵活性。

⑦ 批处理

提供一次性任务,定时任务;满足批量数据处理和分析的场景。

二、集群架构与组件

Kubernetes 集群架构以及相关的核心组件如下图所示:一个 Kubernetes 集群一般包含一个 Master 节点和多个 Node 节点,一个节点可以看成是一台物理机或虚拟机。


1、Master

Master 是 K8S 的集群控制节点,每个 K8S 集群里需要有一个 Master 节点来负责整个集群的管理和控制,基本上 K8S 所有的控制命令都是发给它,它来负责具体的执行过程。Master 节点通常会占据一个独立的服务器,因为它太重要了,如果它不可用,那么所有的控制命令都将失效。

Master 节点上运行着以下关键组件:

kube-apiserver

是集群的统一入口,各组件协调者,以 HTTP Rest 提供接口服务,所有对象资源的增、删、改、查和监听操作都交给 apiserver 处理后再提交给 Etcd 存储。

kube-controller-manager

是 K8S 里所有资源对象的自动化控制中心,处理集群中常规后台任务,一个资源对应一个控制器,而 controller-manager 就是负责管理这些控制器的。

kube-scheduler

根据调度算法为新创建的 Pod 选择一个 Node 节点,可以任意部署,可以部署在同一个节点上,也可以部署在不同的节点上。

etcd

是一个分布式的,一致的 key-value 存储,主要用途是共享配置和服务发现,保存集群状态数据,比如 Pod、Service 等对象信息。

2、Node

除了 Master,K8S 集群中的其它机器被称为 Node 节点,Node 节点是 K8S 集群中的工作负载节点,每个 Node 都会被 Master 分配一些工作负载,当某个 Node 宕机时,其上的工作负载会被 Master 自动转移到其它节点上去。

每个 Node 节点上都运行着以下关键组件:

kubelet

kubelet 是 Master 在 Node 节点上的 Agent(代理),与 Master 密切协作,管理本机运行容器的生命周期,负责 Pod 对应的容器的创建、启停等任务,实现集群管理的基本功能。

kube-proxy

在 Node 节点上实现 Pod 网络代理,实现 Kubernetes Service 的通信,维护网络规则和四层负载均衡工作。

docker engine

Docker 引擎,负责本机的容器创建和管理工作。

Node 节点可以在运行期间动态增加到 K8S 集群中,前提是这个节点上已经正确安装、配置和启动了上述关键组件。在默认情况下 kubelet 会向 Master 注册自己,一旦 Node 被纳入集群管理范围,kubelet 就会定时向 Master 节点汇报自身的情况,例如操作系统、Docker 版本、机器的 CPU 和内存情况,以及之前有哪些 Pod 在运行等,这样 Master 可以获知每个 Node 的资源使用情况,并实现高效均衡的资源调度策略。而某个 Node 超过指定时间不上报信息时,会被 Master 判定为“失联”,Node 的状态被标记为不可用(Not Ready),随后 Master 会触发“工作负载大转移”的自动流程。

三、核心概念

1、Pod

Pod 是 K8S 中最重要也是最基本的概念,Pod 是最小的部署单元,是一组容器的集合。每个 Pod 都由一个特殊的根容器 Pause 容器,以及一个或多个紧密相关的用户业务容器组成。

Pause 容器作为 Pod 的根容器,以它的状态代表整个容器组的状态。K8S 为每个 Pod 都分配了唯一的 IP 地址,称之为 Pod IP。Pod 里的多个业务容器共享 Pause 容器的IP,共享 Pause 容器挂载的 Volume。

2、Label

标签,附加到某个资源上,用于关联对象、查询和筛选。一个 Label 是一个 key=value 的键值对,key 与 value 由用户自己指定。Label 可以附加到各种资源上,一个资源对象可以定义任意数量的 Label,同一个 Label 也可以被添加到任意数量的资源上。

我们可以通过给指定的资源对象捆绑一个或多个不同的 Label 来实现多维度的资源分组管理功能,以便于灵活、方便地进行资源分配、调度、配置、部署等工作。

K8S 通过 Label Selector(标签选择器)来查询和筛选拥有某些 Label 的资源对象。Label Selector 有基于等式( name=label1 )和基于集合( name in (label1, label2) )的两种方式。

3、ReplicaSet(RC)

ReplicaSet 用来确保预期的 Pod 副本数量,如果有过多的 Pod 副本在运行,系统就会停掉一些 Pod,否则系统就会再自动创建一些 Pod。

我们很少单独使用 ReplicaSet,它主要被 Deployment 这个更高层的资源对象使用,从而形成一整套 Pod 创建、删除、更新的编排机制。

4、Deployment

Deployment 用于部署无状态应用,Deployment 为 Pod 和 ReplicaSet 提供声明式更新,只需要在 Deployment 描述想要的目标状态,Deployment 就会将 Pod 和 ReplicaSet 的实际状态改变到目标状态。

5、Horizontal Pod Autoscaler(HPA)

HPA 为 Pod 横向自动扩容,也是 K8S 的一种资源对象。HPA 通过追踪分析 RC 的所有目标 Pod 的负载变化情况,来确定是否需要针对性调整目标 Pod 的副本数量。

6、Service

Service 定义了一个服务的访问入口,通过 Label Selector 与 Pod 副本集群之间“无缝对接”,定义了一组 Pod 的访问策略,防止 Pod 失联。

创建 Service 时,K8S会自动为它分配一个全局唯一的虚拟 IP 地址,即 Cluster IP。服务发现就是通过 Service 的 Name 和 Service 的 ClusterIP 地址做一个 DNS 域名映射来解决的。

7、Namespace

命名空间,Namespace 多用于实现多租户的资源隔离。Namespace 通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组。

K8S 集群在启动后,会创建一个名为 default 的 Namespace,如果不特别指明 Namespace,创建的 Pod、RC、Service 都将被创建到 default 下。

当我们给每个租户创建一个 Namespace 来实现多租户的资源隔离时,还可以结合 K8S 的资源配额管理,限定不同租户能占用的资源,例如 CPU 使用量、内存使用量等。

四、集群搭建 —— 平台规划

1、生产环境 K8S 平台规划

K8S 环境有两种架构方式,单 Master 集群和多 Master 集群,将先搭建起单 Master 集群,再扩展为多 Master 集群。开发、测试环境可以部署单 Master 集群,生产环境为了保证高可用需部署多 Master 集群。

① 单 Master 集群架构

单 Master 集群架构相比于多 Master 集群架构无法保证集群的高可用,因为 master 节点一旦宕机就无法进行集群的管理工作了。单 master 集群主要包含一台 Master 节点,及多个 Node 工作节点、多个 Etcd 数据库节点。

Etcd 是 K8S 集群的数据库,可以安装在任何地方,也可以与 Master 节点在同一台机器上,只要 K8S 能连通 Etcd。


② 多 Master 集群架构

多 Master 集群能保证集群的高可用,相比单 Master 架构,需要一个额外的负载均衡器来负载多个 Master 节点,Node 节点从连接 Master 改成连接 LB 负载均衡器


③ 集群规划

角色 IP 主机 组件
k8s-master-1 192.168.137.14 k8s-master-1 kube-apiserver,kube-controller-manager,kube-scheduler,etcd
k8s-master-1 192.168.137.15 k8s-master-2 kube-apiserver,kube-controller-manager,kube-scheduler
k8s-node-1 192.168.137.16 k8s-node-1 kubelet,kube-proxy,docker,etcd
k8s-node-1 192.168.137.17 k8s-node-2 kubelet,kube-proxy,docker,etcd
Load Balancer(master) 192.168.137.18 k8s-lb-master Nginx L4
Load Balancer(backup) 192.168.137.19 k8s-lb-backup Nginx L4

④ 服务器硬件配置推荐

测试环境与生产环境服务器配置推荐如下,本地虚拟机的配置将按照本地测试环境的配置来创建虚拟机。


2、操作系统初始化

接下来将基于二进制包的方式,手动部署每个组件,来组成 K8S 高可用集群。通过手动部署每个组件,一步步熟悉每个组件的配置、组件之间的通信等,深层次的理解和掌握 K8S。

首先做的是每台服务器的配置初始化,依次按如下步骤初始化。

① 关闭防火墙

systemctl stop firewalld
systemctl disable firewalld

② 关闭 selinux
临时生效 setenforce 0,永久生效 sed -i 's/enforcing/disabled/' /etc/selinux/config

③ 关闭 swap
临时关闭 swapoff -a
永久生效

vim /etc/fstab
#将 [/dev/mapper/centos-swap swap                    swap    defaults        0 0] 这一行注释掉

④ 添加 hosts

vim /etc/hosts

192.168.137.14 k8s-master-1
192.168.137.15 k8s-master-2
192.168.137.16 k8s-node-1
192.168.137.17 k8s-node-2
192.168.137.18 k8s-lb-master
192.168.137.19 k8s-lb-backup

⑤ 同步系统时间
各个节点之间需保持时间一致,因为自签证书是根据时间校验证书有效性,如果时间不一致,将校验不通过。

# #联网情况可使用如下命令
# ntpdate time.windows.com

# #如果不能联外网可使用 date 命令设置时间

五、集群搭建 —— 部署Etcd集群

1、自签证书

K8S 集群安装配置过程中,会使用各种证书,目的是为了加强集群安全性。K8S 提供了基于 CA 签名的双向数字证书认证方式和简单的基于 http base 或 token 的认证方式,其中 CA 证书方式的安全性最高。每个K8S集群都有一个集群根证书颁发机构(CA),集群中的组件通常使用CA来验证API server的证书,由API服务器验证kubelet客户端证书等。

证书生成操作可以在master节点上执行,证书只需要创建一次,以后在向集群中添加新节点时只要将证书拷贝到新节点上,并做一定的配置即可。下面就在 k8s-master-1 节点上来创建证书,详细的介绍也可以参考官方文档:分发自签名-CA-证书

① K8S 证书

如下是 K8S 各个组件需要使用的证书


② 准备 cfssl 工具

我是使用 cfssl 工具来生成证书,首先下载 cfssl 工具。依次执行如下命令:

curl -L https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o /usr/local/bin/cfssl
curl -L https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o /usr/local/bin/cfssljson
curl -L https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 -o /usr/local/bin/cfssl-certinfo

chmod +x /usr/local/bin/cfssl*

2、自签 Etcd SSL 证书

我们首先为 etcd 签发一套SSL证书,通过如下命令创建几个目录,/k8s/etcd/ssl 用于存放 etcd 自签证书,/k8s/etcd/cfg 用于存放 etcd 配置文件,/k8s/etcd/bin 用于存放 etcd 执行程序。

mkdir -p /k8s/etcd/{ssl,cfg,bin}

进入 etcd 目录:

cd /k8s/etcd/ssl

① 创建 CA 配置文件:ca-config.json

执行如下命令创建 ca-config.json

cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "etcd": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}
EOF

说明:

  • signing:表示该证书可用于签名其它证书;生成的 ca.pem 证书中 CA=TRUE;
  • profiles:可以定义多个 profiles,分别指定不同的过期时间、使用场景等参数;后续在签名证书时使用某个 profile;
  • expiry:证书过期时间
  • server auth:表示client可以用该 CA 对server提供的证书进行验证;
  • client auth:表示server可以用该CA对client提供的证书进行验证;

② 创建 CA 证书签名请求文件:ca-csr.json

执行如下命令创建 ca-csr.json:

cat > ca-csr.json <<EOF
{
  "CN": "etcd",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "Shanghai",
      "L": "Shanghai",
      "O": "etcd",
      "OU": "System"
    }
  ],
    "ca": {
       "expiry": "87600h"
    }
}
EOF

说明:

  • CN:Common Name,kube-apiserver 从证书中提取该字段作为请求的用户名 (User Name);浏览器使用该字段验证网站是否合法;
  • key:加密算法
  • C:国家
  • ST:地区
  • L:城市
  • O:组织,kube-apiserver 从证书中提取该字段作为请求用户所属的组 (Group);
  • OU:组织单位

③ 生成 CA 证书和私钥

[root@localhost ssl]# ll
总用量 8
-rw-r--r--. 1 root root 286 9月  17 21:34 ca-config.json
-rw-r--r--. 1 root root 250 9月  17 21:36 ca-csr.json
[root@localhost ssl]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca
2023/09/17 21:40:55 [INFO] generating a new CA key and certificate from CSR
2023/09/17 21:40:55 [INFO] generate received request
2023/09/17 21:40:55 [INFO] received CSR
2023/09/17 21:40:55 [INFO] generating key: rsa-2048
2023/09/17 21:40:55 [INFO] encoded CSR
2023/09/17 21:40:55 [INFO] signed certificate with serial number 701481595761330401456406283469574221010089175792
[root@localhost ssl]# ls
ca-config.json  ca.csr  ca-csr.json  ca-key.pem  ca.pem

说明:

  • ca-key.pem:CA 私钥
  • ca.pem:CA 数字证书

④ 创建证书签名请求文件:etcd-csr.json
执行如下命令创建 etcd-csr.json:

cat > etcd-csr.json <<EOF
{
    "CN": "etcd",
    "hosts": [
      "192.168.137.14",
      "192.168.137.16",
      "192.168.137.17"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "etcd",
            "OU": "System"
        }
    ]
}
EOF

说明:

  • hosts:需要指定授权使用该证书的 IP 或域名列表,这里配置所有 etcd 的IP地址。
  • key:加密算法及长度

⑤ 为 etcd 生成证书和私钥

[root@localhost ssl]# ls
ca-config.json  ca.csr  ca-csr.json  ca-key.pem  ca.pem  etcd-csr.json
[root@localhost ssl]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd etcd-csr.json | cfssljson -bare etcd
2023/09/17 21:46:49 [INFO] generate received request
2023/09/17 21:46:49 [INFO] received CSR
2023/09/17 21:46:49 [INFO] generating key: rsa-2048
2023/09/17 21:46:49 [INFO] encoded CSR
2023/09/17 21:46:49 [INFO] signed certificate with serial number 350599829265555004142693609766831172405337534555
2023/09/17 21:46:49 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
[root@localhost ssl]# ls
ca-config.json  ca.csr  ca-csr.json  ca-key.pem  ca.pem  etcd.csr  etcd-csr.json  etcd-key.pem  etcd.pem

参数说明:

  • ca:指定 CA 数字证书
  • ca-key:指定 CA 私钥
  • config:CA 配置文件
  • profile:指定环境
  • bare:指定证书名前缀

文件说明:

  • etcd-key.pem:etcd 私钥
  • etcd.pem:etcd 数字证书

证书生成完成,后面部署 Etcd 时主要会用到如下几个证书:

[root@localhost ssl]# pwd
/k8s/etcd/ssl
[root@localhost ssl]# ls *.pem
ca-key.pem  ca.pem  etcd-key.pem  etcd.pem

3、Etcd 数据库集群部署
etcd 集群采用主从架构模式(一主多从)部署,集群通过选举产生 leader,因此需要部署奇数个节点(3/5/7)才能正常工作。etcd使用raft一致性算法保证每个节点的一致性。

① 下载 etcd

从 github 上下载合适版本的 etcd

cd /k8s/etcd
wget https://github.com/coreos/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz

解压:

tar zxf etcd-v3.5.0-linux-amd64.tar.gz

将 etcd 复制到 /usr/local/bin 下:

cp etcd-v3.5.0-linux-amd64/{etcd,etcdctl} /k8s/etcd/bin
rm -rf etcd-v3.5.0-linux-amd64*

② 创建 etcd 配置文件:etcd.conf

cat > /k8s/etcd/cfg/etcd.conf <<EOF 
# [member]
ETCD_NAME=etcd-1
ETCD_DATA_DIR=/k8s/data/default.etcd
ETCD_LISTEN_PEER_URLS=https://192.168.137.14:2380
ETCD_LISTEN_CLIENT_URLS=https://192.168.137.14:2379

# [cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://192.168.137.14:2380
ETCD_ADVERTISE_CLIENT_URLS=https://192.168.137.14:2379
ETCD_INITIAL_CLUSTER=etcd-1=https://192.168.137.14:2380,etcd-2=https://192.168.137.16:2380,etcd-3=https://192.168.137.17:2380
ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
ETCD_INITIAL_CLUSTER_STATE=new

# [security]
ETCD_CERT_FILE=/k8s/etcd/ssl/etcd.pem
ETCD_KEY_FILE=/k8s/etcd/ssl/etcd-key.pem
ETCD_TRUSTED_CA_FILE=/k8s/etcd/ssl/ca.pem
ETCD_PEER_CERT_FILE=/k8s/etcd/ssl/etcd.pem
ETCD_PEER_KEY_FILE=/k8s/etcd/ssl/etcd-key.pem
ETCD_PEER_TRUSTED_CA_FILE=/k8s/etcd/ssl/ca.pem
EOF

说明:

  • ETCD_NAME:etcd在集群中的唯一名称
  • ETCD_DATA_DIR:etcd数据存放目录
  • ETCD_LISTEN_PEER_URLS:etcd集群间通讯的地址,设置为本机IP
  • ETCD_LISTEN_CLIENT_URLS:客户端访问的地址,设置为本机IP
  • ETCD_INITIAL_ADVERTISE_PEER_URLS:初始集群通告地址,集群内部通讯地址,设置为本机IP
  • ETCD_ADVERTISE_CLIENT_URLS:客户端通告地址,设置为本机IP
  • ETCD_INITIAL_CLUSTER:集群节点地址,以 key=value 的形式添加各个 etcd 的地址
  • ETCD_INITIAL_CLUSTER_TOKEN:集群令牌,用于集群间做简单的认证
  • ETCD_INITIAL_CLUSTER_STATE:集群状态
  • ETCD_CERT_FILE:客户端 etcd 数字证书路径
  • ETCD_KEY_FILE:客户端 etcd 私钥路径
  • ETCD_TRUSTED_CA_FILE:客户端 CA 证书路径
  • ETCD_PEER_CERT_FILE:集群间通讯etcd数字证书路径
  • ETCD_PEER_KEY_FILE:集群间通讯etcd私钥路径
  • ETCD_PEER_TRUSTED_CA_FILE:集群间通讯CA证书路径
    ③ 创建 etcd 服务:etcd.service

通过EnvironmentFile指定 etcd.conf 作为环境配置文件

cat > /k8s/etcd/etcd.service <<'EOF'
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
EnvironmentFile=/k8s/etcd/cfg/etcd.conf
WorkingDirectory=${ETCD_DATA_DIR}

ExecStart=/k8s/etcd/bin/etcd \
  --name=${ETCD_NAME} \
  --data-dir=${ETCD_DATA_DIR} \
  --listen-peer-urls=${ETCD_LISTEN_PEER_URLS} \
  --listen-client-urls=${ETCD_LISTEN_CLIENT_URLS},http://127.0.0.1:2379 \
  --initial-advertise-peer-urls=${ETCD_INITIAL_ADVERTISE_PEER_URLS} \
  --advertise-client-urls=${ETCD_ADVERTISE_CLIENT_URLS} \
  --initial-cluster=${ETCD_INITIAL_CLUSTER} \
  --initial-cluster-token=${ETCD_INITIAL_CLUSTER_TOKEN} \
  --initial-cluster-state=${ETCD_INITIAL_CLUSTER_STATE} \
  --cert-file=${ETCD_CERT_FILE} \
  --key-file=${ETCD_KEY_FILE} \
  --trusted-ca-file=${ETCD_TRUSTED_CA_FILE} \
  --peer-cert-file=${ETCD_PEER_CERT_FILE} \
  --peer-key-file=${ETCD_PEER_KEY_FILE} \
  --peer-trusted-ca-file=${ETCD_PEER_TRUSTED_CA_FILE}

Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

etcd.service 更多的配置以及说明可以通过如下命令查看:/k8s/etcd/bin/etcd --help

ETCD3.4版本会自动读取环境变量的参数,所以EnvironmentFile文件中有的参数,不需要再次在ExecStart启动参数中添加,二选一,如同时配置,会触发以下类似报错

etcd: conflicting environment variable "ETCD_NAME" is shadowed by corresponding command-line flag (either unset environment variable or disable flag)

④ 将 etcd 目录拷贝到另外两个节点

[root@localhost etcd]# scp -r /k8s/ root@k8s-node-1:/k8s
The authenticity of host 'k8s-node-1 (192.168.137.16)' can't be established.
ECDSA key fingerprint is SHA256:8NHw5kqsFAXGzdOH4LTN6v9ps+Bl3C9k9sEUf6ZEfVw.
ECDSA key fingerprint is MD5:57:b3:24:da:12:ad:bb:37:23:12:57:6f:d0:87:0f:58.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'k8s-node-1,192.168.137.16' (ECDSA) to the list of known hosts.
root@k8s-node-1's password: 
ca-config.json                                                                                                                                                                                                                              100%  286   680.0KB/s   00:00    
ca-csr.json                                                                                                                                                                                                                                 100%  250   504.9KB/s   00:00    
ca.pem                                                                                                                                                                                                                                      100% 1350     3.0MB/s   00:00    
ca-key.pem                                                                                                                                                                                                                                  100% 1679     4.0MB/s   00:00    
ca.csr                                                                                                                                                                                                                                      100%  997     2.6MB/s   00:00    
etcd-csr.json                                                                                                                                                                                                                               100%  352   930.6KB/s   00:00    
etcd.pem                                                                                                                                                                                                                                    100% 1424     3.0MB/s   00:00    
etcd-key.pem                                                                                                                                                                                                                                100% 1675     4.1MB/s   00:00    
etcd.csr                                                                                                                                                                                                                                    100% 1058     2.7MB/s   00:00    
etcd.conf                                                                                                                                                                                                                                   100%  764     2.0MB/s   00:00    
etcd                                                                                                                                                                                                                                        100%   22MB 256.0MB/s   00:00    
etcdctl                                                                                                                                                                                                                                     100%   17MB 262.4MB/s   00:00    
etcd.service                                                                                                                                                                                                                                100% 1033     3.0MB/s   00:00    
[root@localhost etcd]# scp -r /k8s/ root@k8s-node-2:/k8s
The authenticity of host 'k8s-node-2 (192.168.137.17)' can't be established.
ECDSA key fingerprint is SHA256:zdJng7VI65mj43rSyJ+/FhSxmlI07PTpWTG8H5JDsAI.
ECDSA key fingerprint is MD5:f4:87:57:e4:91:de:8a:9d:16:83:50:7e:03:ca:2d:4e.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'k8s-node-2,192.168.137.17' (ECDSA) to the list of known hosts.
root@k8s-node-2's password: 
ca-config.json                                                                                                                                                                                                                              100%  286   480.1KB/s   00:00    
ca-csr.json                                                                                                                                                                                                                                 100%  250   578.8KB/s   00:00    
ca.pem                                                                                                                                                                                                                                      100% 1350     3.3MB/s   00:00    
ca-key.pem                                                                                                                                                                                                                                  100% 1679     4.0MB/s   00:00    
ca.csr                                                                                                                                                                                                                                      100%  997     2.1MB/s   00:00    
etcd-csr.json                                                                                                                                                                                                                               100%  352     1.0MB/s   00:00    
etcd.pem                                                                                                                                                                                                                                    100% 1424     3.2MB/s   00:00    
etcd-key.pem                                                                                                                                                                                                                                100% 1675     4.8MB/s   00:00    
etcd.csr                                                                                                                                                                                                                                    100% 1058     2.7MB/s   00:00    
etcd.conf                                                                                                                                                                                                                                   100%  764     2.0MB/s   00:00    
etcd                                                                                                                                                                                                                                        100%   22MB 242.6MB/s   00:00    
etcdctl                                                                                                                                                                                                                                     100%   17MB 262.6MB/s   00:00    
etcd.service

⑤ 修改两个节点配置文件

修改 k8s-node-1 节点的 /k8s/etcd/cfg/etcd.conf:

# [member]
ETCD_NAME=etcd-2
ETCD_LISTEN_PEER_URLS=https://192.168.31.35:2380
ETCD_LISTEN_CLIENT_URLS=https://192.168.31.35:2379

# [cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://192.168.31.35:2380
ETCD_ADVERTISE_CLIENT_URLS=https://192.168.31.35:2379

修改 k8s-node-2 节点的 /k8s/etcd/cfg/etcd.conf:

# [member]
ETCD_NAME=etcd-3
ETCD_LISTEN_PEER_URLS=https://192.168.31.71:2380
ETCD_LISTEN_CLIENT_URLS=https://192.168.31.71:2379

# [cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://192.168.31.71:2380
ETCD_ADVERTISE_CLIENT_URLS=https://192.168.31.71:2379

⑥ 启动 etcd 服务
首先在三个节点将 etcd.service 拷贝到 /usr/lib/systemd/system/ 下

cp /k8s/etcd/etcd.service /usr/lib/systemd/system/
systemctl daemon-reload

在三个节点启动 etcd 服务 systemctl start etcd
设置开机启动 systemctl enable etcd
查看 etcd 的日志 tail -f /var/log/messages

注意:如果日志中出现连接异常信息,请确认所有节点防火墙是否开放2379,2380端口,或者直接关闭防火墙。
查看 etcd 集群状态

/k8s/etcd/bin/etcdctl \
    --cacert=/k8s/etcd/ssl/ca.pem \
    --key=/k8s/etcd/ssl/etcd-key.pem \
    --cert=/k8s/etcd/ssl/etcd.pem \
    --endpoints=https://192.168.137.14:2379,https://192.168.137.16:2379,https://192.168.137.17:2379 \
    --write-out=table \
endpoint health 

执行结果

[root@localhost etcd]# /k8s/etcd/bin/etcdctl \
>     --cacert=/k8s/etcd/ssl/ca.pem \
>     --key=/k8s/etcd/ssl/etcd-key.pem \
>     --cert=/k8s/etcd/ssl/etcd.pem \
>     --endpoints=https://192.168.137.14:2379,https://192.168.137.16:2379,https://192.168.137.17:2379 \
>     --write-out=table \
> endpoint health 
+-----------------------------+--------+-------------+-------+
|          ENDPOINT           | HEALTH |    TOOK     | ERROR |
+-----------------------------+--------+-------------+-------+
| https://192.168.137.14:2379 |   true |   6.79061ms |       |
| https://192.168.137.17:2379 |   true | 11.304482ms |       |
| https://192.168.137.16:2379 |   true | 11.374983ms |       |
+-----------------------------+--------+-------------+-------+

六、集群搭建 —— 部署Master组件

1、自签 ApiServer SSL 证书

K8S 集群中所有资源的访问和变更都是通过 kube-apiserver 的 REST API 来实现的,首先在 master 节点上部署 kube-apiserver 组件。

我们首先为 apiserver 签发一套SSL证书,过程与 etcd 自签SSL证书类似。通过如下命令创建几个目录,ssl 用于存放自签证书,cfg 用于存放配置文件,bin 用于存放执行程序,logs 存放日志文件。

mkdir -p /k8s/kubernetes/{ssl,cfg,bin,logs}

进入 kubernetes 目录:

cd /k8s/kubernetes/ssl

① 创建 CA 配置文件:ca-config.json

执行如下命令创建 ca-config.json

cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}
EOF

② 创建 CA 证书签名请求文件:ca-csr.json

执行如下命令创建 ca-csr.json:

cat > ca-csr.json <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "Shanghai",
      "L": "Shanghai",
      "O": "kubernetes",
      "OU": "System"
    }
  ],
    "ca": {
       "expiry": "87600h"
    }
}
EOF

③ 生成 CA 证书和私钥

cfssl gencert -initca ca-csr.json | cfssljson -bare ca

④ 创建证书签名请求文件:kubernetes-csr.json

执行如下命令创建 kubernetes-csr.json:

cat > kubernetes-csr.json <<EOF
{
    "CN": "kubernetes",
    "hosts": [
      "127.0.0.1",
      "10.0.0.1",
      "192.168.137.14",
      "192.168.137.15",
      "192.168.137.16",
      "192.168.137.17",
      "kubernetes",
      "kubernetes.default",
      "kubernetes.default.svc",
      "kubernetes.default.svc.cluster",
      "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "kubernetes",
            "OU": "System"
        }
    ]
}
EOF

说明:

  • hosts:指定会直接访问 apiserver 的IP列表,一般需指定 etcd 集群、kubernetes master 集群的主机 IP 和 kubernetes 服务的服务 IP,Node 的IP一般不需要加入。

⑤ 为 kubernetes 生成证书和私钥

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes

2、部署 kube-apiserver 组件

① 下载二进制包

通过 kubernetes Github 下载安装用的二进制包,server 二进制包已经包含了 master、node 上的各个组件,下载 server 二进制包即可。

cd /data/soft
wget https://dl.k8s.io/v1.16.2/kubernetes-server-linux-amd64.tar.gz --no-check-certificate

解压:

tar -zxf kubernetes-server-linux-amd64.tar.gz

先将 master 节点上部署的组件拷贝到 /k8s/kubernetes/bin 目录下:

cp -p /data/soft/kubernetes/server/bin/{kube-apiserver,kube-controller-manager,kube-scheduler} /k8s/kubernetes/bin/
cp -p /data/soft/kubernetes/server/bin/kubectl /usr/local/bin/

② 创建 Node 令牌文件:token.csv
Master apiserver 启用 TLS 认证后,Node节点 kubelet 组件想要加入集群,必须使用CA签发的有效证书才能与apiserver通信,当Node节点很多时,签署证书是一件很繁琐的事情,因此有了 TLS Bootstrap 机制,kubelet 会以一个低权限用户自动向 apiserver 申请证书,kubelet 的证书由 apiserver 动态签署。因此先为 apiserver 生成一个令牌文件,令牌之后会在 Node 中用到。

生成 token,一个随机字符串,可使用如下命令生成 token:apiserver 配置的 token 必须与 Node 节点 bootstrap.kubeconfig 配置保持一致。

[root@k8s-master-1 kubernetes]# head -c 16 /dev/urandom | od -An -t x | tr -d ' '
b9187da74c444ee060a1c6053eab97c7

创建 token.csv,格式:token,用户,UID,用户组

cat > /k8s/kubernetes/cfg/token.csv <<'EOF'
b9187da74c444ee060a1c6053eab97c7,kubelet-bootstrap,10001,"system:node-bootstrapper"
EOF

③ 创建 kube-apiserver 配置文件:kube-apiserver.conf

kube-apiserver 有很多配置项,可以参考官方文档查看每个配置项的用途:kube-apiserver

注意:踩的一个坑,“\” 后面不要有空格,不要有多余的换行,否则启动失败。

cat > /k8s/kubernetes/cfg/kube-apiserver.conf <<'EOF'
KUBE_APISERVER_OPTS="--etcd-servers=https://192.168.137.14:2379,https://192.168.137.16:2379,https://192.168.137.17:2379 \
  --bind-address=192.168.137.14 \
  --secure-port=6443 \
  --advertise-address=192.168.137.14 \
  --allow-privileged=true \
  --service-cluster-ip-range=10.0.0.0/24 \
  --service-node-port-range=30000-32767 \
  --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \
  --authorization-mode=RBAC,Node \
  --enable-bootstrap-token-auth=true \
  --token-auth-file=/k8s/kubernetes/cfg/token.csv \
  --kubelet-client-certificate=/k8s/kubernetes/ssl/kubernetes.pem \
  --kubelet-client-key=/k8s/kubernetes/ssl/kubernetes-key.pem \
  --tls-cert-file=/k8s/kubernetes/ssl/kubernetes.pem \
  --tls-private-key-file=/k8s/kubernetes/ssl/kubernetes-key.pem \
  --client-ca-file=/k8s/kubernetes/ssl/ca.pem \
  --service-account-key-file=/k8s/kubernetes/ssl/ca-key.pem \
  --service-account-signing-key-file=/k8s/kubernetes/ssl/kubernetes-key.pem \
  --service-account-issuer=https://kubernetes.service.account.issuer \
  --etcd-cafile=/k8s/etcd/ssl/ca.pem \
  --etcd-certfile=/k8s/etcd/ssl/etcd.pem \
  --etcd-keyfile=/k8s/etcd/ssl/etcd-key.pem \
  --v=2 \
  --logtostderr=false \
  --log-dir=/k8s/kubernetes/logs \
  --audit-log-maxage=30 \
  --audit-log-maxbackup=3 \
  --audit-log-maxsize=100 \
  --audit-log-path=/k8s/kubernetes/logs/k8s-audit.log"
EOF

重点配置说明:

  • --etcd-servers:etcd 集群地址
  • --bind-address:apiserver 监听的地址,一般配主机IP
  • --secure-port:监听的端口
  • --advertise-address:集群通告地址,其它Node节点通过这个地址连接 apiserver,不配置则使用 --bind-address
  • --service-cluster-ip-range:Service 的 虚拟IP范围,以CIDR格式标识,该IP范围不能与物理机的真实IP段有重合。
  • --service-node-port-range:Service 可映射的物理机端口范围,默认30000-32767
  • --admission-control:集群的准入控制设置,各控制模块以插件的形式依次生效,启用RBAC授权和节点自管理
  • --authorization-mode:授权模式,包括:AlwaysAllow,AlwaysDeny,ABAC(基于属性的访问控制),Webhook,RBAC(基于角色的访问控制),Node(专门授权由 kubelet 发出的API请求)。(默认值"AlwaysAllow")。
  • --enable-bootstrap-token-auth:启用TLS bootstrap功能
  • --token-auth-file:这个文件将被用于通过令牌认证来保护API服务的安全端口。
  • --v:指定日志级别,0~8,越大日志越详细

④ 创建 apiserver 服务:kube-apiserver.service

cat > /usr/lib/systemd/system/kube-apiserver.service <<'EOF'
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
EnvironmentFile=/k8s/kubernetes/cfg/kube-apiserver.conf
ExecStart=/k8s/kubernetes/bin/kube-apiserver $KUBE_APISERVER_OPTS

Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

⑤ 启动 kube-apiserver 组件
启动组件

systemctl daemon-reload
systemctl start kube-apiserver
systemctl enable kube-apiserver

检查启动状态

systemctl status kube-apiserver.service

查看启动日志

less /k8s/kubernetes/logs/kube-apiserver.INFO

⑥ 将 kubelet-bootstrap 用户绑定到系统集群角色,之后便于 Node 使用token请求证书

kubectl create clusterrolebinding kubelet-bootstrap \
  --clusterrole=system:node-bootstrapper \
  --user=kubelet-bootstrap

3、部署 kube-controller-manager 组件

① 创建 kube-controller-manager 配置文件:kube-controller-manager.conf

详细的配置可参考官方文档:kube-controller-manager

cat > /k8s/kubernetes/cfg/kube-controller-manager.conf <<'EOF'
KUBE_CONTROLLER_MANAGER_OPTS="--leader-elect=true \
  --master=127.0.0.1:8080 \
  --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=/k8s/kubernetes/ssl/ca.pem \
  --cluster-signing-key-file=/k8s/kubernetes/ssl/ca-key.pem \
  --root-ca-file=/k8s/kubernetes/ssl/ca.pem \
  --service-account-private-key-file=/k8s/kubernetes/ssl/ca-key.pem \
  --experimental-cluster-signing-duration=87600h0m0s \
  --v=2 \
  --logtostderr=false \
  --log-dir=/k8s/kubernetes/logs"
EOF

重点配置说明:

  • --leader-elect:当该组件启动多个时,自动选举,默认true
  • --master:连接本地apiserver,apiserver 默认会监听本地8080端口
  • --allocate-node-cidrs:是否分配和设置Pod的CDIR
  • --service-cluster-ip-range:Service 集群IP段
    ② 创建 kube-controller-manager 服务:kube-controller-manager.service
cat > /usr/lib/systemd/system/kube-controller-manager.service <<'EOF'
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
EnvironmentFile=/k8s/kubernetes/cfg/kube-controller-manager.conf
ExecStart=/k8s/kubernetes/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_OPTS

Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

③ 启动 kube-controller-manager 组件

启动组件

systemctl daemon-reload
systemctl start kube-controller-manager
systemctl enable kube-controller-manager

4、部署 kube-scheduler 组件
① 创建 kube-scheduler 配置文件:kube-scheduler.conf

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

② 创建 kube-scheduler 服务:kube-scheduler.service

cat > /usr/lib/systemd/system/kube-scheduler.service <<'EOF'
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
EnvironmentFile=/k8s/kubernetes/cfg/kube-scheduler.conf
ExecStart=/k8s/kubernetes/bin/kube-scheduler $KUBE_SCHEDULER_OPTS

Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

③ 启动 kube-scheduler 组件

启动组件

systemctl daemon-reload
systemctl start kube-scheduler
systemctl enable kube-scheduler

查看启动状态

systemctl status kube-scheduler.service

查看启动日志

less /k8s/kubernetes/logs/kube-scheduler.INFO

5、查看集群状态
① 查看组件状态

kubectl get cs

七、集群搭建 —— 部署Node组件

1、安装 Docker

CentOS 安装参考官方文档:https://docs.docker.com/install/linux/docker-ce/centos/
k8s-node-1,k8s-node-2 安装docker
① 卸载旧版本

yum remove docker docker-common docker-selinux

② 安装依赖包

yum install -y yum-utils device-mapper-persistent-data lvm2

③ 安装 Docker 软件包源

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

④ 安装 Docker CE

yum install docker-ce

⑤ 启动 Docker 服务

systemctl start docker

⑥ 设置开机启动

systemctl enable docker

⑦ 验证安装是否成功

docker -v
docker info

2、Node 节点证书

① 创建 Node 节点的证书签名请求文件:kube-proxy-csr.json

首先在 k8s-master-1 节点上,通过颁发的 CA 证书先创建好 Node 节点要使用的证书,先创建证书签名请求文件:kube-proxy-csr.json:

cat > /k8s/kubernetes/ssl/kube-proxy-csr.json <<EOF
{
    "CN": "system:kube-proxy",
    "hosts": [],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "kubernetes",
            "OU": "System"
        }
    ]
}
EOF

② 为 kube-proxy 生成证书和私钥

cd /k8s/kubernetes/ssl

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy

③ node 节点创建工作目录

在 k8s-node-1 节点上创建 k8s 目录

mkdir -p /k8s/kubernetes/{bin,cfg,logs,ssl}

④ 将 k8s-master-1 节点的文件拷贝到 node 节点

将 kubelet、kube-proxy 拷贝到 node 节点上:

scp -r /data/soft/kubernetes/server/bin/{kubelet,kube-proxy} root@k8s-node-1:/k8s/kubernetes/bin/

将证书拷贝到 k8s-node-1 节点上:

scp -r /k8s/kubernetes/ssl/{ca.pem,kube-proxy.pem,kube-proxy-key.pem} root@k8s-node-1:/k8s/kubernetes/ssl/

3、安装 kubelet

① 创建请求证书的配置文件:bootstrap.kubeconfig

bootstrap.kubeconfig 将用于向 apiserver 请求证书,apiserver 会验证 token、证书 是否有效,验证通过则自动颁发证书。

cat > /k8s/kubernetes/cfg/bootstrap.kubeconfig <<'EOF'
apiVersion: v1
clusters:
- cluster: 
    certificate-authority: /k8s/kubernetes/ssl/ca.pem
    server: https://192.168.137.14:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubelet-bootstrap
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: kubelet-bootstrap
  user:
    token: b9187da74c444ee060a1c6053eab97c7
EOF

说明:

  • certificate-authority:CA 证书
  • server:master 地址
  • token:master 上 token.csv 中配置的 token

② 创建 kubelet 配置文件:kubelet-config.yml

为了安全性,kubelet 禁止匿名访问,必须授权才可以,通过 kubelet-config.yml 授权 apiserver 访问 kubelet。

cat > /k8s/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: /k8s/kubernetes/ssl/ca.pem
authorization:
  mode: Webhook
  webhook:
    cacheAuthroizedTTL: 5m0s
    cacheUnauthorizedTTL: 30s
evictionHard:
  imagefs.available: 15%
  memory.available: 100Mi
  nodefs.available: 10%
  nodefs.inodesFree: 5%
maxOpenFiles: 100000
maxPods: 110
EOF

说明:

  • address:kubelet 监听地址
  • port:kubelet 的端口
  • cgroupDriver:cgroup 驱动,与 docker 的 cgroup 驱动一致
  • authentication:访问 kubelet 的授权信息
  • authorization:认证相关信息
  • evictionHard:垃圾回收策略
  • maxPods:最大pod数

③ 创建 kubelet 服务配置文件:kubelet.conf

cat > /k8s/kubernetes/cfg/kubelet.conf <<'EOF'
KUBELET_OPTS="--hostname-override=k8s-node-1 \
  --network-plugin=cni \
  --cni-bin-dir=/opt/cni/bin \
  --cni-conf-dir=/etc/cni/net.d \
  --cgroups-per-qos=false \
  --enforce-node-allocatable="" \
  --kubeconfig=/k8s/kubernetes/cfg/kubelet.kubeconfig \
  --bootstrap-kubeconfig=/k8s/kubernetes/cfg/bootstrap.kubeconfig \
  --config=/k8s/kubernetes/cfg/kubelet-config.yml \
  --cert-dir=/k8s/kubernetes/ssl \
  --pod-infra-container-image=kubernetes/pause:latest \
  --v=2 \
  --logtostderr=false \
  --log-dir=/k8s/kubernetes/logs"
EOF

说明:

  • --hostname-override:当前节点注册到K8S中显示的名称,默认为主机 hostname
  • --network-plugin:启用 CNI 网络插件
  • --cni-bin-dir:CNI 插件可执行文件位置,默认在 /opt/cni/bin 下
  • --cni-conf-dir:CNI 插件配置文件位置,默认在 /etc/cni/net.d 下
  • --cgroups-per-qos:必须加上这个参数和--enforce-node-allocatable,否则报错 [Failed to start ContainerManager failed to initialize top level QOS containers.......]
  • --kubeconfig:会自动生成 kubelet.kubeconfig,用于连接 apiserver
  • --bootstrap-kubeconfig:指定 bootstrap.kubeconfig 文件
  • --config:kubelet 配置文件
  • --cert-dir:证书目录
  • --pod-infra-container-image:管理Pod网络的镜像,基础的 Pause 容器,默认是 k8s.gcr.io/pause:3.1

④ 创建 kubelet 服务:kubelet.service

cat > /usr/lib/systemd/system/kubelet.service <<'EOF'
[Unit]
Description=Kubernetes Kubelet
After=docker.service
Before=docker.service

[Service]
EnvironmentFile=/k8s/kubernetes/cfg/kubelet.conf
ExecStart=/k8s/kubernetes/bin/kubelet $KUBELET_OPTS
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

⑤ 启动 kubelet

systemctl daemon-reload
systemctl start kubelet

开机启动:

systemctl enable kubelet

查看启动日志:

tail -f /k8s/kubernetes/logs/kubelet.INFO

⑥ master 给 node 授权

kubelet 启动后,还没加入到集群中,会向 apiserver 请求证书,需手动在 k8s-master-1 上对 node 授权。

通过如下命令查看是否有新的客户端请求颁发证书:

[root@k8s-master-1 ssl]# kubectl get csr
NAME                                                   AGE   REQUESTOR           CONDITION
node-csr-6jk-QVHhy1BdMfqAZ6znhkgmFocCk69nSozPR59QBMI   60s   kubelet-bootstrap   Pending

给客户端颁发证书,允许客户端加入集群:

[root@k8s-master-1 ssl]# kubectl certificate approve node-csr-6jk-QVHhy1BdMfqAZ6znhkgmFocCk69nSozPR59QBMI
certificatesigningrequest.certificates.k8s.io/node-csr-6jk-QVHhy1BdMfqAZ6znhkgmFocCk69nSozPR59QBMI approved

⑦ 授权成功

查看 node 是否加入集群(此时的 node 还处于未就绪的状态,因为还没有安装 CNI 组件):

[root@k8s-master-1 ssl]# kubectl get node
NAME         STATUS     ROLES    AGE   VERSION
k8s-node-1   NotReady   <none>   2s    v1.16.2

颁发证书后,可以在 /k8s/kubenetes/ssl 下看到 master 为 kubelet 颁发的证书:

[root@k8s-node-1 ~]# ls /k8s/kubernetes/ssl/
ca.pem  kubelet-client-2023-09-18-17-15-25.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key  kube-proxy-key.pem  kube-proxy.pem

在 /k8s/kubenetes/cfg 下可以看到自动生成的 kubelet.kubeconfig 配置文件:

[root@k8s-node-1 ~]# ls /k8s/kubernetes/cfg
bootstrap.kubeconfig  kubelet.conf  kubelet-config.yml  kubelet.kubeconfig

4、安装 kube-proxy

① 创建 kube-proxy 连接 apiserver 的配置文件:kube-proxy.kubeconfig

cat > /k8s/kubernetes/cfg/kube-proxy.kubeconfig <<'EOF'
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /k8s/kubernetes/ssl/ca.pem
    server: https://192.168.137.14:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kube-proxy
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: kube-proxy
  user:
    client-certificate: /k8s/kubernetes/ssl/kube-proxy.pem
    client-key: /k8s/kubernetes/ssl/kube-proxy-key.pem
EOF

② 创建 kube-proxy 配置文件:kube-proxy-config.yml

cat > /k8s/kubernetes/cfg/kube-proxy-config.yml <<'EOF'
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
address: 0.0.0.0
metrisBindAddress: 0.0.0.0:10249
clientConnection:
  kubeconfig: /k8s/kubernetes/cfg/kube-proxy.kubeconfig
hostnameOverride: k8s-node-1
clusterCIDR: 10.0.0.0/24
mode: ipvs
ipvs:
  scheduler: "rr"
iptables:
  masqueradeAll: true
EOF

说明:

  • metrisBindAddress:采集指标暴露的地址端口,便于监控系统,采集数据
  • clusterCIDR:集群 Service 网段

③ 创建 kube-proxy 配置文件:kube-proxy.conf

cat > /k8s/kubernetes/cfg/kube-proxy.conf <<'EOF'
KUBE_PROXY_OPTS="--config=/k8s/kubernetes/cfg/kube-proxy-config.yml \
  --v=2 \
  --logtostderr=false \
  --log-dir=/k8s/kubernetes/logs"
EOF

④ 创建 kube-proxy 服务:kube-proxy.service

cat > /usr/lib/systemd/system/kube-proxy.service <<'EOF'
[Unit]
Description=Kubernetes Proxy
After=network.target

[Service]
EnvironmentFile=/k8s/kubernetes/cfg/kube-proxy.conf
ExecStart=/k8s/kubernetes/bin/kube-proxy $KUBE_PROXY_OPTS
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

⑤ 启动 kube-proxy
启动服务:

systemctl daemon-reload
systemctl start kube-proxy

开机启动:

systemctl enable kube-proxy

查看启动日志:

tail -f /k8s/kubernetes/logs/kube-proxy.INFO

5、部署其它Node节点
部署其它 Node 节点基于与上述流程一致,只需将配置文件中 k8s-node-1 改为 k8s-node-2 即可。

[root@k8s-master-1 ssl]# kubectl get node -o wide
NAME         STATUS     ROLES    AGE     VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION           CONTAINER-RUNTIME
k8s-node-1   NotReady   <none>   19m     v1.16.2   192.168.137.16   <none>        CentOS Linux 7 (Core)   3.10.0-1127.el7.x86_64   docker://24.0.6
k8s-node-2   NotReady   <none>   2m57s   v1.16.2   192.168.137.17   <none>        CentOS Linux 7 (Core)   3.10.0-1127.el7.x86_64   docker://24.0.6

6、部署K8S容器集群网络(Flannel)

① K8S 集群网络

Kubernetes 项目并没有使用 Docker 的网络模型,kubernetes 是通过一个 CNI 接口维护一个单独的网桥来代替 docker0,这个网桥默认叫 cni0

CNI(Container Network Interface)是CNCF旗下的一个项目,由一组用于配置 Linux 容器的网络接口的规范和库组成,同时还包含了一些插件。CNI仅关心容器创建时的网络分配,和当容器被删除时释放网络资源。

Flannel 是 CNI 的一个插件,可以看做是 CNI 接口的一种实现。Flannel 是针对 Kubernetes 设计的一个网络规划服务,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。

Flannel 网络架构请参考:flannel 网络架构

② 创建 CNI 工作目录

通过给 kubelet 传递 --network-plugin=cni 命令行选项来启用 CNI 插件。 kubelet 从 --cni-conf-dir (默认是 /etc/cni/net.d)读取配置文件并使用该文件中的 CNI 配置来设置每个 pod 的网络。CNI 配置文件必须与 CNI 规约匹配,并且配置引用的任何所需的 CNI 插件都必须存在于 --cni-bin-dir(默认是 /opt/cni/bin)指定的目录。

由于前面部署 kubelet 服务时,指定了 --cni-conf-dir=/etc/cni/net.d--cni-bin-dir=/opt/cni/bin,因此首先在node节点上创建这两个目录:

mkdir -p /opt/cni/bin /etc/cni/net.d

③ 装 CNI 插件

可以从 github 上下载 CNI 插件:下载 CNI 插件

mkdir -p /data/soft && cd /data/soft/
wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz
tar zxf cni-plugins-linux-amd64-v1.1.1.tgz -C /opt/cni/bin/
cd /opt/cni/bin/

[root@k8s-node-1 bin]# ls
bandwidth  bridge  dhcp  firewall  host-device  host-local  ipvlan  loopback  macvlan  portmap  ptp  sbr  static  tuning  vlan  vrf

④ 部署 Flannel

下载 flannel 配置文件:

cat >> /etc/hosts << EOF
185.199.110.133 raw.githubusercontent.com
EOF

cd /k8s/kubernetes/cfg

wget https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml

注意如下配置:Network 的地址需与** kube-controller-manager.conf** 中的 --cluster-cidr=10.244.0.0/16 保持一致。

在 k8s-master-1 节点上部署 Flannel:

[root@k8s-master-1 cfg]# kubectl apply -f kube-flannel.yml
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds-amd64 created
daemonset.apps/kube-flannel-ds-arm64 created
daemonset.apps/kube-flannel-ds-arm created
daemonset.apps/kube-flannel-ds-ppc64le created
daemonset.apps/kube-flannel-ds-s390x created

⑤ 检查部署状态

Flannel 会在 Node 上起一个 Flannel 的 Pod,可以查看 pod 的状态看 flannel 是否启动成功:

[root@k8s-master-1 cfg]# kubectl get pods -n kube-system -o wide
NAME                          READY   STATUS     RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
kube-flannel-ds-amd64-k77nl   0/1     Init:0/1   0          3m41s   192.168.137.17   k8s-node-2   <none>           <none>
kube-flannel-ds-amd64-s7b7b   0/1     Init:0/1   0          3m41s   192.168.137.16   k8s-node-1   <none>           <none>

观察 k8s-node-1/2 上面的 kubelet 的输出,存在报错,kubernetes/pause:latest镜像无法拉取

[root@k8s-node-1 ~]# journalctl -f -u kubelet
-- Logs begin at 一 2023-09-18 14:01:17 CST. --
9月 18 22:56:03 k8s-node-1 kubelet[29325]: E0918 22:56:03.561630   29325 kubelet.go:2187] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
9月 18 22:56:08 k8s-node-1 kubelet[29325]: E0918 22:56:08.570365   29325 kubelet.go:2187] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
9月 18 22:56:13 k8s-node-1 kubelet[29325]: E0918 22:56:13.583713   29325 kubelet.go:2187] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
9月 18 22:56:14 k8s-node-1 kubelet[29325]: E0918 22:56:14.011477   29325 aws_credentials.go:77] while getting AWS credentials NoCredentialProviders: no valid providers in chain. Deprecated.
9月 18 22:56:14 k8s-node-1 kubelet[29325]: For verbose messaging see aws.Config.CredentialsChainVerboseErrors
9月 18 22:56:18 k8s-node-1 kubelet[29325]: E0918 22:56:18.312985   29325 remote_runtime.go:105] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = failed pulling image "kubernetes/pause:latest": Error response from daemon: manifest for kubernetes/pause:latest not found: manifest unknown: manifest unknown
9月 18 22:56:18 k8s-node-1 kubelet[29325]: E0918 22:56:18.313023   29325 kuberuntime_sandbox.go:68] CreatePodSandbox for pod "kube-flannel-ds-amd64-s7b7b_kube-system(71fc2598-b9e6-401f-a4ba-795184198586)" failed: rpc error: code = Unknown desc = failed pulling image "kubernetes/pause:latest": Error response from daemon: manifest for kubernetes/pause:latest not found: manifest unknown: manifest unknown
9月 18 22:56:18 k8s-node-1 kubelet[29325]: E0918 22:56:18.313039   29325 kuberuntime_manager.go:710] createPodSandbox for pod "kube-flannel-ds-amd64-s7b7b_kube-system(71fc2598-b9e6-401f-a4ba-795184198586)" failed: rpc error: code = Unknown desc = failed pulling image "kubernetes/pause:latest": Error response from daemon: manifest for kubernetes/pause:latest not found: manifest unknown: manifest unknown
9月 18 22:56:18 k8s-node-1 kubelet[29325]: E0918 22:56:18.313084   29325 pod_workers.go:191] Error syncing pod 71fc2598-b9e6-401f-a4ba-795184198586 ("kube-flannel-ds-amd64-s7b7b_kube-system(71fc2598-b9e6-401f-a4ba-795184198586)"), skipping: failed to "CreatePodSandbox" for "kube-flannel-ds-amd64-s7b7b_kube-system(71fc2598-b9e6-401f-a4ba-795184198586)" with CreatePodSandboxError: "CreatePodSandbox for pod \"kube-flannel-ds-amd64-s7b7b_kube-system(71fc2598-b9e6-401f-a4ba-795184198586)\" failed: rpc error: code = Unknown desc = failed pulling image \"kubernetes/pause:latest\": Error response from daemon: manifest for kubernetes/pause:latest not found: manifest unknown: manifest unknown"
9月 18 22:56:18 k8s-node-1 kubelet[29325]: E0918 22:56:18.589370   29325 kubelet.go:2187] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
9月 18 22:56:23 k8s-node-1 kubelet[29325]: E0918 22:56:23.594946   29325 kubelet.go:2187] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

k8s-node-1/2 上不知道什么原因,无法拉取

[root@k8s-node-2 ~]# docker search kubernetes/pause
NAME               DESCRIPTION                               STARS     OFFICIAL   AUTOMATED
kubernetes/pause   restore from backup of kubernetes/pause   13                   
[root@k8s-node-2 ~]# docker pull kubernetes/pause
Using default tag: latest
Error response from daemon: manifest for kubernetes/pause:latest not found: manifest unknown: manifest unknown

为了解决错误,手动拉取其他镜像,然后重新打tag来处理,这样就没问题了

[root@k8s-node-1 ~]# docker pull mirrorgooglecontainers/pause:3.1
3.1: Pulling from mirrorgooglecontainers/pause
67ddbfb20a22: Pull complete 
Digest: sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
Status: Downloaded newer image for mirrorgooglecontainers/pause:3.1
docker.io/mirrorgooglecontainers/pause:3.1
[root@k8s-node-1 ~]# docker images
REPOSITORY                     TAG       IMAGE ID       CREATED       SIZE
mirrorgooglecontainers/pause   3.1       da86e6ba6ca1   5 years ago   742kB
[root@k8s-node-1 ~]# docker tag mirrorgooglecontainers/pause:3.1 kubernetes/pause:latest
[root@k8s-node-1 ~]# docker images
REPOSITORY                     TAG       IMAGE ID       CREATED       SIZE
kubernetes/pause               latest    da86e6ba6ca1   5 years ago   742kB
mirrorgooglecontainers/pause   3.1       da86e6ba6ca1   5 years ago   742kB

同理,k8s-node-2 一样处理,查看 pod 的状态看 flannel 是否启动成功

[root@k8s-master-1 cfg]# kubectl get pods -n kube-system -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
kube-flannel-ds-amd64-k77nl   1/1     Running   0          34m   192.168.137.17   k8s-node-2   <none>           <none>
kube-flannel-ds-amd64-s7b7b   1/1     Running   0          34m   192.168.137.16   k8s-node-1   <none>           <none>

Flannel 部署成功后,就可以看 Node 是否就绪:

[root@k8s-master-1 cfg]# kubectl get nodes -o wide
NAME         STATUS   ROLES    AGE     VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION           CONTAINER-RUNTIME
k8s-node-1   Ready    <none>   5h55m   v1.16.2   192.168.137.16   <none>        CentOS Linux 7 (Core)   3.10.0-1127.el7.x86_64   docker://24.0.6
k8s-node-2   Ready    <none>   5h39m   v1.16.2   192.168.137.17   <none>        CentOS Linux 7 (Core)   3.10.0-1127.el7.x86_64   docker://24.0.6

在 Node 上查看网络配置,可以看到多了一个 flannel.1 的虚拟网卡,这块网卡用于接收 Pod 的流量并转发出去。


⑥ 测试创建 Pod

例如创建一个 Nginx 服务:

[root@k8s-master-1 cfg]# kubectl create deployment web --image=nginx
deployment.apps/web created

查看 Pod 状态,容器被部署到k8s-node-1

[root@k8s-master-1 cfg]# kubectl get pods -o wide
NAME                  READY   STATUS              RESTARTS   AGE   IP       NODE         NOMINATED NODE   READINESS GATES
web-d86c95cc9-wcjp6   0/1     ContainerCreating   0          32s   <none>   k8s-node-1   <none>           <none>

在对应的k8s-node-1节点上可以看到部署的容器:

[root@k8s-node-1 soft]# docker ps -a
CONTAINER ID   IMAGE                     COMMAND                   CREATED              STATUS                      PORTS     NAMES
7068e2c4b1bd   nginx                     "/docker-entrypoint.…"   About a minute ago   Up About a minute                     k8s_nginx_web-d86c95cc9-wcjp6_default_08f51dcd-0d1d-4a52-8cc1-9673a8ab2581_0
57c6c3eebb5f   kubernetes/pause:latest   "/pause"                  2 minutes ago        Up 2 minutes                          k8s_POD_web-d86c95cc9-wcjp6_default_08f51dcd-0d1d-4a52-8cc1-9673a8ab2581_0
f7b02f620190   ff281650a721              "/opt/bin/flanneld -…"   19 minutes ago       Up 19 minutes                         k8s_kube-flannel_kube-flannel-ds-amd64-s7b7b_kube-system_71fc2598-b9e6-401f-a4ba-795184198586_0
62f71925e927   quay.io/coreos/flannel    "cp -f /etc/kube-fla…"   19 minutes ago       Exited (0) 19 minutes ago             k8s_install-cni_kube-flannel-ds-amd64-s7b7b_kube-system_71fc2598-b9e6-401f-a4ba-795184198586_0
2bc5e3a5f596   kubernetes/pause:latest   "/pause"                  20 minutes ago       Up 20 minutes                         k8s_POD_kube-flannel-ds-amd64-s7b7b_kube-system_71fc2598-b9e6-401f-a4ba-795184198586_0

容器创建成功后,再在 Node 上查看网络配置,又多了一块 cni0 的虚拟网卡,cni0 用于 pod 本地通信使用



暴露端口并访问 Nginx:

[root@k8s-master-1 cfg]# kubectl expose deployment web --port=80 --type=NodePort
service/web exposed
[root@k8s-master-1 cfg]# kubectl get pods,svc
NAME                      READY   STATUS    RESTARTS   AGE
pod/web-d86c95cc9-wcjp6   1/1     Running   0          9m35s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP        8h
service/web          NodePort    10.0.0.141   <none>        80:31896/TCP   15s
[root@k8s-master-1 cfg]# curl k8s-node-1:31896
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

7、部署内部 DNS 服务

在Kubernetes集群推荐使用Service Name作为服务的访问地址,因此需要一个Kubernetes集群范围的DNS服务实现从Service Name到Cluster IP的解析,这就是Kubernetes基于DNS的服务发现功能。

① 部署 CoreDNS

[root@k8s-master-1 cfg]# cat coredns.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: coredns
  namespace: kube-system
  labels:
      kubernetes.io/cluster-service: "true"
      addonmanager.kubernetes.io/mode: Reconcile
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
    addonmanager.kubernetes.io/mode: Reconcile
  name: system:coredns
rules:
- apiGroups:
  - ""
  resources:
  - endpoints
  - services
  - pods
  - namespaces
  verbs:
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
    addonmanager.kubernetes.io/mode: EnsureExists
  name: system:coredns
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:coredns
subjects:
- kind: ServiceAccount
  name: coredns
  namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
  labels:
      addonmanager.kubernetes.io/mode: EnsureExists
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            upstream
            fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        proxy . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "CoreDNS"
spec:
  # replicas: not specified here:
  # 1. In order to make Addon Manager do not reconcile this replicas parameter.
  # 2. Default is 1.
  # 3. Will be tuned in real time if DNS horizontal auto-scaling is turned on.
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
      annotations:
        seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
    spec:
      serviceAccountName: coredns
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
        - key: "CriticalAddonsOnly"
          operator: "Exists"
      containers:
      - name: coredns
        image: coredns/coredns:1.2.6
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        args: [ "-conf", "/etc/coredns/Corefile" ]
        volumeMounts:
        - name: config-volume
          mountPath: /etc/coredns
          readOnly: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9153
          name: metrics
          protocol: TCP
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - all
          readOnlyRootFilesystem: true
      dnsPolicy: Default
      volumes:
        - name: config-volume
          configMap:
            name: coredns
            items:
            - key: Corefile
              path: Corefile
---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "CoreDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.0.0.2 
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

注意如下 clusterIP 一定要与 kubelet-config.yaml 中的 clusterDNS 保持一致


部署 CoreDNS:

[root@k8s-master-1 cfg]# kubectl apply -f coredns.yaml
serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created

② 验证 CoreDNS

创建 busybox 服务:

cat > busybox.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  dnsPolicy: ClusterFirst
  containers:
  - name: busybox
    image: busybox:1.28.4
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
EOF

kubectl apply -f busybox.yaml

验证是否安装成功:

kubectl exec -ti busybox sh

提示错误:

[root@k8s-master-1 cfg]# kubectl exec -ti pod/busybox sh
error: unable to upgrade connection: Forbidden (user=kubernetes, verb=create, resource=nodes, subresource=proxy)

权限问题,参考文章 https://www.kancloud.cn/idzqj/customer/1873079

[root@k8s-master-1 cfg]# kubectl create clusterrolebinding kube-apiserver:kubelet-apis --clusterrole=system:kubelet-api-admin --user kubernetes
clusterrolebinding.rbac.authorization.k8s.io/kube-apiserver:kubelet-apis created
[root@k8s-master-1 cfg]# kubectl exec -ti pod/busybox sh
/ # nslookup web
Server:    10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local

Name:      web
Address 1: 10.0.0.141 web.default.svc.cluster.local
/ # exit

八、集群搭建 —— 部署 Dashboard

K8S 提供了一个 Web 版 Dashboard,用户可以用 dashboard 部署容器化的应用、监控应用的状态,能够创建和修改各种 K8S 资源,比如 Deployment、Job、DaemonSet 等。用户可以 Scale Up/Down Deployment、执行 Rolling Update、重启某个 Pod 或者通过向导部署新的应用。Dashboard 能显示集群中各种资源的状态以及日志信息。Kubernetes Dashboard 提供了 kubectl 的绝大部分功能。

1、部署 K8S Dashboard

通过此地址下载 dashboard yaml文件:https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta5/aio/deploy/recommended.yaml
因为本次kubernetes版本对应的是1.16.2,所以dashboard需要是这个版本对应匹配的版本【v2.0.0-beta5】,一定要对的上,否则会有其他奇奇怪怪的错误

下载下来之后,需更新如下内容:通过 Node 暴露端口访问 dashboard


namespace: kubernetes-dashboard 全部替换为 namespace: kube-system
部署 dashboard:

[root@k8s-master-1 cfg]# kubectl apply -f kubernetes-dashboard.yaml
namespace/kubernetes-dashboard unchanged
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard unchanged
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard configured
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created

查看是否部署成功:

[root@k8s-master-1 cfg]# kubectl get pods,svc -n kube-system
NAME                                             READY   STATUS    RESTARTS   AGE
pod/coredns-84d8b55f97-cdjwv                     1/1     Running   0          70m
pod/dashboard-metrics-scraper-76585494d8-lhlhl   1/1     Running   0          7m23s
pod/kube-flannel-ds-amd64-k77nl                  1/1     Running   0          6d
pod/kube-flannel-ds-amd64-s7b7b                  1/1     Running   0          6d
pod/kubernetes-dashboard-5c68c48b47-dfgth        1/1     Running   0          7m23s

NAME                                TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
service/dashboard-metrics-scraper   ClusterIP   10.0.0.3     <none>        8000/TCP        7m23s
service/kube-dns                    ClusterIP   10.0.0.2     <none>        53/UDP,53/TCP   70m
service/kubernetes-dashboard        NodePort    10.0.0.134   <none>        443:30005/TCP   17m

通过 https 访问 dashboard(ip 地址为 node1 或者node2):
如果提示



解决办法:在chrome该页面上,在当前页面(也就是上图页面)注意是在当前页面打开的时候点网页任意地方,不是输入框,直接输入thisisunsafe回车,直接就打开页面了


2、登录授权

Dashboard 支持 Kubeconfig 和 Token 两种认证方式,为了简化配置,我们通过配置文件为 Dashboard 默认用户赋予 admin 权限。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
EOF

授权:

[root@k8s-master-1 cfg]# kubectl apply -f kubernetes-adminuser.yaml
serviceaccount/admin-user created
clusterrolebinding.rbac.authorization.k8s.io/admin-user created

获取登录的 token:

[root@k8s-master-1 cfg]# kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk ' {print $1}')
Name:         admin-user-token-hrdpx
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: admin-user
              kubernetes.io/service-account.uid: 5d0b81f2-5b15-4ceb-97e1-f0b35d05b9ce

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1383 bytes
namespace:  11 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6InBXa2VKS0Y4TGpFUFFZN2M3c1M1djdNcDhEU09YZnZ4c1dYOENLRWd4ZG8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWhyZHB4Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1ZDBiODFmMi01YjE1LTRjZWItOTdlMS1mMGIzNWQwNWI5Y2UiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.BpsqqIRD6jUwGm9Luya_3Y38ApE__VqmOSQe9tBWMvJ6Uh5uJVyZrWT52mPpTudNJoGSAYqtVPuZIpBZzabyAGeC1BIt0unBHj6B0hEI2Yc6WetfPeHgPhmHQ6K_2W9VQKTW497ZZXuu1A5T-XI7ompWPWF0gC1ZQgZkeVoFaZvRLjrX02e0lFyP_EInL_Wss0-5gh2qDzY1vbocCUaR87Hq8dEpkTBhR9paRwqJaTOf5vtwpcS6I0dk2eivWVmVcDQ8a3s08xdQD1Fd4oRxlqKrMcK9BEFW6WTUjEqgrMCByn3T0fVr9J9rgUfH9WYDXqTUdJevDNeA_0n-7AXzog

通过token登录进 dashboard,就可以查看集群的信息:


九、集群搭建 —— 多 Master 部署

1、部署Master2组件

① 将 k8s-master-1 上相关文件拷贝到 k8s-master-2 上

创建k8s工作目录:

[root@k8s-master-2 ~]# mkdir -p /k8s/kubernetes
[root@k8s-master-2 ~]# mkdir -p /k8s/etcd
[root@k8s-master-2 ~]# mkdir -p /data/soft

拷贝 k8s 配置文件、执行文件、证书:
k8s-master-1 上执行

scp -r /k8s/kubernetes/{cfg,ssl,bin} root@k8s-master-2:/k8s/kubernetes
scp -r /data/soft/ root@k8s-master-2:/data/

k8s-master-2 上执行

cp -p /data/soft/kubernetes/server/bin/kubectl /usr/local/bin/

拷贝 etcd 证书:

[root@k8s-master-1 cfg]# scp -r /k8s/etcd/ssl root@k8s-master-2:/k8s/etcd

拷贝 k8s 服务的service文件:

[root@k8s-master-1 cfg]# scp /usr/lib/systemd/system/kube-* root@k8s-master-2:/usr/lib/systemd/system

② 修改 k8s-master-2 上的配置文件

修改 kube-apiserver.conf,修改IP为本机IP


③ 启动 k8s-master-2 组件

重新加载配置:

systemctl daemon-reload

启动 kube-apiserver:

systemctl start kube-apiserver
systemctl enable kube-apiserver

启动 kube-controller-manager:

systemctl start kube-controller-manager
systemctl enable kube-controller-manager

部署 kube-scheduler:

systemctl start kube-scheduler
systemctl enable kube-scheduler

④ 验证

[root@k8s-master-2 ~]# kubectl get nodes
NAME         STATUS   ROLES    AGE    VERSION
k8s-node-1   Ready    <none>   6d6h   v1.16.2
k8s-node-2   Ready    <none>   6d6h   v1.16.2
[root@k8s-master-2 ~]# kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
busybox               1/1     Running   1          107m
web-d86c95cc9-wcjp6   1/1     Running   0          6d

2、部署 Nginx 负载均衡
为了保证 k8s master 的高可用,将使用 k8s-lb-master 和 k8s-lb-backup 这两台机器来部署负载均衡。这里使用 nginx 做负载均衡器,下面分别在 k8s-lb-master 和 k8s-lb-backup 这两台机器上部署 nginx。

① gcc等环境安装,后续有些软件安装需要这些基础环境

# gcc安装:
yum install gcc-c++
# PCRE pcre-devel 安装:
yum install -y pcre pcre-devel
# zlib 安装:
yum install -y zlib zlib-devel
#OpenSSL 安装:
yum install -y openssl openssl-devel

② 安装nginx

rpm -ivh https://nginx.org/packages/rhel/7/x86_64/RPMS/nginx-1.16.1-1.el7.ngx.x86_64.rpm

③ apiserver 负载配置

vim /etc/nginx/nginx.conf

增加如下配置:

stream {
    log_format main '$remote_addr $upstream_addr - [$time_local] $status $upstream_bytes_sent';
    access_log /var/log/nginx/k8s-access.log main;

    upstream k8s-apiserver {
        server 192.168.137.14:6443;
        server 192.168.137.15:6443;
    }

    server {
        listen 6443;
        proxy_pass k8s-apiserver;
    }
}

④ 启动 nginx

systemctl start nginx
systemctl enable nginx

3、部署 KeepAlive
为了保证 nginx 的高可用,还需要部署 keepalive,keepalive 主要负责 nginx 的健康检查和故障转移。

① 分别在 k8s-lb-master 和 k8s-lb-backup 这两台机器上安装 keepalive

yum install keepalived -y

② master 启动 keepalived

修改 k8s-lb-master keepalived 配置文件

cat > /etc/keepalived/keepalived.conf <<'EOF'
global_defs {
   script_user root
   enable_script_security

   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id NGINX_MASTER
}

vrrp_script check_nginx {
   script "/etc/keepalived/check_nginx.sh" 
}

# vrrp实例
vrrp_instance VI_1 {
    state MASTER 
    interface eth0
    virtual_router_id 51 
    priority 100
    advert_int 1 
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.137.100/24 
    }
    track_script {
        check_nginx
    }
}
EOF

配置说明:

  • vrrp_script:用于健康检查nginx状态,如果nginx没有正常工作,就会进行故障漂移,使备节点接管VIP,这里通过 shell 脚本来检查nginx状态
  • state:keepalived 角色,主节点为 MASTER,备节点为 BACKUP
  • interface:接口,配置本地网卡名,keepalived 会将虚拟IP绑定到这个网卡上
  • virtual_router_id:#VRRP 路由ID实例,每个实例是唯一的
  • priority:优先级,备服务器设置90
  • advert_int:指定VRRP心跳包通告间隔时间,默认1秒
  • virtual_ipaddress:VIP,要与当前机器在同一网段,keepalived 会在网卡上附加这个IP,之后通过这个IP来访问Nginx,当nginx不可用时,会将此虚拟IP漂移到备节点上。

增加 check_nginx.sh 脚本,通过此脚本判断 nginx 是否正常:

cat > /etc/keepalived/check_nginx.sh <<'EOF'
#!/bin/bash
count=$(ps -ef | grep nginx | egrep -cv "grep|$$")
if [ "$count" -eq 0 ];then
    exit 1;
else 
    exit 0;
fi
EOF

增加可执行权限:

chmod +x /etc/keepalived/check_nginx.sh

启动 keepalived:

systemctl start keepalived
systemctl enable keepalived

③ backup 启动 keepalived

修改 k8s-lb-backup keepalived 配置文件

cat > /etc/keepalived/keepalived.conf <<'EOF'
global_defs {
   script_user root
   enable_script_security

   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id NGINX_BACKUP
}

vrrp_script check_nginx {
   script "/etc/keepalived/check_nginx.sh"
}

# vrrp实例
vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.137.100/24
    }
    track_script {
        check_nginx
    }
}
EOF

增加 check_nginx.sh 脚本:

cat > /etc/keepalived/check_nginx.sh <<'EOF'
#!/bin/bash
count=$(ps -ef | grep nginx | egrep -cv "grep|$$")
if [ "$count" -eq 0 ];then
    exit 1;
else 
    exit 0;
fi
EOF

增加可执行权限:

chmod +x /etc/keepalived/check_nginx.sh

启动 keepalived:

systemctl start keepalived
systemctl enable keepalived

注意:本地网卡名可以先通过 ip a查看下,按自己实际的来,我的虚拟机是eth0

④ 验证负载均衡

keepalived 已经将VIP附加到MASTER所在的网卡上

[root@k8s-lb-master ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0a:0e:0d brd ff:ff:ff:ff:ff:ff
    inet 192.168.137.18/24 brd 192.168.137.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet 192.168.137.100/24 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::fdca:50bb:5250:8fce/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

BACKUP节点上并没有

[root@k8s-lb-backup nginx]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0a:0e:0e brd ff:ff:ff:ff:ff:ff
    inet 192.168.137.19/24 brd 192.168.137.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::195d:5816:625e:75f/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

关闭 k8s-lb-master 上的nginx,可看到VIP已经不在了

[root@k8s-lb-master ~]# systemctl stop nginx
[root@k8s-lb-master ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0a:0e:0d brd ff:ff:ff:ff:ff:ff
    inet 192.168.137.18/24 brd 192.168.137.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::fdca:50bb:5250:8fce/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

可以看到已经漂移到备节点上了,如果再重启 MASTER 上的 Ngnix,VIP又会漂移到主节点上。

[root@k8s-lb-backup nginx]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0a:0e:0e brd ff:ff:ff:ff:ff:ff
    inet 192.168.137.19/24 brd 192.168.137.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet 192.168.137.100/24 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::195d:5816:625e:75f/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

访问虚拟IP还是可以访问的

[root@k8s-master-1 ~]# curl 192.168.137.100
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

4、Node节点连接VIP

① 修改 node 节点的配置文件中连接 k8s-master 的IP为VIP
node1

[root@k8s-node-1 cfg]# pwd
/k8s/kubernetes/cfg
[root@k8s-node-1 cfg]# grep 192 *
bootstrap.kubeconfig:    server: https://192.168.137.14:6443
kubelet.kubeconfig:    server: https://192.168.137.14:6443
kube-proxy.kubeconfig:    server: https://192.168.137.14:6443
[root@k8s-node-1 cfg]# sed -i 's#192.168.137.14#192.168.137.100#' *
[root@k8s-node-1 cfg]# grep 192 *
bootstrap.kubeconfig:    server: https://192.168.137.100:6443
kubelet.kubeconfig:    server: https://192.168.137.100:6443
kube-proxy.kubeconfig:    server: https://192.168.137.100:6443

② 重启 kubelet 和 kube-proxy

systemctl restart kubelet
systemctl restart kube-proxy

node2同样操作

③ 在 node 节点上访问VIP调用 apiserver 验证

Authorization 的token 是前面生成 token.csv 中的令牌。

[root@k8s-node-1 cfg]# ssh root@k8s-master-1 'cat /k8s/kubernetes/cfg/token.csv'
root@k8s-master-1's password: 
b9187da74c444ee060a1c6053eab97c7,kubelet-bootstrap,10001,"system:node-bootstrapper"
[root@k8s-node-1 cfg]# curl -k --header "Authorization: Bearer b9187da74c444ee060a1c6053eab97c7" https://192.168.137.100:6443/version
{
  "major": "1",
  "minor": "16",
  "gitVersion": "v1.16.2",
  "gitCommit": "c97fe5036ef3df2967d086711e6c0c405941e14b",
  "gitTreeState": "clean",
  "buildDate": "2019-10-15T19:09:08Z",
  "goVersion": "go1.12.10",
  "compiler": "gc",
  "platform": "linux/amd64"
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容