虽然网上有大量从零搭建K8S的文章,但大都针对老版本,若直接照搬去安装最新的1.20版本会遇到一堆问题。故此将我的安装步骤记录下来,希望能为读者提供copy and paste式的集群搭建帮助。 我是在腾讯云CentOS的2台服务器上,在不翻墙的情况下使用kubeadm(最简单的部署工具)搭建K8S集群。其中,容器网络使用flannel,最终安装好可以外网访问的2.0.4版本的dashboard。不走弯路的话,4步即可完成:
准备系统环境 对这2台新购的云虚拟机,都需要安装docker:
安装kubeadm前,需要先添加kubeadm的yum源:
1 2 3 4 5 6 7 8 cat > /etc/yum.repos.d/kubernetes.repo <<EOF [kubernetes ] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=0 repo_gpgcheck=0 EOF
接着安装目前最新版的kubeadm:
1 yum install kubeadm-1.20.1-0.x86_64
其中,kubeadm依赖的kubelet(K8S最核心的组件)、kubectl、kubernetes-cni网络插件等会自动安装。 最后启用这2个服务:
1 2 systemctl enable kubelet.service systemctl start docker.service
腾讯云主机默认已经将bridge-nf-call-iptables和ip_forward置为1,如果你的主机值为0,需要先将功能开启:
1 2 sysctl -w net.bridge.bridge-nf-call-iptables=1 sysctl -w net.ipv4.ip_forward=1
接着,针对部署K8S master节点的主机,还需要做些工作。由于腾讯云默认的主机名并不是域名格式(不符合master节点要求),所以需要修改为你自定义的域名。这需要2个步骤:
将设计好的主机名写入/etc/hostname文件;
将/etc/hosts中原主机名替换掉。
部署master节点 kubeadm的init命令即可初始化以单节点部署的master。为了避免翻墙,我使用了registry.aliyuncs.com/google_containers
源 。虽然可以在kubeadm命令行中输入源,比如:
1 kubeadm init --image-repository registry.aliyuncs.com/google_containers
但将其写入资源编排文件更易维护,我将其命名为kubeadm.yaml:
1 2 3 4 5 6 7 8 9 10 11 apiVersion: kubeadm.k8s.io/v1beta2 imageRepository: registry.aliyuncs.com/google_containers kind: ClusterConfiguration kubernetesVersion: v1.20.1 networking: podSubnet: 10.244 .0 .0 /16 controllerManager: ExtraArgs: horizontal-pod-autoscaler-use-rest-clients: "true" horizontal-pod-autoscaler-sync-period: "10s" node-monitor-grace-period: "10s"
注意,这个编排文件有4个细节:
接口版本使用了v1beta2;
为flannel分配的网段是10.244.0.0/16
;
选择的kubernetes版本是当前最新的1.20.1;
加入了controllerManager
的水平扩容功能。
接着,使用编排文件执行init命令:
1 kubeadm init --config ./kubeadm.yaml
注意,执行成功后kubeadm会返回类似下面的字样:
1 Your Kubernetes control-plane has initialized successfully!
之后,还有2条重要的信息: 1、后续kubectl默认控制集群时,需要使用到CA密钥,通过TLS协议保障通讯的安全性。我们要通过下面3行命令拷贝密钥信息,这样,kubectl执行时会首先访问当前用户的.kube目录,使用这些授权信息访问集群:
1 2 3 mkdir -p $HOME /.kube sudo cp -i /etc/kubernetes/admin.conf $HOME /.kube/config sudo chown $(id -u):$(id -g) $HOME /.kube/config
2、之后添加worker节点时,要通过token才能保障安全性。因此,先把显示的这行命令保存下来:
1 2 kubeadm join 172.27.0.11:6443 --token xxxxxxxxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxx
部署flannel网络 flannel网络需要指定IP地址段,在上一步中已经通过编排文件设置为10.244.0.0/16
。部署flannel只需要一行命令:
1 kubectl apply -f ./kube-flannel.yml
其中,由于原本可以直接下载的文件必须翻墙,所以我把文件内容列在下方,保存为上述命令的kube-flannel.yml文件(参见第5小节): 此时,调用get pods命令,可以看到master节点的组件和flannel网络都牌Running状态:
1 2 3 4 5 6 7 8 9 10 [root@k8s] NAME READY STATUS RESTARTS AGE coredns-7f89b7bc75-g8c42 1/1 Running 0 118s coredns-7f89b7bc75-z2p7c 1/1 Running 0 118s etcd-taohui.tech 1/1 Running 0 2m6s kube-apiserver-taohui.tech 1/1 Running 0 2m6s kube-controller-manager-taohui.tech 1/1 Running 0 2m6s kube-flannel-ds-kfzrg 1/1 Running 0 13s kube-proxy-qbdz5 1/1 Running 0 118s kube-scheduler-taohui.tech 1/1 Running 0 2m6s
接着,登入另一台服务器上,通过之前保存的kubeadm join命令,将其加入K8S集群。通过get nodes命令可以看到集群中已有2个节点:
1 2 3 4 NAME STATUS ROLES AGE VERSION taohui.tech Ready control-plane,master 14h v1.20.0 workernode Ready <none> 12h v1.20.1
至此集群部署成功!如果有参数错误需要修改,你也可以在reset后重新init集群:
部署dashboard的 我们可以用以WEB页面的可视化dashboard,监控集群的状态。部署时同样面临翻墙和版本匹配的问题,这里我将2个镜像源替换后保存为dashboard.yaml编排文件(参见第6小节):
1 kubectl apply -f dashboard.yaml
执行成功后,你可以查看到service:
1 2 3 4 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE dashboard-metrics-scraper ClusterIP 10.100.6.83 <none> 8000/TCP 13h kubernetes-dashboard ClusterIP 10.99.243.226 <none> 443/TCP 13h
注意,dashboard默认不允许外网访问。即使我们通过kubectl proxy允许外网访问,但dashboard又只允许HTTPS访问,这样kubeadm init时自签名的CA证书是不被浏览器承认的。我采用的方案是Nginx作为反向代理,使用Lets Encrypt提供的有效证书对外提供服务,再经由proxy_pass指令反向代理到kubectl proxy上,例如:
1 kubectl proxy --port=8888 --accept-hosts='^*$'
此时,本地可经由8888访问到dashboard。再通过Nginx访问它:
1 proxy_pass http://localhost:8888;
这样,当在外网根据我的域名访问如下URL:
1 https://mydomain/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/login
就会展现2种登陆方式: 接下来采用token方式。首先要创建管理员帐户:
1 2 3 4 5 6 7 cat <<EOF kubectl apply -f - apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard EOF
执行完后,serviceaccount/admin-user用户已经创建。接着,将用户绑定已经存在的集群管理员角色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 cat <<EOF kubectl apply -f - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard EOF
最后,获取可用户于访问的token:
1 kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret grep admin-user awk '{print $1}')
将token粘贴到登录页面,就可以看到dashboard了(下图是node页面):
用于部署flannel网络的资源编排文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 --- apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: psp.flannel.unprivileged annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default spec: privileged: false volumes: - configMap - secret - emptyDir - hostPath allowedHostPaths: - pathPrefix: "/etc/cni/net.d" - pathPrefix: "/etc/kube-flannel" - pathPrefix: "/run/flannel" readOnlyRootFilesystem: false runAsUser: rule: RunAsAny supplementalGroups: rule: RunAsAny fsGroup: rule: RunAsAny allowPrivilegeEscalation: false defaultAllowPrivilegeEscalation: false allowedCapabilities: ['NET_ADMIN' , 'NET_RAW' ] defaultAddCapabilities: [] requiredDropCapabilities: [] hostPID: false hostIPC: false hostNetwork: true hostPorts: - min: 0 max: 65535 seLinux: rule: 'RunAsAny' --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: flannel rules: - apiGroups: ['extensions' ] resources: ['podsecuritypolicies' ] verbs: ['use' ] resourceNames: ['psp.flannel.unprivileged' ] - apiGroups: - "" resources: - pods verbs: - get - apiGroups: - "" resources: - nodes verbs: - list - watch - apiGroups: - "" resources: - nodes/status verbs: - patch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: flannel roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: flannel subjects: - kind: ServiceAccount name: flannel namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: flannel namespace: kube-system --- kind: ConfigMap apiVersion: v1 metadata: name: kube-flannel-cfg namespace: kube-system labels: tier: node app: flannel data: cni-conf.json: { "name": "cbr0" , "cniVersion": "0.3.1" , "plugins": [ { "type": "flannel" , "delegate": { "hairpinMode": true , "isDefaultGateway": true } }, { "type": "portmap" , "capabilities": { "portMappings": true } } ] } net-conf.json: { "Network": "10.244.0.0/16" , "Backend": { "Type": "vxlan" } } --- apiVersion: apps/v1 kind: DaemonSet metadata: name: kube-flannel-ds namespace: kube-system labels: tier: node app: flannel spec: selector: matchLabels: app: flannel template: metadata: labels: tier: node app: flannel spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/os operator: In values: - linux hostNetwork: true priorityClassName: system-node-critical tolerations: - operator: Exists effect: NoSchedule serviceAccountName: flannel initContainers: - name: install-cni image: quay.io/coreos/flannel:v0.13.1-rc1 command: - cp args: - -f - /etc/kube-flannel/cni-conf.json - /etc/cni/net.d/10-flannel.conflist volumeMounts: - name: cni mountPath: /etc/cni/net.d - name: flannel-cfg mountPath: /etc/kube-flannel/ containers: - name: kube-flannel image: quay.io/coreos/flannel:v0.13.1-rc1 command: - /opt/bin/flanneld args: - --ip-masq - --kube-subnet-mgr resources: requests: cpu: "100m" memory: "50Mi" limits: cpu: "100m" memory: "50Mi" securityContext: privileged: false capabilities: add: ["NET_ADMIN" , "NET_RAW" ] env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumeMounts: - name: run mountPath: /run/flannel - name: flannel-cfg mountPath: /etc/kube-flannel/ volumes: - name: run hostPath: path: /run/flannel - name: cni hostPath: path: /etc/cni/net.d - name: flannel-cfg configMap: name: kube-flannel-cfg
用于部署dashboard的资源编排文件dashboard.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 apiVersion: v1 kind: Namespace metadata: name: kubernetes-dashboard --- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-certs namespace: kubernetes-dashboard type: Opaque --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-csrf namespace: kubernetes-dashboard type: Opaque data: csrf: "" --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-key-holder namespace: kubernetes-dashboard type: Opaque --- kind: ConfigMap apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-settings namespace: kubernetes-dashboard --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard rules: - apiGroups: ["" ] resources: ["secrets" ] resourceNames: ["kubernetes-dashboard-key-holder" , "kubernetes-dashboard-certs" , "kubernetes-dashboard-csrf" ] verbs: ["get" , "update" , "delete" ] - apiGroups: ["" ] resources: ["configmaps" ] resourceNames: ["kubernetes-dashboard-settings" ] verbs: ["get" , "update" ] - apiGroups: ["" ] resources: ["services" ] resourceNames: ["heapster" , "dashboard-metrics-scraper" ] verbs: ["proxy" ] - apiGroups: ["" ] resources: ["services/proxy" ] resourceNames: ["heapster" , "http:heapster:" , "https:heapster:" , "dashboard-metrics-scraper" , "http:dashboard-metrics-scraper" ] verbs: ["get" ] --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard rules: - apiGroups: ["metrics.k8s.io" ] resources: ["pods" , "nodes" ] verbs: ["get" , "list" , "watch" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: kubernetes-dashboard template: metadata: labels: k8s-app: kubernetes-dashboard spec: containers: - name: kubernetes-dashboard image: registry.cn-shanghai.aliyuncs.com/jieee/dashboard:v2.0.4 imagePullPolicy: Always ports: - containerPort: 8443 protocol: TCP args: - --auto-generate-certificates - --namespace=kubernetes-dashboard volumeMounts: - name: kubernetes-dashboard-certs mountPath: /certs - mountPath: /tmp name: tmp-volume livenessProbe: httpGet: scheme: HTTPS path: / port: 8443 initialDelaySeconds: 30 timeoutSeconds: 30 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 volumes: - name: kubernetes-dashboard-certs secret: secretName: kubernetes-dashboard-certs - name: tmp-volume emptyDir: {} serviceAccountName: kubernetes-dashboard nodeSelector: "kubernetes.io/os": linux tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule --- kind: Service apiVersion: v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: ports: - port: 8000 targetPort: 8000 selector: k8s-app: dashboard-metrics-scraper --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: dashboard-metrics-scraper template: metadata: labels: k8s-app: dashboard-metrics-scraper annotations: seccomp.security.alpha.kubernetes.io/pod: 'runtime/default' spec: containers: - name: dashboard-metrics-scraper image: registry.cn-shanghai.aliyuncs.com/jieee/metrics-scraper:v1.0.4 ports: - containerPort: 8000 protocol: TCP livenessProbe: httpGet: scheme: HTTP path: / port: 8000 initialDelaySeconds: 30 timeoutSeconds: 30 volumeMounts: - mountPath: /tmp name: tmp-volume securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 serviceAccountName: kubernetes-dashboard nodeSelector: "kubernetes.io/os": linux tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule volumes: - name: tmp-volume emptyDir: {}