K8S学习笔记之二进制部署Kubernetes v1.13.4 高可用集群

时间:2020-12-05 01:41:03

0x00 概述

本次采用二进制文件方式部署,本文过程写成了更详细更多可选方案的ansible部署方案 https://github.com/zhangguanzhang/Kubernetes-ansible
和之前的步骤差不多都是和kubeadm步骤一样,不过这次所有kubelet全部走bootstrap不会像之前master上的kubelet生成证书,另外证书换成openssl生成

不建议使用secureCRT这个ssh软件复制本篇博客内容的命令,因为它的部分版本对包含多条命令的处理结果并不完美,可能很多命令不是预期结果

# 本文命令里有些是输出,不要乱粘贴输入(虽然也没影响)

# 本文命令全部是在k8s-m1上执行

# 本文很多步骤是选择其一,别啥都不看一路往下复制粘贴

# 如果某些步骤理解不了可以上下内容一起看来理解

# 本文后面的几个svc用了externalIPs,上生产的话必须用hostnetwork下然后VIP或者LB代替

# 本文的HA是vip,生产和云上可以用LB和SLB,不过阿里的SLB四层有问题(不支持回源),可以每个node上代理127.0.0.1的某个port分摊在所有apiserver的port上,aws的SLB正常

# master节点一定要kube-proxy和calico或者flannel,kube-proxy是维持svc的ip到pod的ip的负载均衡,而你流量想到pod的ip需要calico或者flannel组件的overlay网络下才可以,后续学到APIService和CRD的时候,APIService如果选中了svc,kube-apiserver会把这个APISerivce的请求代理到选中的svc上,最后流量流到svc选中的pod,该pod要处理请求然后回应,这个时候就是kube-apiserver解析svc的名字得到svc的ip,然后kube-proxy定向到pod的ip,calico或者flannel把包发到目标机器上,这个时候如果kube-proxy和calico或者flannel没有那你创建的APISerivce就没用了。apiserver的路由聚合没试过不知道可行不可行,本文中的metrics-server就是这样的工作流程,所以建议master也跑pod,不然后续某些CRD用不了

本次安装的版本

# Kubernetes v1.13.4
# CNI v0.7.4
# Etcd v3.3.12
# Flannel v0.11.0 或者 Calico v3.4
# Docker CE 18.06.03

在官方的支持版本里好像没说1.13.4支持18.09,保险起见我这里使用的是18.06.03

本次部署的网络信息

# Cluster IP CIDR: 10.244.0.0/16
# Service Cluster IP CIDR: 10.96.0.0/12
# Service DNS IP: 10.96.0.10
# DNS DN: cluster.local

上面不建议改

# Kubernetes API VIP: 192.168.26.166
# Kubernetes Ingress VIP: 192.168.26.167
# 如果单台master的话Kubernetes API VIP写master的ip即可,单台就别搞啥HA了
# 单台master的话所有复制到其他mster的操作都忽略即可

K8S学习笔记之二进制部署Kubernetes v1.13.4 高可用集群

节点信息

IP Hostname CPU Memory
192.168.26.135 K8S-M1 4 8G
192.168.26.136 K8S-M2 4 8G
192.168.26.137 K8S-M3 4 8G
192.168.26.138 K8S-N1 2 4G
192.168.26.139 K8S-N2 2 4G

另外VIP为192.168.26.144,由所有master节点keepalived+haproxy来选择VIP的归属保持高可用(VIP的IP地址是个虚拟IP,不是实例机器)

# 所有操作全部用root使用者进行
# 高可用一般建议大于等于3台的奇数台,我使用3台master来做高可用

0x01 事前准备

所有机器彼此网路互通,并且k8s-m1SSH登入其他节点为passwdless(如果不互信可以后面的操作)。
所有防火墙与SELinux 已关闭,iptables注意配置。如CentOS:否则后续 K8S 挂载目录时可能报错 Permission denied
# systemctl disable --now firewalld NetworkManager
# setenforce 0
# sed -ri '/^[^#]*SELINUX=/s#=.+$#=disabled#' /etc/selinux/config
关闭 dnsmasq (可选)
linux 系统开启了 dnsmasq 后(如 GUI 环境),将系统 DNS Server 设置为 127.0.0.1,这会导致 docker 容器无法解析域名,需要关闭它
# systemctl disable --now dnsmasq

Kubernetes v1.8+要求关闭系统Swap,若不关闭则需要修改kubelet设定参数( –fail-swap-on 设置为 false 来忽略 swap on),在所有机器使用以下指令关闭swap并注释掉/etc/fstab中swap的行:
# swapoff -a && sysctl -w vm.swappiness=0
# sed -ri '/^[^#]*swap/s@^@#@' /etc/fstab
如果是centos的话不想升级后面的最新内核,可以此时升级到保守内核,去掉update的--exclude即可
# yum install epel-release -y
# yum install wget git jq psmisc socat -y
# yum update -y --exclude=kernel*
如果上面yum update没有加--exclude=kernel*就重启下加载保守内核
# reboot
因为目前市面上包管理下内核版本会很低,安装docker后无论centos还是ubuntu会有如下bug,4.15的内核依然存在
kernel:unregister_netdevice: waiting for lo to become free. Usage count = 1
所以建议先升级内核

perl是内核的依赖包,如果没有就安装下
# [ ! -f /usr/bin/perl ] && yum install perl -y
升级内核需要使用 elrepo 的yum 源,首先我们导入 elrepo 的 key并安装 elrepo 源
# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
# rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
查看可用的内核
# yum --disablerepo="*" --enablerepo="elrepo-kernel" list available  --showduplicates
# 在yum的ELRepo源中,mainline 为最新版本的内核,安装kernel

ipvs依赖于nf_conntrack_ipv4内核模块,4.19包括之后内核里改名为nf_conntrack,1.13.1之前的kube-proxy的代码里没有加判断一直用的nf_conntrack_ipv4,好像是1.13.1后的kube-proxy代码里增加了判断,我测试了是会去load nf_conntrack使用ipvs正常

下面链接可以下载到其他归档版本的

下面是ml的内核和上面归档内核版本任选其一的安装方法

01. 自选版本内核安装方法

# export Kernel_Version=4.18.9-1
# wget http://mirror.rc.usf.edu/compute_lock/elrepo/kernel/el7/x86_64/RPMS/kernel-ml{,-devel}-${Kernel_Version}.el7.elrepo.x86_64.rpm
# yum localinstall -y kernel-ml*

02. 最新内核安装(也是我使用的)

# yum --disablerepo="*" --enablerepo="elrepo-kernel" list available  --showduplicates | grep -Po '^kernel-ml.x86_64\s+\K\S+(?=.el7)'
# yum --disablerepo="*" --enablerepo=elrepo-kernel install -y kernel-ml{,-devel}

修改内核启动顺序,默认启动的顺序应该为1,升级以后内核是往前面插入,为0(如果每次启动时需要手动选择哪个内核,该步骤可以省略)

# grub2-set-default  0 && grub2-mkconfig -o /etc/grub2.cfg

使用下面命令看看确认下是否启动默认内核指向上面安装的内核

# grubby --default-kernel
docker官方的内核检查脚本建议(RHEL7/CentOS7: User namespaces disabled; add 'user_namespace.enable=1' to boot command line),使用下面命令开启
# grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
重启加载新内核
# reboot
所有机器安装ipvs(ipvs性能甩iptables几条街并且排错更直观)
在每台机器上安装依赖包:
CentOS:
# yum install ipvsadm ipset sysstat conntrack libseccomp -y
Ubuntu:
# sudo apt-get install -y wget git conntrack ipvsadm ipset jq sysstat curl iptables libseccomp
所有机器选择需要开机加载的内核模块,以下是 ipvs 模式需要加载的模块并设置开机自动加载
# :> /etc/modules-load.d/ipvs.conf
module=(
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
br_netfilter
)
for kernel_module in ${module[@]};do
/sbin/modinfo -F filename $kernel_module |& grep -qv ERROR && echo $kernel_module >> /etc/modules-load.d/ipvs.conf || :
done
# systemctl enable --now systemd-modules-load.service

上面如果systemctl enable命令报错可以systemctl status -l systemd-modules-load.service看看哪个内核模块加载不了,在/etc/modules-load.d/ipvs.conf里注释掉它再enable试试

所有机器需要设定/etc/sysctl.d/k8s.conf的系统参数。
# cat <<EOF > /etc/sysctl.d/k8s.conf
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 10
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
net.ipv4.neigh.default.gc_stale_time = 120
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2
net.ipv4.ip_forward = 1
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_synack_retries = 2
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.netfilter.nf_conntrack_max = 2310720
fs.inotify.max_user_watches=89100
fs.may_detach_mounts = 1
fs.file-max = 52706963
fs.nr_open = 52706963
net.bridge.bridge-nf-call-arptables = 1
vm.swappiness = 0
vm.overcommit_memory=1
vm.panic_on_oom=0
EOF # https://github.com/moby/moby/issues/31208
# ipvsadm -l --timout
# 修复ipvs模式下长连接timeout问题 小于900即可 # sysctl --system
检查系统内核和模块是否适合运行 docker (仅适用于 linux 系统)
# curl https://raw.githubusercontent.com/docker/docker/master/contrib/check-config.sh > check-config.sh
# bash ./check-config.sh
所有机器需要安装Docker CE 版本的容器引擎,推荐使用年份命名版本的docker ce:
在官方查看K8s支持的docker版本 https://github.com/kubernetes/kubernetes 里进对应版本的changelog里搜The list of validated docker versions remain
这里利用docker的官方安装脚本来安装,可以使用yum list --showduplicates 'docker-ce查询可用的docker版本,选择你要安装的k8s版本支持的docker版本即可,这里我使用的是18.06.03
# export VERSION=18.06
# curl -fsSL "https://get.docker.com/" | bash -s -- --mirror Aliyun
所有机器配置加速源并配置docker的启动参数使用systemd,使用systemd是官方的建议,详见 https://kubernetes.io/docs/setup/cri/
# mkdir -p /etc/docker/
# cat>/etc/docker/daemon.json<<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://fz5yth0r.mirror.aliyuncs.com"],
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": ""
}
}
EOF
设置docker开机启动,CentOS安装完成后docker需要手动设置docker命令补全:
# yum install -y epel-release bash-completion && cp /usr/share/bash-completion/completions/docker /etc/bash_completion.d/
# systemctl enable --now docker
切记所有机器需要自行设定ntp,否则不只HA下apiserver通信有问题,各种千奇百怪的问题。

此时可以关机做个快照

0x02 使用环境变量声明集群信息

根据自己环境声明用到的变量,后续操作依赖于环境变量,所以断开了ssh后要重新声明下(主要是ip和一些信息,路径最好别改)

下面键是主机的hostname,而的值是主机的IP。默认Kubelet向集群注册是带hostname上去的,或者选项--hostname-override去设置注册的名字。后面没有用--hostname-override,我是偷懒直接使用hostname,生产力建议用--hostname-override固定。所有机器同理

haproxy每台上占据8443端口去负载到每台master上的api-server6443端口
然后keepalived会保证vip飘在可用的master
所有管理组件kubelet都会去访问vip:8443确保了即使down掉一台master也能访问到apiserver
云上的话选择熟练的LB来代替掉haproxy和keepalived即可
VIP选同一个局域网的没用过IP来使用即可

建议下面写进一个文件,这样断开了ssh也能source一下加载

# 声明集群成员信息
declare -A MasterArray otherMaster NodeArray AllNode Other
MasterArray=(['k8s-m1']=192.168.26.135 ['k8s-m2']=192.168.26.136 ['k8s-m3']=192.168.26.137)
otherMaster=(['k8s-m2']=192.168.26.136 ['k8s-m3']=192.168.26.137)
NodeArray=(['k8s-n1']=192.168.26.138 ['k8s-n2']=192.168.26.139)
# 下面复制上面的信息粘贴即可
AllNode=(['k8s-m1']=192.168.26.135 ['k8s-m2']=192.168.26.136 ['k8s-m3']=192.168.26.137 ['k8s-n1']=192.168.26.138 ['k8s-n2']=192.168.26.139)
Other=(['k8s-m2']=192.168.26.136 ['k8s-m3']=192.168.26.137 ['k8s-n1']=192.168.26.138 ['k8s-n2']=192.169.26.139) export VIP=192.168.26.144 [ "${#MasterArray[@]}" -eq 1 ] && export VIP=${MasterArray[@]} || export API_PORT=8443
export KUBE_APISERVER=https://${VIP}:${API_PORT:=6443} #声明需要安装的的k8s版本
export KUBE_VERSION=v1.13.4 # 网卡名 此处需要根据实际网卡修改
export interface=ens32 # cni
export CNI_URL="https://github.com/containernetworking/plugins/releases/download"
export CNI_VERSION=v0.7.4
# etcd
export ETCD_version=v3.3.12

k8s-m1登陆其他机器要免密(不然就后面文章手动输入)或者在k8s-m1安装sshpass后使用别名来让ssh和scp不输入密码,zhangguanzhang为所有机器密码

# yum install sshpass -y
# alias ssh='sshpass -p zhangguanzhang ssh -o StrictHostKeyChecking=no'
# alias scp='sshpass -p zhangguanzhang scp -o StrictHostKeyChecking=no'

设置所有机器的hostname,有些人喜欢用master1就自己改,我的是下面的k8s-m1,所有机器都要设置

for name in ${!AllNode[@]};do
echo "--- $name ${AllNode[$name]} ---"
ssh ${AllNode[$name]} "hostnamectl set-hostname $name"
done

首先在k8s-m1通过git获取部署要用到的二进制配置文件和yml

# git clone https://github.com/zhangguanzhang/k8s-manual-files.git ~/k8s-manual-files -b v1.13.4
# cd ~/k8s-manual-files/

k8s-m1下载Kubernetes二进制文件后分发到其他机器

可通过下面命令查询所有stable版本(耐心等待,请确保能访问到github)

# curl -s https://zhangguanzhang.github.io/bash/pull.sh | bash -s search gcr.io/google_containers/kube-apiserver-amd64/ | grep -P 'v[\d.]+$' | sort -t '.' -n -k 2

无越墙工具的,我已把所有二进制文件上传到dockerhub了,详情见 k8s_bin-docker_cp

01. 使用下面命令可以不越墙下载

# cd ~/k8s-manual-files/
# docker pull zhangguanzhang/k8s_bin:$KUBE_VERSION-full
# docker run --rm -d --name temp zhangguanzhang/k8s_bin:$KUBE_VERSION-full sleep 10
# docker cp temp:/kubernetes-server-linux-amd64.tar.gz .
# tar -zxvf kubernetes-server-linux-amd64.tar.gz --strip-components=3 -C /usr/local/bin kubernetes/server/bin/kube{let,ctl,-apiserver,-controller-manager,-scheduler,-proxy}

02. 有越墙工具的,官网下载地址使用下面命令

# curl  https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/kubernetes-server-linux-amd64.tar.gz > kubernetes-server-linux-amd64.tar.gz
# tar -zxvf kubernetes-server-linux-amd64.tar.gz --strip-components=3 -C /usr/local/bin kubernetes/server/bin/kube{let,ctl,-apiserver,-controller-manager,-scheduler,-proxy

分发master相关组件二进制文件到其他master上

for NODE in "${!otherMaster[@]}"; do
echo "--- $NODE ${otherMaster[$NODE]} ---"
scp /usr/local/bin/kube{let,ctl,-apiserver,-controller-manager,-scheduler,-proxy} ${otherMaster[$NODE]}:/usr/local/bin/
done

分发node的kubernetes二进制文件

for NODE in "${!NodeArray[@]}"; do
echo "--- $NODE ${NodeArray[$NODE]} ---"
scp /usr/local/bin/kube{let,-proxy} ${NodeArray[$NODE]}:/usr/local/bin/
done

k81-m1下载Kubernetes CNI 二进制文件并分发

分发cni文件到other

# mkdir -p /opt/cni/bin
# wget "${CNI_URL}/${CNI_VERSION}/cni-plugins-amd64-${CNI_VERSION}.tgz"
# tar -zxf cni-plugins-amd64-${CNI_VERSION}.tgz -C /opt/cni/bin # 分发cni文件
for NODE in "${!Other[@]}"; do
echo "--- $NODE ${Other[$NODE]} ---"
ssh ${Other[$NODE]} 'mkdir -p /opt/cni/bin'
scp /opt/cni/bin/* ${Other[$NODE]}:/opt/cni/bin/
done

0x03 建立集群CA keys 与Certificates

在这个部分,将需要产生多个元件的Certificates,这包含Etcd、Kubernetes 元件等,并且每个集群都会有一个根数位凭证认证机构(Root Certificate Authority)被用在认证API Server 与Kubelet 端的凭证。

PS这边要注意CA JSON档的CN(Common Name)与O(Organization)等内容是会影响Kubernetes元件认证的。
CN Common Name, apiserver 会从证书中提取该字段作为请求的用户名 (User Name)
O Organization, apiserver 会从证书中提取该字段作为请求用户所属的组 (Group)
CA (Certificate Authority) 是自签名的根证书,用来签名后续创建的其它证书。
本文使用openssl创建所有证书

etcd的话推荐下官方的在线工具,有兴趣可以去试试 http://play.etcd.io

准备openssl 证书配置文件

注入ip信息

# mkdir -p /etc/kubernetes/pki/etcd
# sed -i "/IP.2/a IP.3 = $VIP" ~/k8s-manual-files/pki/openssl.cnf
# sed -ri '/IP.3/r '<( paste -d '' <(seq -f 'IP.%g = ' 4 $[${#AllNode[@]}+3]) <(xargs -n1<<<${AllNode[@]} | sort) ) ~/k8s-manual-files/pki/openssl.cnf
# sed -ri '$r '<( paste -d '' <(seq -f 'IP.%g = ' 2 $[${#MasterArray[@]}+1]) <(xargs -n1<<<${MasterArray[@]} | sort) ) ~/k8s-manual-files/pki/openssl.cnf
# cp ~/k8s-manual-files/pki/openssl.cnf /etc/kubernetes/pki/
# cd /etc/kubernetes/pki

生成证书

path Default CN description
ca.crt,key kubernetes-ca Kubernetes general CA
etcd/ca.crt,key etcd-ca For all etcd-related functions
front-proxy-ca.crt,key kubernetes-front-proxy-ca For the front-end proxy

kubernetes-ca

# openssl genrsa -out ca.key 2048
# openssl req -x509 -new -nodes -key ca.key -config openssl.cnf -subj "/CN=kubernetes-ca" -extensions v3_ca -out ca.crt -days 10000

etcd-ca

# openssl genrsa -out etcd/ca.key 2048
# openssl req -x509 -new -nodes -key etcd/ca.key -config openssl.cnf -subj "/CN=etcd-ca" -extensions v3_ca -out etcd/ca.crt -days 10000

front-proxy-ca

# openssl genrsa -out front-proxy-ca.key 2048
# openssl req -x509 -new -nodes -key front-proxy-ca.key -config openssl.cnf -subj "/CN=kubernetes-ca" -extensions v3_ca -out front-proxy-ca.crt -days 10000

生成所有的证书信息

Default CN Parent CA O (in Subject) kind
kube-etcd etcd-ca   server, client
kube-etcd-peer etcd-ca   server, client
kube-etcd-healthcheck-client etcd-ca   client
kube-apiserver-etcd-client etcd-ca system:masters client
kube-apiserver kubernetes-ca   server
kube-apiserver-kubelet-client kubernetes-ca system:masters client
front-proxy-client kubernetes-front-proxy-ca   client

证书路径

Default CN recommend key path recommended cert path command key argument cert argument
etcd-ca   etcd/ca.crt kube-apiserver   –etcd-cafile
etcd-client apiserver-etcd-client.key apiserver-etcd-client.crt kube-apiserver –etcd-keyfile –etcd-certfile
kubernetes-ca   ca.crt kube-apiserver   –client-ca-file
kube-apiserver apiserver.key apiserver.crt kube-apiserver –tls-private-key-file –tls-cert-file
apiserver-kubelet-client   apiserver-kubelet-client.crt kube-apiserver   –kubelet-client-certificate
front-proxy-ca   front-proxy-ca.crt kube-apiserver   –requestheader-client-ca-file
front-proxy-client front-proxy-client.key front-proxy-client.crt kube-apiserver –proxy-client-key-file –proxy-client-cert-file
etcd-ca   etcd/ca.crt etcd   –trusted-ca-file, –peer-trusted-ca-file
kube-etcd etcd/server.key etcd/server.crt etcd –key-file –cert-file
kube-etcd-peer etcd/peer.key etcd/peer.crt etcd –peer-key-file –peer-cert-file
etcd-ca   etcd/ca.crt etcdctl   –cacert
kube-etcd-healthcheck-client etcd/healthcheck-client.key etcd/healthcheck-client.crt etcdctl –key –cert

生成证书

apiserver-etcd-client

# openssl genrsa -out apiserver-etcd-client.key 2048
# openssl req -new -key apiserver-etcd-client.key -subj "/CN=apiserver-etcd-client/O=system:masters" -out apiserver-etcd-client.csr
# openssl x509 -in apiserver-etcd-client.csr -req -CA etcd/ca.crt -CAkey etcd/ca.key -CAcreateserial -extensions v3_req_etcd -extfile openssl.cnf -out apiserver-etcd-client.crt -days 10000

kube-etcd

# openssl genrsa -out etcd/server.key 2048
# openssl req -new -key etcd/server.key -subj "/CN=etcd-server" -out etcd/server.csr
# openssl x509 -in etcd/server.csr -req -CA etcd/ca.crt -CAkey etcd/ca.key -CAcreateserial -extensions v3_req_etcd -extfile openssl.cnf -out etcd/server.crt -days 10000

kube-etcd-peer

# openssl genrsa -out etcd/peer.key 2048
# openssl req -new -key etcd/peer.key -subj "/CN=etcd-peer" -out etcd/peer.csr
# openssl x509 -in etcd/peer.csr -req -CA etcd/ca.crt -CAkey etcd/ca.key -CAcreateserial -extensions v3_req_etcd -extfile openssl.cnf -out etcd/peer.crt -days 10000

kube-etcd-healthcheck-client

# openssl genrsa -out etcd/healthcheck-client.key 2048
# openssl req -new -key etcd/healthcheck-client.key -subj "/CN=etcd-client" -out etcd/healthcheck-client.csr
# openssl x509 -in etcd/healthcheck-client.csr -req -CA etcd/ca.crt -CAkey etcd/ca.key -CAcreateserial -extensions v3_req_etcd -extfile openssl.cnf -out etcd/healthcheck-client.crt -days 10000

kube-apiserver

# openssl genrsa -out apiserver.key 2048
# openssl req -new -key apiserver.key -subj "/CN=kube-apiserver" -config openssl.cnf -out apiserver.csr
# openssl x509 -req -in apiserver.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 10000 -extensions v3_req_apiserver -extfile openssl.cnf -out apiserver.crt

apiserver-kubelet-client

# openssl genrsa -out  apiserver-kubelet-client.key 2048
# openssl req -new -key apiserver-kubelet-client.key -subj "/CN=apiserver-kubelet-client/O=system:masters" -out apiserver-kubelet-client.csr
# openssl x509 -req -in apiserver-kubelet-client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 10000 -extensions v3_req_client -extfile openssl.cnf -out apiserver-kubelet-client.crt

front-proxy-client

# openssl genrsa -out  front-proxy-client.key 2048
# openssl req -new -key front-proxy-client.key -subj "/CN=front-proxy-client" -out front-proxy-client.csr
# openssl x509 -req -in front-proxy-client.csr -CA front-proxy-ca.crt -CAkey front-proxy-ca.key -CAcreateserial -days 10000 -extensions v3_req_client -extfile openssl.cnf -out front-proxy-client.crt

kube-scheduler

# openssl genrsa -out  kube-scheduler.key 2048
# openssl req -new -key kube-scheduler.key -subj "/CN=system:kube-scheduler" -out kube-scheduler.csr
# openssl x509 -req -in kube-scheduler.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 10000 -extensions v3_req_client -extfile openssl.cnf -out kube-scheduler.crt

sa.pub sa.key

# openssl genrsa -out  sa.key 2048
# openssl ecparam -name secp521r1 -genkey -noout -out sa.key
# openssl ec -in sa.key -outform PEM -pubout -out sa.pub
# openssl req -new -sha256 -key sa.key -subj "/CN=system:kube-controller-manager" -out sa.csr
# openssl x509 -req -in sa.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 10000 -extensions v3_req_client -extfile openssl.cnf -out sa.crt

admin(kubectl)

# openssl genrsa -out  admin.key 2048
# openssl req -new -key admin.key -subj "/CN=kubernetes-admin/O=system:masters" -out admin.csr
# openssl x509 -req -in admin.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 10000 -extensions v3_req_client -extfile openssl.cnf -out admin.crt

清理 csr srl

# find . -name "*.csr" -o -name "*.srl"|xargs  rm -f

0x04 利用证书生成组件的kubeconfig

filename credential name Default CN O (in Subject)
admin.kubeconfig default-admin kubernetes-admin system:masters
controller-manager.kubeconfig default-controller-manager system:kube-controller-manager  
scheduler.kubeconfig default-manager system:kube-scheduler

kube-controller-manager

CLUSTER_NAME="kubernetes"
KUBE_USER="system:kube-controller-manager"
KUBE_CERT="sa"
KUBE_CONFIG="controller-manager.kubeconfig" # 设置集群参数
kubectl config set-cluster ${CLUSTER_NAME} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置客户端认证参数
kubectl config set-credentials ${KUBE_USER} \
--client-certificate=/etc/kubernetes/pki/${KUBE_CERT}.crt \
--client-key=/etc/kubernetes/pki/${KUBE_CERT}.key \
--embed-certs=true \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置上下文参数
kubectl config set-context ${KUBE_USER}@${CLUSTER_NAME} \
--cluster=${CLUSTER_NAME} \
--user=${KUBE_USER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置当前使用的上下文
kubectl config use-context ${KUBE_USER}@${CLUSTER_NAME} --kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 查看生成的配置文件
kubectl config view --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

kube-scheduler

CLUSTER_NAME="kubernetes"
KUBE_USER="system:kube-scheduler"
KUBE_CERT="kube-scheduler"
KUBE_CONFIG="scheduler.kubeconfig" # 设置集群参数
kubectl config set-cluster ${CLUSTER_NAME} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置客户端认证参数
kubectl config set-credentials ${KUBE_USER} \
--client-certificate=/etc/kubernetes/pki/${KUBE_CERT}.crt \
--client-key=/etc/kubernetes/pki/${KUBE_CERT}.key \
--embed-certs=true \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置上下文参数
kubectl config set-context ${KUBE_USER}@${CLUSTER_NAME} \
--cluster=${CLUSTER_NAME} \
--user=${KUBE_USER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置当前使用的上下文
kubectl config use-context ${KUBE_USER}@${CLUSTER_NAME} --kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 查看生成的配置文件
kubectl config view --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

admin(kubectl)

CLUSTER_NAME="kubernetes"
KUBE_USER="kubernetes-admin"
KUBE_CERT="admin"
KUBE_CONFIG="admin.kubeconfig" # 设置集群参数
kubectl config set-cluster ${CLUSTER_NAME} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置客户端认证参数
kubectl config set-credentials ${KUBE_USER} \
--client-certificate=/etc/kubernetes/pki/${KUBE_CERT}.crt \
--client-key=/etc/kubernetes/pki/${KUBE_CERT}.key \
--embed-certs=true \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置上下文参数
kubectl config set-context ${KUBE_USER}@${CLUSTER_NAME} \
--cluster=${CLUSTER_NAME} \
--user=${KUBE_USER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置当前使用的上下文
kubectl config use-context ${KUBE_USER}@${CLUSTER_NAME} --kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 查看生成的配置文件
kubectl config view --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

分发证书

分发到 kubeconfig 及证书其他 master 节点

for NODE in "${!otherMaster[@]}"; do
echo "--- $NODE ${otherMaster[$NODE]} ---"
scp -r /etc/kubernetes ${otherMaster[$NODE]}:/etc
done

0x05 配置ETCD

Etcd 二进制文件

# Etcd:用来保存集群所有状态的 Key/Value 存储系统,所有 Kubernetes 组件会通过 API Server 来跟 Etcd 进行沟通从而保存或读取资源状态。
# 这边etcd跑在master上,有条件的可以单独几台机器跑,不过得会配置apiserver指向etcd集群

etcd所有标准版本可以在下面url查看

https://github.com/etcd-io/etcd/releases

k8s-m1上下载etcd的二进制文件,单台的话建议使用v3.1.9因为有bug详情见我github

# [ "${#MasterArray[@]}" -eq 1 ] && ETCD_version=v3.1.9 || :
# cd ~/k8s-manual-files

如果下面直接下载失败的话一样使用骚套路:docker拉镜像后cp出来

# wget https://github.com/etcd-io/etcd/releases/download/${ETCD_version}/etcd-${ETCD_version}-linux-amd64.tar.gz

# tar -zxvf etcd-${ETCD_version}-linux-amd64.tar.gz --strip-components=1 -C /usr/local/bin etcd-${ETCD_version}-linux-amd64/etcd{,ctl}
#------- #上面被墙了可以使用骚套路
docker pull quay.io/coreos/etcd:$ETCD_version
docker run --rm -d --name temp quay.io/coreos/etcd:$ETCD_version sleep 10
docker cp temp:/usr/local/bin/etcd /usr/local/bin
docker cp temp:/usr/local/bin/etcdctl /usr/local/bin

k8s-m1上分发etcd的二进制文件到其他master上

for NODE in "${!otherMaster[@]}"; do
echo "--- $NODE ${otherMaster[$NODE]} ---"
scp /usr/local/bin/etcd* ${otherMaster[$NODE]}:/usr/local/bin/
done

k8s-m1上配置etcd配置文件并分发相关文件
配置文件路径为/etc/etcd/etcd.config.yml,参考官方 https://github.com/etcd-io/etcd/blob/master/etcd.conf.yml.sample
注入基础变量

# cd ~/k8s-manual-files/master/
# etcd_servers=$( xargs -n1<<<${MasterArray[@]} | sort | sed 's#^#https://#;s#$#:2379#;$s#\n##' | paste -d, -s - )
# etcd_initial_cluster=$( for i in ${!MasterArray[@]};do echo $i=https://${MasterArray[$i]}:2380; done | sort | paste -d, -s - )
# sed -ri "/initial-cluster:/s#'.+'#'${etcd_initial_cluster}'#" etc/etcd/config.yml

分发systemd和配置文件

for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
ssh ${MasterArray[$NODE]} "mkdir -p /etc/etcd /var/lib/etcd"
scp systemd/etcd.service ${MasterArray[$NODE]}:/usr/lib/systemd/system/etcd.service
scp etc/etcd/config.yml ${MasterArray[$NODE]}:/etc/etcd/etcd.config.yml
ssh ${MasterArray[$NODE]} "sed -i "s/{HOSTNAME}/$NODE/g" /etc/etcd/etcd.config.yml"
ssh ${MasterArray[$NODE]} "sed -i "s/{PUBLIC_IP}/${MasterArray[$NODE]}/g" /etc/etcd/etcd.config.yml"
ssh ${MasterArray[$NODE]} 'systemctl daemon-reload'
done

k8s-m1上启动所有etcd
etcd 进程首次启动时会等待其它节点的 etcd 加入集群,命令 systemctl start etcd 会卡住一段时间,为正常现象
可以全部启动后后面的etcdctl命令查看状态确认正常否

for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
ssh ${MasterArray[$NODE]} 'systemctl enable --now etcd' &
done
wait

然后输出到终端了的时候多按几下回车直到等光标回到终端状态

k8s-m1上执行下面命令验证 ETCD 集群状态,下面第二个是使用3的api去查询集群的键值

# etcdctl \
--cert-file /etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key-file /etc/kubernetes/pki/etcd/healthcheck-client.key \
--ca-file /etc/kubernetes/pki/etcd/ca.crt \
--endpoints $etcd_servers cluster-health ...下面是输出
member 4f15324b6756581c is healthy: got healthy result from https://10.0.6.166:2379
member cce1303a6b6dd443 is healthy: got healthy result from https://10.0.6.167:2379
member ead42f3e6c9bb295 is healthy: got healthy result from https://10.0.6.168:2379
cluster is healthy ETCDCTL_API=3 \
etcdctl \
--cert /etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key /etc/kubernetes/pki/etcd/healthcheck-client.key \
--cacert /etc/kubernetes/pki/etcd/ca.crt \
--endpoints $etcd_servers get / --prefix --keys-only
如果想了解更多etcdctl操作可以去官网etcdctl command 文章。

0x06 Kubernetes Masters

本部分将说明如何建立与设定Kubernetes Master 角色,过程中会部署以下元件:

kubelet:

# 负责管理容器的生命周期,定期从API Server获取节点上的预期状态(如网络、存储等等配置)资源,并让对应的容器插件(CRI、CNI 等)来达成这个状态。任何 Kubernetes 节点(node)都会拥有这个
# 关闭只读端口,在安全端口 10250 接收 https 请求,对请求进行认证和授权,拒绝匿名访问和非授权访问
# 使用 kubeconfig 访问 apiserver 的安全端口

kube-apiserver:

# 以 REST APIs 提供 Kubernetes 资源的 CRUD,如授权、认证、存取控制与 API 注册等机制。
# 关闭非安全端口,在安全端口 6443 接收 https 请求
# 严格的认证和授权策略 (x509、token、RBAC)
# 开启 bootstrap token 认证,支持 kubelet TLS bootstrapping
# 使用 https 访问 kubelet、etcd,加密通信

kube-controller-manager:

# 通过核心控制循环(Core Control Loop)监听 Kubernetes API 的资源来维护集群的状态,这些资源会被不同的控制器所管理,如 Replication Controller、Namespace Controller 等等。而这些控制器会处理着自动扩展、滚动更新等等功能。
# 关闭非安全端口,在安全端口 10252 接收 https 请求
# 使用 kubeconfig 访问 apiserver 的安全端口

kube-scheduler:

# 负责将一個(或多个)容器依据调度策略分配到对应节点上让容器引擎(如 Docker)执行。而调度受到 QoS 要求、软硬性约束、亲和性(Affinity)等等因素影响

HAProxy:

# 提供多个 API Server 的负载均衡(Load Balance),确保haproxy的端口负载到所有的apiserver的6443端口

Keepalived:

# 提供虚拟IP位址(VIP),来让vip落在可用的master主机上供所有组件都能访问到可用的master,结合haproxy能访问到master上的apiserver的6443端口

部署与设定

# 信息啥的按照自己实际填写,文件改了后如果不懂我下面写的shell估计是改不回了
# 网卡名改为各自宿主机的网卡名,下面用export interface=eth0后续的所有文件同理
# 若cluster dns或domain有改变的话,需要修改kubelet-conf.yml

HA(haproxy+keepalived) 单台master就不要用HA了

首先所有master安装haproxy+keepalived,多按几次回车如果没输出的话

for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
ssh ${MasterArray[$NODE]} 'yum install haproxy keepalived -y' &
done
wait

k8s-m1节点下把相关配置文件配置后再分发

# cd ~/k8s-manual-files/master/etc

# 修改haproxy.cfg配置文件
# sed -i '$r '<(paste <( seq -f' server k8s-api-%g' ${#MasterArray[@]} ) <( xargs -n1<<<${MasterArray[@]} | sort | sed 's#$#:6443 check#')) haproxy/haproxy.cfg # 修改keepalived(网卡和VIP写进去,使用下面命令) # sed -ri "s#\{\{ VIP \}\}#${VIP}#" keepalived/*
# sed -ri "s#\{\{ interface \}\}#${interface}#" keepalived/keepalived.conf
# sed -i '/unicast_peer/r '<(xargs -n1<<<${MasterArray[@]} | sort | sed 's#^#\t#') keepalived/keepalived.conf # 分发文件
for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
scp -r haproxy/ ${MasterArray[$NODE]}:/etc
scp -r keepalived/ ${MasterArray[$NODE]}:/etc
ssh ${MasterArray[$NODE]} 'systemctl enable --now haproxy keepalived'
done

ping下vip看看能通否,先等待大概四五秒等keepalived和haproxy起来

# ping $VIP

如果vip没起来就是keepalived没起来就每个节点上去restart下keepalived或者确认下配置文件/etc/keepalived/keepalived.conf里网卡名和ip是否注入成功

for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
ssh ${MasterArray[$NODE]} 'systemctl restart haproxy keepalived'
done

Master组件

k8s-m1节点下把相关配置文件配置后再分发

# cd ~/k8s-manual-files/master/
etcd_servers=$( xargs -n1<<<${MasterArray[@]} | sort | sed 's#^#https://#;s#$#:2379#;$s#\n##' | paste -d, -s - ) # 注入VIP和etcd_servers,apiserver数量
# sed -ri '/--etcd-servers/s#=.+#='"$etcd_servers"' \\#' systemd/kube-apiserver.service
# sed -ri '/apiserver-count/s#=[^\]+#='"${#MasterArray[@]}"' #' systemd/kube-apiserver.service # 分发文件
for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
ssh ${MasterArray[$NODE]} 'mkdir -p /etc/kubernetes/manifests /var/lib/kubelet /var/log/kubernetes'
scp systemd/kube-*.service ${MasterArray[$NODE]}:/usr/lib/systemd/system/
#注入网卡ip
ssh ${MasterArray[$NODE]} "sed -ri '/bind-address/s#=[^\]+#=${MasterArray[$NODE]} #' /usr/lib/systemd/system/kube-apiserver.service && sed -ri '/--advertise-address/s#=[^\]+#=${MasterArray[$NODE]} #' /usr/lib/systemd/system/kube-apiserver.service" done

k8s-m1上给所有master机器启动kubelet 服务并设置kubectl补全脚本:

for NODE in "${!MasterArray[@]}"; do
echo "--- $NODE ${MasterArray[$NODE]} ---"
ssh ${MasterArray[$NODE]} 'systemctl enable --now kube-apiserver kube-controller-manager kube-scheduler;
mkdir -p ~/.kube/
cp /etc/kubernetes/admin.kubeconfig ~/.kube/config;
kubectl completion bash > /etc/bash_completion.d/kubectl'
done

验证组件

完成后,在任意一台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"} # kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 36s

配置 bootstrap

由于本次安装启用了TLS认证,因此每个节点的kubelet都必须使用kube-apiserver的CA的凭证后,才能与kube-apiserver进行沟通,而该过程需要手动针对每台节点单独签署凭证是一件繁琐的事情,且一旦节点增加会延伸出管理不易问题;而TLS bootstrapping目标就是解决该问题,通过让kubelet先使用一个预定低权限使用者连接到kube-apiserver,然后在对kube-apiserver申请凭证签署,当授权Token一致时,Node节点的kubelet凭证将由kube-apiserver动态签署提供。具体作法可以参考TLS BootstrappingAuthenticating with Bootstrap Tokens

后面kubectl命令只需要在任何一台master执行就行了

首先在k8s-m1建立一个变数来产生BOOTSTRAP_TOKEN,并建立bootstrap的kubeconfig文件:

接着在k8s-m1建立TLS bootstrap secret来提供自动签证使用:

TOKEN_PUB=$(openssl rand -hex 3)
TOKEN_SECRET=$(openssl rand -hex 8)
BOOTSTRAP_TOKEN="${TOKEN_PUB}.${TOKEN_SECRET}" # kubectl -n kube-system create secret generic bootstrap-token-${TOKEN_PUB} \
--type 'bootstrap.kubernetes.io/token' \
--from-literal description="cluster bootstrap token" \
--from-literal token-id=${TOKEN_PUB} \
--from-literal token-secret=${TOKEN_SECRET} \
--from-literal usage-bootstrap-authentication=true \
--from-literal usage-bootstrap-signing=true

建立bootstrap的kubeconfig文件

CLUSTER_NAME="kubernetes"
KUBE_USER="kubelet-bootstrap"
KUBE_CONFIG="bootstrap.kubeconfig" # 设置集群参数
kubectl config set-cluster ${CLUSTER_NAME} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置上下文参数
kubectl config set-context ${KUBE_USER}@${CLUSTER_NAME} \
--cluster=${CLUSTER_NAME} \
--user=${KUBE_USER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置客户端认证参数
kubectl config set-credentials ${KUBE_USER} \
--token=${BOOTSTRAP_TOKEN} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 设置当前使用的上下文
kubectl config use-context ${KUBE_USER}@${CLUSTER_NAME} --kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # 查看生成的配置文件
kubectl config view --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}
若想要用手动签署凭证来进行授权的话,可以参考Certificate

授权 kubelet 可以创建 csr

# kubectl create clusterrolebinding kubeadm:kubelet-bootstrap --clusterrole system:node-bootstrapper --group system:bootstrappers

批准 csr 请求

允许 system:bootstrappers 组的所有 csr
cat <<EOF | kubectl apply -f -
# Approve all CSRs for the group "system:bootstrappers"
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: auto-approve-csrs-for-group
subjects:
- kind: Group
name: system:bootstrappers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
apiGroup: rbac.authorization.k8s.io
EOF

允许 kubelet 能够更新自己的证书

cat <<EOF | kubectl apply -f -
# Approve renewal CSRs for the group "system:nodes"
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: auto-approve-renewals-for-nodes
subjects:
- kind: Group
name: system:nodes
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
apiGroup: rbac.authorization.k8s.io
EOF

0x07 Kubernetes Nodes

本部分将说明如何建立与设定Kubernetes Node 角色,Node 是主要执行容器实例(Pod)的工作节点。
在开始部署前,先在k8-m1将需要用到的文件复制到所有其他节点上:

for NODE in "${!Other[@]}"; do
echo "--- $NODE ${Other[$NODE]} ---"
ssh ${Other[$NODE]} "mkdir -p /etc/kubernetes/pki /etc/kubernetes/manifests /var/lib/kubelet/"
for FILE in /etc/kubernetes/pki/ca.crt /etc/kubernetes/bootstrap.kubeconfig; do
scp ${FILE} ${Other[$NODE]}:${FILE}
done
done

部署与设定

这边建议healthzBindAddress kubeadm生成的是127,我建议设置成网卡ip方便后续检测curl http://192.168.26.135:10248/healthz

k8s-m1节点分发kubelet.service文件和配置文件到每台上去管理kubelet:

# cd ~/k8s-manual-files/
for NODE in "${!AllNode[@]}"; do
echo "--- $NODE ${AllNode[$NODE]} ---"
scp master/systemd/kubelet.service ${AllNode[$NODE]}:/lib/systemd/system/kubelet.service
scp master/etc/kubelet/kubelet-conf.yml ${AllNode[$NODE]}:/etc/kubernetes/kubelet-conf.yml
ssh ${AllNode[$NODE]} "sed -ri '/0.0.0.0/s#\S+\$#${MasterArray[$NODE]}#' /etc/kubernetes/kubelet-conf.yml"
ssh ${AllNode[$NODE]} "sed -ri '/127.0.0.1/s#\S+\$#${MasterArray[$NODE]}#' /etc/kubernetes/kubelet-conf.yml"
done

最后在k8s-m1上去启动每个node节点的kubelet 服务:

for NODE in "${!AllNode[@]}"; do
echo "--- $NODE ${AllNode[$NODE]} ---"
ssh ${AllNode[$NODE]} 'systemctl enable --now kubelet.service'
done

验证集群

完成后,在任意一台master节点并通过简单指令验证:

# kubectl get nodes
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-m1 NotReady <none> 22s v1.13.4 10.0.6.166 <none> CentOS Linux 7 (Core) 4.20.13-1.el7.elrepo.x86_64 docker://18.6.3
k8s-m2 NotReady <none> 24s v1.13.4 10.0.6.167 <none> CentOS Linux 7 (Core) 4.20.13-1.el7.elrepo.x86_64 docker://18.6.3
k8s-m3 NotReady <none> 21s v1.13.4 10.0.6.168 <none> CentOS Linux 7 (Core) 4.20.13-1.el7.elrepo.x86_64 docker://18.6.3
k8s-n1 NotReady <none> 22s v1.13.4 10.0.6.169 <none> CentOS Linux 7 (Core) 4.20.13-1.el7.elrepo.x86_64 docker://18.6.3
k8s-n2 NotReady <none> 22s v1.13.4 10.0.6.170 <none> CentOS Linux 7 (Core) 4.20.13-1.el7.elrepo.x86_64 docker://18.6.3
# csr自动被授权
# kubectl get csr
NAME AGE REQUESTOR CONDITION
node-csr-4fCDrNulc_btdBiRgev0JO4EorZ0rMuyJ756wrn9NqQ 27s system:bootstrap:e860ec Approved,Issued
node-csr-P3Y_knryQNaQWDDYFObtcdfXB4XAl9IB2Be2YJ-b-dA 27s system:bootstrap:e860ec Approved,Issued
node-csr-r_4ZDnanqBw2HPTSn6bSL50r-kJkTPjix6SY1n9UmjY 28s system:bootstrap:e860ec Approved,Issued
node-csr-vy-6tgMI9vUiIzR3Ogv6bPVGA2_gZrd7aMIWMSuHrME 27s system:bootstrap:e860ec Approved,Issued
node-csr-zOvVxSaY1iMco2LnOHmwqiBDwPUaLix7cSqUfZWTGFo 26s system:bootstrap:e860ec Approved,Issued

设定master节点加上污点Taint不让(没有声明容忍该污点的)pod跑在master节点上:

kubectl taint nodes ${!MasterArray[@]} node-role.kubernetes.io/master="":NoSchedule
# 下面是输出
node "k8s-m1" tainted
node "k8s-m2" tainted
node "k8s-m3" tainted

node打标签声明role

# kubectl label node ${!MasterArray[@]} node-role.kubernetes.io/master=""
# kubectl label node ${!NodeArray[@]} node-role.kubernetes.io/worker=worker

0x08 Kubernetes Proxy(二进制和Daemonset部署任选其一)

Kube-proxy是实现Service的关键插件,kube-proxy会在每台节点上执行,然后监听API Server的Service与Endpoint资源物件的改变,然后来依据变化执行iptables来实现网路的转发。这边我们会需要建议一个DaemonSet来执行,并且建立一些需要的Certificates。

二进制部署方式(ds比二进制更好扩展,后面有ds部署)

k8s-m1配置 kube-proxy:
创建一个 kube-proxy 的 service account:

# kubectl -n kube-system create serviceaccount kube-proxy

将 kube-proxy 的 serviceaccount 绑定到 clusterrole system:node-proxier 以允许 RBAC:

# kubectl create clusterrolebinding kubeadm:kube-proxy \
--clusterrole system:node-proxier \
--serviceaccount kube-system:kube-proxy

创建kube-proxykubeconfig:

CLUSTER_NAME="kubernetes"
KUBE_CONFIG="kube-proxy.kubeconfig" SECRET=$(kubectl -n kube-system get sa/kube-proxy \
--output=jsonpath='{.secrets[0].name}') JWT_TOKEN=$(kubectl -n kube-system get secret/$SECRET \
--output=jsonpath='{.data.token}' | base64 -d) # kubectl config set-cluster ${CLUSTER_NAME} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # kubectl config set-context ${CLUSTER_NAME} \
--cluster=${CLUSTER_NAME} \
--user=${CLUSTER_NAME} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # kubectl config set-credentials ${CLUSTER_NAME} \
--token=${JWT_TOKEN} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG} # kubectl config use-context ${CLUSTER_NAME} --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}
# kubectl config view --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

k8s-m1分发kube-proxy 的 相关文件到所有节点

# cd ~/k8s-manual-files/
for NODE in "${!Other[@]}"; do
echo "--- $NODE ${Other[$NODE]} ---"
scp /etc/kubernetes/kube-proxy.kubeconfig ${Other[$NODE]}:/etc/kubernetes/kube-proxy.kubeconfig
done for NODE in "${!AllNode[@]}"; do
echo "--- $NODE ${AllNode[$NODE]} ---"
scp addons/kube-proxy/kube-proxy.conf ${AllNode[$NODE]}:/etc/kubernetes/kube-proxy.conf
scp addons/kube-proxy/kube-proxy.service ${AllNode[$NODE]}:/usr/lib/systemd/system/kube-proxy.service
ssh ${AllNode[$NODE]} "sed -ri '/0.0.0.0/s#\S+\$#${MasterArray[$NODE]}#' /etc/kubernetes/kube-proxy.conf"
done

然后在k8s-m1上启动所有节点的kube-proxy 服务:

for NODE in "${!AllNode[@]}"; do
echo "--- $NODE ${AllNode[$NODE]} ---"
ssh ${AllNode[$NODE]} 'systemctl enable --now kube-proxy'
done

daemonSet方式部署

# cd ~/k8s-manual-files

# 注入变量
# sed -ri "/server:/s#(: ).+#\1${KUBE_APISERVER}#" addons/kube-proxy/kube-proxy.yml
# sed -ri "/image:.+kube-proxy/s#:[^:]+\$#:$KUBE_VERSION#" addons/kube-proxy/kube-proxy.yml # kubectl apply -f addons/kube-proxy/kube-proxy.yml
# 下面是输出
serviceaccount "kube-proxy" created
clusterrolebinding.rbac.authorization.k8s.io "system:kube-proxy" created
configmap "kube-proxy" created
daemonset.apps "kube-proxy" created

正常是下面状态,如果有问题可以看看docker拉到了镜像否和kubelet的日志输出

正常了可以直接翻到下面ipvsadm -ln那
# kubectl -n kube-system get po -l k8s-app=kube-proxy
NAME READY STATUS RESTARTS AGE
kube-proxy-dd2m7 1/1 Running 0 8m
kube-proxy-fwgx8 1/1 Running 0 8m
kube-proxy-kjn57 1/1 Running 0 8m
kube-proxy-vp47w 1/1 Running 0 8m

通过ipvsadm查看 proxy 规则

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.1:443 rr
-> 10.0.6.155:6443 Masq 1 0 0

确认使用ipvs模式

$ curl localhost:10249/proxyMode
ipvs

0x09 网络部署与设定(flannel或者calico任选其一)

集群网络

Kubernetes 在默认情況下与 Docker 的网络有所不同。在 Kubernetes 中有四个问题是需要被解決的,分別为:

  • 高耦合的容器到容器通信:通过 Pods 内 localhost 的來解決。
  • Pod 到 Pod 的通信:通过实现网络模型来解决。
  • Pod 到 Service 通信:由 Service objects 结合 kube-proxy 解決。
  • 外部到 Service 通信:一样由 Service objects 结合 kube-proxy 解決。

而 Kubernetes 对于任何网络的实现都需要满足以下基本要求(除非是有意调整的网络分段策略):

  • 所有容器能够在沒有 NAT 的情況下与其他容器通信。
  • 所有节点能夠在沒有 NAT 情況下与所有容器通信(反之亦然)。
  • 容器看到的 IP 与其他人看到的 IP 是一样的。

庆幸的是 Kubernetes 已经有非常多种的网络模型作为网络插件(Network Plugins)方式被实现,因此可以选用满足自己需求的网络功能来使用。另外 Kubernetes 中的网络插件有以下两种形式:

  • CNI plugins:以 appc/CNI 标准规范所实现的网络,详细可以阅读 CNI Specification
  • Kubenet plugin:使用 CNI plugins 的 bridge 与 host-local 来实现基本的 cbr0。这通常被用在公有云服务上的 Kubernetes 集群网络。

如果是公有云不在一个vpc里建议用flannel,因为公有云是SDN,只有vxlan才能到达目标,每个node上的flannel.1充当了vtep身份.另外完成到集群可以使用后会发现只有pod所在的node能访问到它这台上面的clusterIP,是因为kubelet上报的节点的node public IP是取网卡的ip,公有云网卡ip都是内网ip,所以当flannel包要发到目标机器的flannel上的时候会发到目标机器的内网ip上,根本发不出去,解决方法看我博客跨vpc搭建集群的文章

flannel

flannel 使用 vxlan 技术为各节点创建一个可以互通的 Pod 网络,使用的端口为 UDP 8472,需要开放该端口(如公有云 AWS 等)。

flannel 第一次启动时,从 etcd 获取 Pod 网段信息,为本节点分配一个未使用的 /24 段地址,然后创建 flannel.1(也可能是其它名称,如 flannel1 等) 接口。
yml来源于官方删除无用架构的ds部分 https://github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml

这边镜像因为是quay.io域名仓库会拉取很慢,所有节点可以提前拉取下,否则就等。镜像名根据输出来,可能我博客部分使用镜像版本更新了

# grep -Pom1 'image:\s+\K\S+' addons/flannel/kube-flannel.yml
quay.io/coreos/flannel:v0.11.0-amd64
# curl -s https://zhangguanzhang.github.io/bash/pull.sh | bash -s -- quay.io/coreos/flannel:v0.11.0-amd64

创建flannel,这边使用ds来创建,yaml来源于官方,删除了非amd64的ds

# sed -ri "s#\{\{ interface \}\}#${interface}#" addons/flannel/kube-flannel.yml

# kubectl apply -f addons/flannel/kube-flannel.yml
...

检查是否启动

# kubectl -n kube-system get po -l k8s-app=flannel
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-27jwl 2/2 Running 0 59s
kube-flannel-ds-4fgv6 2/2 Running 0 59s
kube-flannel-ds-mvrt7 2/2 Running 0 59s
kube-flannel-ds-p2q9g 2/2 Running 0 59s
kube-flannel-ds-zchsz 2/2 Running 0 59s

Calico

Calico 是一款纯 Layer 3 的网络,其好处是它整合了各种云原生平台(Docker、Mesos 与 OpenStack 等),且 Calico 不采用 vSwitch,而是在每个 Kubernetes 节点使用 vRouter 功能,并通过 Linux Kernel 既有的 L3 forwarding 功能,而当资料中心复杂度增加时,Calico 也可以利用 BGP route reflector 來达成。

由于 Calico 提供了 Kubernetes resources YAML 文件来快速以容器方式部署网络插件至所有节点上,因此只需要在k8s-m1使用 kubeclt 执行下面指令來建立:

这边镜像因为是quay.io域名仓库会拉取很慢,所有节点可以提前拉取下,否则就等。镜像名根据输出来,可能我博客部分使用镜像版本更新了

# grep -Po 'image:\s+\K\S+' addons/calico/v3.1/calico.yml
quay.io/calico/typha:v0.7.4
quay.io/calico/node:v3.1.3
quay.io/calico/cni:v3.1.3
  • 另外当节点超过 50 台,可以使用 Calico 的 Typha 模式来减少通过 Kubernetes datastore 造成 API Server 的负担。

包含上面三个镜像,拉取两个即可

# curl -s https://zhangguanzhang.github.io/bash/pull.sh | bash -s -- quay.io/calico/node:v3.1.3
# curl -s https://zhangguanzhang.github.io/bash/pull.sh | bash -s -- quay.io/calico/cni:v3.1.3
# sed -ri "s#\{\{ interface \}\}#${interface}#" addons/calico/v3.1/calico.yml
# kubectl apply -f addons/calico/v3.1
# kubectl -n kube-system get pod --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-node-2hdqf 0/2 ContainerCreating 0 4m
kube-system calico-node-456fh 0/2 ContainerCreating 0 4m
kube-system calico-node-jh6vd 0/2 ContainerCreating 0 4m
kube-system calico-node-sp6w9 0/2 ContainerCreating 0 4m
kube-system calicoctl-6dfc585667-24s9h 0/1 Pending 0 4m
kube-system kube-proxy-46hr5 1/1 Running 0 7m
kube-system kube-proxy-l42sk 1/1 Running 0 7m
kube-system kube-proxy-p2nbf 1/1 Running 0 7m
kube-system kube-proxy-q6qn9 1/1 Running 0 7m

calico正常是下面状态

# kubectl get pod --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-node-2hdqf 2/2 Running 0 4m
kube-system calico-node-456fh 2/2 Running 2 4m
kube-system calico-node-jh6vd 2/2 Running 0 4m
kube-system calico-node-sp6w9 2/2 Running 0 4m
kube-system calicoctl-6dfc585667-24s9h 1/1 Running 0 4m
kube-system kube-proxy-46hr5 1/1 Running 0 8m
kube-system kube-proxy-l42sk 1/1 Running 0 8m
kube-system kube-proxy-p2nbf 1/1 Running 0 8m
kube-system kube-proxy-q6qn9 1/1 Running 0 8m

部署后通过下面查看状态即使正常

# kubectl -n kube-system get po -l k8s-app=calico-node
NAME READY STATUS RESTARTS AGE
calico-node-bv7r9 2/2 Running 4 5m
calico-node-cmh2w 2/2 Running 3 5m
calico-node-klzrz 2/2 Running 4 5m
calico-node-n4c9j 2/2 Running 4 5m

查找calicoctl的pod名字

# kubectl -n kube-system get po -l k8s-app=calicoctl
NAME READY STATUS RESTARTS AGE
calicoctl-6b5bf7cb74-d9gv8 1/1 Running 0 5m

通过 kubectl exec calicoctl pod 执行命令来检查功能是否正常

# kubectl -n kube-system exec calicoctl-6b5bf7cb74-d9gv8 -- calicoctl get profiles -o wide
NAME LABELS
kns.default map[]
kns.kube-public map[]
kns.kube-system map[] # kubectl -n kube-system exec calicoctl-6b5bf7cb74-d9gv8 -- calicoctl get node -o wide
NAME ASN IPV4 IPV6
k8s-m1 (unknown) 192.168.88.111/24
k8s-m2 (unknown) 192.168.88.112/24
k8s-m3 (unknown) 192.168.88.113/24
k8s-n1 (unknown) 10.244.3.1/24

完成后,通过检查节点是否不再是NotReady,以及 Pod 是否不再是Pending:

0x0A CoreDNS or KubeDNS

1.11后CoreDNS 已取代 Kube DNS 作为集群服务发现元件,由于 Kubernetes 需要让 Pod 与 Pod 之间能夠互相通信,然而要能够通信需要知道彼此的 IP 才行,而这种做法通常是通过 Kubernetes API 来获取,但是 Pod IP 会因为生命周期变化而改变,因此这种做法无法弹性使用,且还会增加 API Server 负担,基于此问题 Kubernetes 提供了 DNS 服务来作为查询,让 Pod 能夠以 Service 名称作为域名来查询 IP 位址,因此使用者就再不需要关心实际 Pod IP,而 DNS 也会根据 Pod 变化更新资源记录(Record resources)。

CoreDNS

CoreDNS 是由 CNCF 维护的开源 DNS 方案,该方案前身是 SkyDNS,其采用了 Caddy 的一部分来开发伺服器框架,使其能够建立一套快速灵活的 DNS,而 CoreDNS 每个功能都可以被当作成一個插件的中介软体,如 Log、Cache、Kubernetes 等功能,甚至能够将源记录存储在 Redis、Etcd 中。

# kubectl exec coredns-xxxxxx -- kill -SIGUSR1 1

k8s-m1通过 kubeclt 执行下面命令來创建,并检查是否部署成功:

kubectl apply -f addons/coredns/coredns.yml
# 下面是输出
serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.extensions/coredns created
service/kube-dns created kubectl -n kube-system get po -l k8s-app=kube-dns
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-6975654877-jjqkg 1/1 Running 0 1m
kube-system coredns-6975654877-ztqjh 1/1 Running 0 1m

完成后,通过检查节点是否不再是NotReady,以及 Pod 是否不再是Pending:

# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-m1 Ready master 17m v1.11.1
k8s-m2 Ready master 16m v1.11.1
k8s-m3 Ready master 16m v1.11.1
k8s-n1 Ready node 6m v1.11.1

这里似乎有个官方bug https://github.com/coredns/coredns/issues/2289
coredns正常否看脸,可以下面创建pod来测试
先创建一个dnstool的pod

# cat<<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- name: busybox
image: busybox:1.28
command:
- sleep
- ""
imagePullPolicy: IfNotPresent
restartPolicy: Always
EOF

nslookup下看看能返回地址不

# kubectl exec -ti busybox -- nslookup kubernetes
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

下面则是遇到了,这个现象是官方bug,如果想看log的话在Corefile加一行log则开启log打印查看,上面的issue里官方目前也无解

Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'kubernetes'
command terminated with exit code 1

KubeDNS(如果遇到上面的CoreDNS的bug的话使用它)

Kube DNS是Kubernetes集群内部Pod之间互相沟通的重要Addon,它允许Pod可以通过Domain Name方式来连接Service,其主要由Kube DNS与Sky DNS组合而成,通过Kube DNS监听Service与Endpoint变化,来提供给Sky DNS资讯,已更新解析位址。

如果CoreDNS工作不正常,先删掉它,删掉后确保coredns的pod和svc不存在

# kubectl delete -f addons/coredns/coredns.yml
# kubectl -n kube-system get pod,svc -l k8s-app=kube-dns
No resources found.

创建KubeDNS

# kubectl apply -f addons/Kubedns/kubedns.yml 

serviceaccount/kube-dns created
service/kube-dns created
deployment.extensions/kube-dns create

查看pod状态

# kubectl -n kube-system get pod,svc -l k8s-app=kube-dns

NAME                            READY   STATUS    RESTARTS   AGE
pod/kube-dns-59c677cb95-pxcbc 3/3 Running 0 3m
pod/kube-dns-59c677cb95-wlprb 3/3 Running 0 3m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 3m

检查集群dns正常否

# kubectl exec -ti busybox -- nslookup kubernetes
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

0x0B 测试集群高可用

SSH进入k8s-m1节点,然后关闭该节点:

# sudo poweroff

接着进入到k8s-m2节点,通过kubectl来检查集群是否能够正常执行:

# 先检查 etcd 状态,可以发现 etcd-0 因為关机而中断
# kubectl get cs
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-1 Healthy {"health": "true"}
etcd-2 Healthy {"health": "true"}
etcd-0 Unhealthy Get https://192.168.88.111:2379/health: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

参考