K8S學習筆記之二進制部署Kubernetes v1.13.4 高可用集群


 

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的操作都忽略即可

節點信息

 

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": "3"
  }
}
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
      - "3600"
    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)

 

參考

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM