2. 死磕 k8s系列之安裝k8s集群(v1.16.2)

  • 2020 年 2 月 10 日
  • 筆記

註:本文基於k8s集群v1.16.2版本。

簡介

k8s真是一個複雜的東西,每一次安裝都可能出現不一樣的問題,本文特記錄下來彤哥安裝過程中出現的一些問題及解決方案。

配置要求

  • 3台2核4G的ECS

可以到阿里雲、騰訊雲上購買按需付費,用完釋放

  • CentOS 7.6

此版本驗證通過,其它版本不確定

安裝軟體的版本

  • Docker 18.09.7
  • Kubernetes v1.16.2

安裝後的拓撲結構為一個master節點,兩個worker節點。

  • nginx-ingress 1.5.5

安裝過程

檢查 centos / hostname

# 在 master 節點和 worker 節點都要執行  cat /etc/redhat-release    # 此處 hostname 的輸出將會是該機器在 Kubernetes 集群中的節點名字  # 不能使用 localhost 作為節點的名字  hostname    # 請使用 lscpu 命令,核對 CPU 資訊  # Architecture: x86_64    本安裝文檔不支援 arm 架構  # CPU(s):       2         CPU 內核數量不能低於 2  lscpu

修改 hostname

如果您需要修改 hostname,可執行如下指令:

# 修改 hostname  hostnamectl set-hostname [your-new-host-name]  # 查看修改結果  hostnamectl status  # 設置 hostname 解析  echo "127.0.0.1   $(hostname)" >> /etc/hosts

安裝 docker / kubelet

使用 root 身份在所有節點執行如下程式碼,以安裝軟體:

  • docker
  • nfs-utils
  • kubectl / kubeadm / kubelet

手動執行以下程式碼,或保存為sh腳本執行:

#!/bin/bash    # 在 master 節點和 worker 節點都要執行    # 安裝 docker  # 參考文檔如下  # https://docs.docker.com/install/linux/docker-ce/centos/  # https://docs.docker.com/install/linux/linux-postinstall/    # 卸載舊版本  yum remove -y docker   docker-client   docker-client-latest   docker-common   docker-latest   docker-latest-logrotate   docker-logrotate   docker-selinux   docker-engine-selinux   docker-engine    # 設置 yum repository  yum install -y yum-utils   device-mapper-persistent-data   lvm2  yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo    # 安裝並啟動 docker  yum install -y docker-ce-18.09.7 docker-ce-cli-18.09.7 containerd.io  systemctl enable docker  systemctl start docker    # 安裝 nfs-utils  # 必須先安裝 nfs-utils 才能掛載 nfs 網路存儲  yum install -y nfs-utils  yum install -y wget    # 關閉 防火牆  systemctl stop firewalld  systemctl disable firewalld    # 關閉 SeLinux  setenforce 0  sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config    # 關閉 swap  swapoff -a  yes | cp /etc/fstab /etc/fstab_bak  cat /etc/fstab_bak |grep -v swap > /etc/fstab    # 修改 /etc/sysctl.conf  # 如果有配置,則修改  sed -i "s#^net.ipv4.ip_forward.*#net.ipv4.ip_forward=1#g"  /etc/sysctl.conf  sed -i "s#^net.bridge.bridge-nf-call-ip6tables.*#net.bridge.bridge-nf-call-ip6tables=1#g"  /etc/sysctl.conf  sed -i "s#^net.bridge.bridge-nf-call-iptables.*#net.bridge.bridge-nf-call-iptables=1#g"  /etc/sysctl.conf  # 可能沒有,追加  echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf  echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf  echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf  # 執行命令以應用  sysctl -p    # 配置K8S的yum源  cat <<EOF > /etc/yum.repos.d/kubernetes.repo  [kubernetes]  name=Kubernetes  baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64  enabled=1  gpgcheck=0  repo_gpgcheck=0  gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg         http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg  EOF    # 卸載舊版本  yum remove -y kubelet kubeadm kubectl    # 安裝kubelet、kubeadm、kubectl  yum install -y kubelet-1.16.2 kubeadm-1.16.2 kubectl-1.16.2    # 修改docker Cgroup Driver為systemd  # # 將/usr/lib/systemd/system/docker.service文件中的這一行 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock  # # 修改為 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd  # 如果不修改,在添加 worker 節點時可能會碰到如下錯誤  # [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd".  # Please follow the guide at https://kubernetes.io/docs/setup/cri/  sed -i "s#^ExecStart=/usr/bin/dockerd.*#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd#g" /usr/lib/systemd/system/docker.service    # 設置 docker 鏡像,提高 docker 鏡像下載速度和穩定性  # 如果您訪問 https://hub.docker.io 速度非常穩定,亦可以跳過這個步驟  curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io    # 重啟 docker,並啟動 kubelet  systemctl daemon-reload  systemctl restart docker  systemctl enable kubelet && systemctl start kubelet    docker version

注意:上面的腳本需要在所有節點上執行。

初始化 master 節點

關於初始化時用到的環境變數:

  • APISERVER_NAME 不能是 master 的 hostname
  • APISERVER_NAME 必須全為小寫字母、數字、小數點,不能包含減號
  • PODSUBNET 所使用的網段不能與 master節點/worker節點 所在的網段重疊。該欄位的取值為一個 CIDR 值,如果您對 CIDR 這個概念還不熟悉,請仍然執行 export PODSUBNET=10.100.0.1/16 命令,不做修改
# 只在 master 節點執行  # 替換 x.x.x.x 為 master 節點實際 IP(請使用內網 IP)  # export 命令只在當前 shell 會話中有效,開啟新的 shell 窗口後,如果要繼續安裝過程,請重新執行此處的 export 命令  export MASTER_IP=10.202.12.21  # 替換 apiserver.demo 為 您想要的 dnsName  export APISERVER_NAME=apiserver.demo  # Kubernetes 容器組所在的網段,該網段安裝完成後,由 kubernetes 創建,事先並不存在於您的物理網路中    echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts

使用kubeadm初始化master節點,執行以下腳本:

#!/bin/bash    # 只在 master 節點執行    # 腳本出錯時終止執行  set -e    # 查看完整配置選項 https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2  rm -f ./kubeadm-config.yaml  cat <<EOF > ./kubeadm-config.yaml  apiVersion: kubeadm.k8s.io/v1beta2  kind: ClusterConfiguration  kubernetesVersion: v1.16.2  imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers  controlPlaneEndpoint: "${APISERVER_NAME}:6443"  networking:    serviceSubnet: "10.96.0.0/16"    podSubnet: "${POD_SUBNET}"    dnsDomain: "cluster.local"  EOF    # kubeadm init  # 根據您伺服器網速的情況,您需要等候 3 - 10 分鐘  kubeadm init --config=kubeadm-config.yaml --upload-certs    # 配置 kubectl  rm -rf /root/.kube/  mkdir /root/.kube/  cp -i /etc/kubernetes/admin.conf /root/.kube/config    # 安裝 calico 網路插件  # 參考文檔 https://docs.projectcalico.org/v3.9/getting-started/kubernetes/  rm -f calico-3.9.2.yaml  wget https://kuboard.cn/install-script/calico/calico-3.9.2.yaml  sed -i "s#192.168.0.0/16#${POD_SUBNET}#" calico-3.9.2.yaml  kubectl apply -f calico-3.9.2.yaml

檢查 master 初始化結果

# 只在 master 節點執行    # 執行如下命令,等待 3-10 分鐘,直到所有的容器組處於 Running 狀態  watch kubectl get pod -n kube-system -o wide    # 查看 master 節點初始化結果  kubectl get nodes -o wide

注意,初始化 master 節點時,如果因為中間某些步驟的配置出錯,想要重新初始化 master 節點,請先執行 kubeadm reset 操作。

初始化 worker節點

獲得 join命令參數

在 master 節點上執行

# 只在 master 節點執行  kubeadm token create --print-join-command

執行結果大致類似下面這樣:

# 請不要直接拷貝這條命令,拷貝你的master節點上執行的結果  # 這裡token的有效時間為兩個小時,兩小時內可以在worker節點上執行  kubeadm join apiserver.demo:6443 --token h345zw.em6ycnpcpevxz8as     --discovery-token-ca-cert-hash sha256:84a8710bbfbb046915a90064132203ca453353b85ef5d29f961b03f129f7d5e3

初始化worker節點

針對所有的 worker 節點執行

# 只在 worker 節點執行  # 替換 x.x.x.x 為 master 節點實際 IP(請使用內網 IP)  export MASTER_IP=x.x.x.x  # 替換 apiserver.demo 為初始化 master 節點時所使用的 APISERVER_NAME  export APISERVER_NAME=apiserver.demo  echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts    # 替換為 master 節點上 kubeadm token create 命令的輸出  kubeadm join apiserver.demo:6443 --token h345zw.em6ycnpcpevxz8as     --discovery-token-ca-cert-hash sha256:84a8710bbfbb046915a90064132203ca453353b85ef5d29f961b03f129f7d5e3

檢查初始化結果

在 master 節點上執行

# 只在 master 節點執行  kubectl get nodes -o wide

輸出結果如下:

[root@instance-ji0xk9uh-1 ~]# kubectl get nodes -o wide  NAME     STATUS   ROLES    AGE     VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION               CONTAINER-RUNTIME  master   Ready    master   19m     v1.16.2   192.168.64.4   <none>        CentOS Linux 7 (Core)   3.10.0-957.27.2.el7.x86_64   docker://18.9.7  node1    Ready    <none>   2m43s   v1.16.2   192.168.64.5   <none>        CentOS Linux 7 (Core)   3.10.0-957.27.2.el7.x86_64   docker://18.9.7  node2    Ready    <none>   2m17s   v1.16.2   192.168.64.6   <none>        CentOS Linux 7 (Core)   3.10.0-957.27.2.el7.x86_64   docker://18.9.7

移除 worker 節點

注意,正常情況下,您無需移除 worker 節點,如果添加到集群出錯,您可以移除 worker 節點,再重新嘗試添加。

在準備移除的 worker 節點上執行

# 只在 worker 節點執行  kubeadm reset

在 master 節點上執行

# 只在 master 節點執行  # worker 節點的名字可以通過在節點 master 上執行 kubectl get nodes 命令獲得  kubectl delete node [worker_node1]

安裝 Ingress Controller

在master上新建如下 nginx-ingress.yaml 文件:

# 如果打算用於生產環境,請參考 https://github.com/nginxinc/kubernetes-ingress/blob/v1.5.5/docs/installation.md 並根據您自己的情況做進一步訂製    apiVersion: v1  kind: Namespace  metadata:    name: nginx-ingress    ---  apiVersion: v1  kind: ServiceAccount  metadata:    name: nginx-ingress    namespace: nginx-ingress    ---  apiVersion: v1  kind: Secret  metadata:    name: default-server-secret    namespace: nginx-ingress  type: Opaque  data:    tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN2akNDQWFZQ0NRREFPRjl0THNhWFhEQU5CZ2txaGtpRzl3MEJBUXNGQURBaE1SOHdIUVlEVlFRRERCWk8KUjBsT1dFbHVaM0psYzNORGIyNTBjbTlzYkdWeU1CNFhEVEU0TURreE1qRTRNRE16TlZvWERUSXpNRGt4TVRFNApNRE16TlZvd0lURWZNQjBHQTFVRUF3d1dUa2RKVGxoSmJtZHlaWE56UTI5dWRISnZiR3hsY2pDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUwvN2hIUEtFWGRMdjNyaUM3QlBrMTNpWkt5eTlyQ08KR2xZUXYyK2EzUDF0azIrS3YwVGF5aGRCbDRrcnNUcTZzZm8vWUk1Y2Vhbkw4WGM3U1pyQkVRYm9EN2REbWs1Qgo4eDZLS2xHWU5IWlg0Rm5UZ0VPaStlM2ptTFFxRlBSY1kzVnNPazFFeUZBL0JnWlJVbkNHZUtGeERSN0tQdGhyCmtqSXVuektURXUyaDU4Tlp0S21ScUJHdDEwcTNRYzhZT3ExM2FnbmovUWRjc0ZYYTJnMjB1K1lYZDdoZ3krZksKWk4vVUkxQUQ0YzZyM1lma1ZWUmVHd1lxQVp1WXN2V0RKbW1GNWRwdEMzN011cDBPRUxVTExSakZJOTZXNXIwSAo1TmdPc25NWFJNV1hYVlpiNWRxT3R0SmRtS3FhZ25TZ1JQQVpQN2MwQjFQU2FqYzZjNGZRVXpNQ0F3RUFBVEFOCkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWpLb2tRdGRPcEsrTzhibWVPc3lySmdJSXJycVFVY2ZOUitjb0hZVUoKdGhrYnhITFMzR3VBTWI5dm15VExPY2xxeC9aYzJPblEwMEJCLzlTb0swcitFZ1U2UlVrRWtWcitTTFA3NTdUWgozZWI4dmdPdEduMS9ienM3bzNBaS9kclkrcUI5Q2k1S3lPc3FHTG1US2xFaUtOYkcyR1ZyTWxjS0ZYQU80YTY3Cklnc1hzYktNbTQwV1U3cG9mcGltU1ZmaXFSdkV5YmN3N0NYODF6cFErUyt1eHRYK2VBZ3V0NHh3VlI5d2IyVXYKelhuZk9HbWhWNThDd1dIQnNKa0kxNXhaa2VUWXdSN0diaEFMSkZUUkk3dkhvQXprTWIzbjAxQjQyWjNrN3RXNQpJUDFmTlpIOFUvOWxiUHNoT21FRFZkdjF5ZytVRVJxbStGSis2R0oxeFJGcGZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=    tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdi91RWM4b1JkMHUvZXVJTHNFK1RYZUprckxMMnNJNGFWaEMvYjVyYy9XMlRiNHEvClJOcktGMEdYaVN1eE9ycXgrajlnamx4NXFjdnhkenRKbXNFUkJ1Z1B0ME9hVGtIekhvb3FVWmcwZGxmZ1dkT0EKUTZMNTdlT1l0Q29VOUZ4amRXdzZUVVRJVUQ4R0JsRlNjSVo0b1hFTkhzbysyR3VTTWk2Zk1wTVM3YUhudzFtMApxWkdvRWEzWFNyZEJ6eGc2clhkcUNlUDlCMXl3VmRyYURiUzc1aGQzdUdETDU4cGszOVFqVUFQaHpxdmRoK1JWClZGNGJCaW9CbTVpeTlZTW1hWVhsMm0wTGZzeTZuUTRRdFFzdEdNVWozcGJtdlFmazJBNnljeGRFeFpkZFZsdmwKMm82MjBsMllxcHFDZEtCRThCay90elFIVTlKcU56cHpoOUJUTXdJREFRQUJBb0lCQVFDZklHbXowOHhRVmorNwpLZnZJUXQwQ0YzR2MxNld6eDhVNml4MHg4Mm15d1kxUUNlL3BzWE9LZlRxT1h1SENyUlp5TnUvZ2IvUUQ4bUFOCmxOMjRZTWl0TWRJODg5TEZoTkp3QU5OODJDeTczckM5bzVvUDlkazAvYzRIbjAzSkVYNzZ5QjgzQm9rR1FvYksKMjhMNk0rdHUzUmFqNjd6Vmc2d2szaEhrU0pXSzBwV1YrSjdrUkRWYmhDYUZhNk5nMUZNRWxhTlozVDhhUUtyQgpDUDNDeEFTdjYxWTk5TEI4KzNXWVFIK3NYaTVGM01pYVNBZ1BkQUk3WEh1dXFET1lvMU5PL0JoSGt1aVg2QnRtCnorNTZud2pZMy8yUytSRmNBc3JMTnIwMDJZZi9oY0IraVlDNzVWYmcydVd6WTY3TWdOTGQ5VW9RU3BDRkYrVm4KM0cyUnhybnhBb0dCQU40U3M0ZVlPU2huMVpQQjdhTUZsY0k2RHR2S2ErTGZTTXFyY2pOZjJlSEpZNnhubmxKdgpGenpGL2RiVWVTbWxSekR0WkdlcXZXaHFISy9iTjIyeWJhOU1WMDlRQ0JFTk5jNmtWajJTVHpUWkJVbEx4QzYrCk93Z0wyZHhKendWelU0VC84ajdHalRUN05BZVpFS2FvRHFyRG5BYWkyaW5oZU1JVWZHRXFGKzJyQW9HQkFOMVAKK0tZL0lsS3RWRzRKSklQNzBjUis3RmpyeXJpY05iWCtQVzUvOXFHaWxnY2grZ3l4b25BWlBpd2NpeDN3QVpGdwpaZC96ZFB2aTBkWEppc1BSZjRMazg5b2pCUmpiRmRmc2l5UmJYbyt3TFU4NUhRU2NGMnN5aUFPaTVBRHdVU0FkCm45YWFweUNweEFkREtERHdObit3ZFhtaTZ0OHRpSFRkK3RoVDhkaVpBb0dCQUt6Wis1bG9OOTBtYlF4VVh5YUwKMjFSUm9tMGJjcndsTmVCaWNFSmlzaEhYa2xpSVVxZ3hSZklNM2hhUVRUcklKZENFaHFsV01aV0xPb2I2NTNyZgo3aFlMSXM1ZUtka3o0aFRVdnpldm9TMHVXcm9CV2xOVHlGanIrSWhKZnZUc0hpOGdsU3FkbXgySkJhZUFVWUNXCndNdlQ4NmNLclNyNkQrZG8wS05FZzFsL0FvR0FlMkFVdHVFbFNqLzBmRzgrV3hHc1RFV1JqclRNUzRSUjhRWXQKeXdjdFA4aDZxTGxKUTRCWGxQU05rMXZLTmtOUkxIb2pZT2pCQTViYjhibXNVU1BlV09NNENoaFJ4QnlHbmR2eAphYkJDRkFwY0IvbEg4d1R0alVZYlN5T294ZGt5OEp0ek90ajJhS0FiZHd6NlArWDZDODhjZmxYVFo5MWpYL3RMCjF3TmRKS2tDZ1lCbyt0UzB5TzJ2SWFmK2UwSkN5TGhzVDQ5cTN3Zis2QWVqWGx2WDJ1VnRYejN5QTZnbXo5aCsKcDNlK2JMRUxwb3B0WFhNdUFRR0xhUkcrYlNNcjR5dERYbE5ZSndUeThXczNKY3dlSTdqZVp2b0ZpbmNvVlVIMwphdmxoTUVCRGYxSjltSDB5cDBwWUNaS2ROdHNvZEZtQktzVEtQMjJhTmtsVVhCS3gyZzR6cFE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=    ---  kind: ConfigMap  apiVersion: v1  metadata:    name: nginx-config    namespace: nginx-ingress  data:    server-names-hash-bucket-size: "1024"      ---  kind: ClusterRole  apiVersion: rbac.authorization.k8s.io/v1beta1  metadata:    name: nginx-ingress  rules:  - apiGroups:    - ""    resources:    - services    - endpoints    verbs:    - get    - list    - watch  - apiGroups:    - ""    resources:    - secrets    verbs:    - get    - list    - watch  - apiGroups:    - ""    resources:    - configmaps    verbs:    - get    - list    - watch    - update    - create  - apiGroups:    - ""    resources:    - pods    verbs:    - list  - apiGroups:    - ""    resources:    - events    verbs:    - create    - patch  - apiGroups:    - extensions    resources:    - ingresses    verbs:    - list    - watch    - get  - apiGroups:    - "extensions"    resources:    - ingresses/status    verbs:    - update  - apiGroups:    - k8s.nginx.org    resources:    - virtualservers    - virtualserverroutes    verbs:    - list    - watch    - get    ---  kind: ClusterRoleBinding  apiVersion: rbac.authorization.k8s.io/v1beta1  metadata:    name: nginx-ingress  subjects:  - kind: ServiceAccount    name: nginx-ingress    namespace: nginx-ingress  roleRef:    kind: ClusterRole    name: nginx-ingress    apiGroup: rbac.authorization.k8s.io    ---  apiVersion: apps/v1  kind: DaemonSet  metadata:    name: nginx-ingress    namespace: nginx-ingress    annotations:      prometheus.io/scrape: "true"      prometheus.io/port: "9113"  spec:    selector:      matchLabels:        app: nginx-ingress    template:      metadata:        labels:          app: nginx-ingress      spec:        serviceAccountName: nginx-ingress        containers:        - image: nginx/nginx-ingress:1.5.5          name: nginx-ingress          ports:          - name: http            containerPort: 80            hostPort: 80          - name: https            containerPort: 443            hostPort: 443          - name: prometheus            containerPort: 9113          env:          - name: POD_NAMESPACE            valueFrom:              fieldRef:                fieldPath: metadata.namespace          - name: POD_NAME            valueFrom:              fieldRef:                fieldPath: metadata.name          args:            - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config            - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret           #- -v=3 # Enables extensive logging. Useful for troubleshooting.           #- -report-ingress-status           #- -external-service=nginx-ingress           #- -enable-leader-election            - -enable-prometheus-metrics           #- -enable-custom-resources

使用如下命令執行該文件:

kubectl apply -f nginx-ingress.yaml

查看是否安裝成功:

[root@instance-ji0xk9uh-1 ~]# kubectl get all -n nginx-ingress  NAME                      READY   STATUS    RESTARTS   AGE  pod/nginx-ingress-55dfk   1/1     Running   0          29s  pod/nginx-ingress-9847f   1/1     Running   0          29s    NAME                           DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE  daemonset.apps/nginx-ingress   2         2         2       2            2           <none>          29s

驗證是否成功:

# 打開瀏覽器,輸入任意worker節點的ip地址,回車,如果出現 404 NOT FOUND,表示nginx ingress安裝成功  # 因為現在還沒有安裝任何服務,所以出現404是正常的,這個就相當於是一個nginx,用來做後端請求的轉發。

總結

本章我們就把一個完整的k8s集群搭好了,你可能還比較迷茫,不用著急,我們下一章安裝Dashboard,更直觀的感受下k8s集群的魅力。

參考

本文參考自https://kuboard.cn/install/install-k8s.html,非常推薦這個網站,上面有很多k8s不錯的教程,非常適合新手入門。