AWS 中國區部署 Kubernetes 1.9.3

AWS 中國區部署 Kubernetes 1.9.3

來自專欄即刻技術團隊

本文檔初版寫於 2018 年 6 月, kops release 最新版本為 1.9.1

本文沒有覆蓋 Kubernetes 的基礎知識,不清楚的應該去官網跑一遍 minikube 的 demo

0. 目標和背景

即刻技術團隊一直在實踐 DevOps 文化,部署 Kubernetes 集群的目標是 Production Ready,要求集群穩定性必須達到生產標準。

即刻到目前為止已經在生產環境使用過兩套 Kubernetes 部署方案,這兩套方案都有穩定性以及其他方面的問題。

第一套方案:juju

juju 使用很方便,但是社區不活躍,可定製性太弱,而且後來遇到了 ubuntu 實例的 kernel panic 問題。

第二套方案:kubespray

選擇第二套方案的時候 kops 還沒有集成 gossip,無法使用於 AWS 中國區,當時選擇了可定製性極強的 kubespray。

kubespray 本身沒有什麼大問題,其問題反而是太過靈活,缺乏最佳實踐。結果遇到了這些問題:

  • 從過去的經驗出發我當時選擇了 CentOS 作為操作系統,但是後來頻繁遇到 docker issue 5618,沒有太好的解決方案,這個 bug 在 CentOS / RedHat 上更容易觸發;
  • launch configuration 和 autoscaling group 需要自己寫腳本管理,很容易出錯;
  • 縮容的時候需要做一些繁瑣的事情,包括把它從 autoscaling group 中移出,drain 它,刪除 node,如果使用了 calico 還需要把它從 calico 配置中移走,最後才能關閉機器。

在選擇第三套方案的時候我們吸取了前兩次的經驗,需要在易用性和可定製性之間找到一個平衡,而同時 kops 已經集成了 gossip 部署方案,看起來是一個成熟的解決方案。但是在實施過程中我們還是遇到了很多問題,以至於切換集群的半個多月中產生了若干次的服務故障。本文後面會覆蓋到我們踩過的主要的坑,但也難免有所遺漏,歡迎大家提問和補充。


1. 平台、工具選擇

  • 部署工具:kops,因為它不需要很多定製就可以直接集成以下功能
    • EBS -> PV
    • LoadBalancer
    • flannel vxlan
    • AutoScalingGroup
  • 操作系統:debian jessie,雖然功能落後於 ubuntu,但是穩定

2. 難題和解決方案

AWS 中國區沒有 Route53 這個 DNS 服務

自從 kops 1.7 以後,就支持 gossip 了,只要 cluster 名字是以 k8s.local 結尾,就不用做任何 DNS 配置。

缺少基礎 AMI

中國區的 market-place 沒有 k8s-1.8-debian-jessie-amd64-hvm-ebs-YYYY-mm-dd 這樣的基礎鏡像,需要自己 build 一個。

build 出來的鏡像 kernel 是定製的,內核參數也已經做過調優,可以直接投入生產使用。

flannel 的坑

想使用 flannel,還需要載入一個內核模塊 br_netfilter,否則會遇到這個 kube-dns 的問題。比較方便的辦法是製作 AMI 之前把它寫到 /etc/modules 文件中。

有些基礎組件還是放在下載困難的地方

原本 kops 支持配置 containerRegistry 的,但是目前的版本似乎有 bug,這個選項無法生效,為此需要做一些額外的工作,主要包括:

  • offline mode 離線模式
  • 拉一些別的鏡像再生成新的 AMI:用 build 出來的 AMI 創建一個 EC2 實例,然後設置好代理,拉好以下鏡像:

gcr.io/google_containers/cluster-proportional-autoscaler-amd64:1.1.2-r2gcr.io/google_containers/etcd:2.2.1gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.10gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.10gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.10gcr.io/google_containers/pause-amd64:3.0gcr.io/google_containers/kube-apiserver:v1.9.3gcr.io/google_containers/kube-controller-manager:v1.9.3gcr.io/google_containers/kube-proxy:v1.9.3gcr.io/google_containers/kube-scheduler:v1.9.3

提示:假如不確定還需要什麼鏡像,可以在嘗試實施部署,設置好全局的 HTTP 代理,可以順利部署完,然後就可以看到鏡像列表了

提示的提示:但是部署了 HTTP 代理的集群不應該投入生產環境使用


3. 開始部署

3.0 準備工作

  • 創建一個 IAM user,其中 Route53 的部分跳過
  • 想一個以 .k8s.local 結尾的集群名字,比如 jike-test.k8s.local

3.1 配置 AWS 環境

下面的腳本中,所有以 your- 開頭的名字都是隨便取的,請按實際情況修改。

# 安裝 awsclipip install awscli# 生成 awscli 使用的本地配置,輸入上一步驟拿到的 key 和 secret,還有 region,北京區那就是 cn-north-1。如果已經配置過則可以跳過aws configureexport AWS_REGION=$(aws configure get region)export AWS_AZ=a # 可用區export NAME=your-cluster.k8s.local# 開一個 s3 bucket 存放 kops stateexport KOPS_STATE_STORE=your-kops-storeaws s3api create-bucket --bucket $KOPS_STATE_STORE --create-bucket-configuration LocationConstraint=$AWS_REGION# 從 key-pair 生成一個 public keyexport SSH_PUBLIC_KEY=your-public-key.pub # 這個 public key 將會被 copy 到集群中的所有節點上,方便 ssh 登陸ssh-keygen -f pemfile.pem -y >${SSH_PUBLIC_KEY}# 在真正部署之前所有需要的環境變數還有這些沒配置過export VPC_ID=your-vpc # 打算放到哪個 VPCexport VPC_NETWORK_CIDR=your-vpc-network-cidr # VPC 的 CIDRexport SUBNETS=your-vpc-subnet # VPC 的哪個子網export UTILITY_SUBNETS=your-vpc-utility-subnet # 暫時沒用上,如果需要部署 bastion host,會放在這個子網中export AMI=your-ami # AMI 的 source,注意前面有串數字export KUBERNETES_VERSION="v1.9.3" # 雖然 kops 1.9.1 發布的時候 kubernetes 已經發布了 1.9.7,但是推薦的版本還是 1.9.3export KOPS_VERSION="1.9.1"export ASSET_BUCKET="kops-asset" # offline mode 需要export KOPS_BASE_URL="https://s3.cn-north-1.amazonaws.com.cn/$ASSET_BUCKET/kops/$KOPS_VERSION/" # 同上export CNI_VERSION_URL="https://s3.cn-north-1.amazonaws.com.cn/$ASSET_BUCKET/kubernetes/network-plugins/cni-plugins-amd64-v0.6.0.tgz" # 需要的 CNIexport CNI_ASSET_HASH_STRING="d595d3ded6499a64e8dac02466e2f5f2ce257c9f" # CNI 的 sha1 hashexport SSH_ACCESS=your-cidr1,your-cidr2 # 逗號分隔的 CIDR,用於限制 ssh 登陸的來源 IP,會寫進安全組裡

3.2 創建集群

kops create cluster --zones ${AWS_REGION}${AWS_AZ} --vpc ${VPC_ID} --network-cidr ${VPC_NETWORK_CIDR} --image ${AMI} --associate-public-ip=true --api-loadbalancer-type public # 如果要在公網使用 kubectl 控制集群,就設置為 public,否則設置為 internal --topology public # 節點放在 public 子網 --networking flannel # 默認 backend 就是 VXLAN --kubernetes-version https://s3.cn-north-1.amazonaws.com.cn/$ASSET_BUCKET/kubernetes/release/$KUBERNETES_VERSION --ssh-public-key ${SSH_PUBLIC_KEY} --subnets ${SUBNETS} --utility-subnets ${UTILITY_SUBNETS} # bastion 會使用 --master-count 3 # master 節點數量,建議配置為至少 3,後面不好變更 --master-size m4.xlarge # 參看這裡:https://kubernetes.io/docs/admin/cluster-large/ --node-count 1 --node-volume-size 200 ${NAME}

3.3 實施前的設置

到這裡,我們已經在 state store 中生成了一份集群的配置,但是還沒有實施部署。

看一下目前的集群配置

kops get clusters $NAME -o yaml

看一下所有所有的配置,包括 instance groups(後面簡稱為 IG)

kops get --name $NAME -o yaml

列出所有的 IG

kops get ig --name $NAME

目前能看到兩種 ROLE:master 和 node

默認配置需要做一些調整以保證其正常工作,配置主要包含集群配置和 IG 配置。

  • 修改集群配置 kops edit cluster $NAME

spec: docker: registryMirrors: - https://xxxx # docker registry mirror 地址,加速 docker.io 上的鏡像的下載速度 kubelet: # 一些 kubelet cgroup 相關的配置 kubeletCgroups: "/systemd/system.slice" # 見 https://github.com/kubernetes/kops/issues/4049 runtimeCgroups: "/systemd/system.slice" imageGCHighThresholdPercent: 70 # 舊鏡像回收 imageGCLowThresholdPercent: 50 # 同上 masterKubelet: # 一些 master 上的 kubelet cgroup 相關的配置 kubeletCgroups: "/systemd/system.slice" runtimeCgroups: "/systemd/system.slice"

  • 修改 IG 配置 kops edit ig your-ig-name --name $NAME

spec: rootVolumeOptimization: true # 打開 EBS 優化 rootVolumeSize: 200 # volume 大小 nodeLabels: [...] # 一些自定義的標籤 taints: [...] # 一些 taint

3.4 實施部署

kops update cluster $NAME --yes

等幾分鐘,然後

kops validate cluster

如果顯示為 Ready,那就一切就緒了

假如顯示 NotReady,並且提示說 kube-dns 沒有啟動,有可能是給 Node 加的 taint 使得 kube-dns 找不到可以分配的 node,可以通過修改 toleration 去解決。

3.5 部署以後

  • 增加、修改、刪除 IG

kops create ig $IG_NAME --name $NAME --edit

注意修改配置,尤其是 spec.image,默認的鏡像在中國區是找不到的。

  • 安裝 kubernetes-dashboard

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

確認相關 pod 已經起來以後

kubectl proxy

這時使用瀏覽器打開

http://localhost:8001/ui

會發現需要輸入一些東西才能登陸,和以前不一樣了。

注意,我們創建集群時生成的 config 文件是不能直接當作登陸憑證的。具體方法見文檔

  • 給 Master / Node 增加 IAM Statement

kops 支持對的特定 role 增加 IAM Statement。

注意是 role 而不是 IG。所以如果要給一個 node 增加一個 Statement,那麼所有的 node 都會有這個 Statement。

先看一下原來的 Node 有哪些策略:

aws iam list-role-policies --role-name nodes.${NAME}

結果是

{ "PolicyNames": [ "nodes.${NAME}" ]}

注意這裡的 role name 和 policy name 用了同一個名字。這是一個 Inline 的 policy,看看裡面有什麼 Statement,運行

aws iam get-role-policy --role-name nodes.${NAME} --policy-name nodes.${NAME}

結果是

{ "RoleName": "nodes.${NAME}", "PolicyName": "nodes.${NAME}", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "kopsK8sEC2NodePerms", "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:DescribeRegions" ], "Resource": [ "*" ] }, { "Sid": "kopsK8sS3GetListBucket", "Effect": "Allow", "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Resource": [ "arn:aws-cn:s3:::kops-k8s-v1-state-store" ] }, { "Sid": "kopsK8sS3NodeBucketSelectiveGet", "Effect": "Allow", "Action": [ "s3:Get*" ], "Resource": [ "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/addons/*", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/cluster.spec", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/config", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/instancegroup/*", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/pki/issued/*", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/pki/private/kube-proxy/*", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/pki/private/kubelet/*", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/pki/ssh/*", "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/secrets/dockerconfig" ] }, { "Sid": "kopsK8sS3NodeBucketGetKuberouter", "Effect": "Allow", "Action": [ "s3:Get*" ], "Resource": "arn:aws-cn:s3:::kops-k8s-v1-state-store/jike-a.k8s.local/pki/private/kube-router/*" }, { "Sid": "kopsK8sECR", "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:GetRepositoryPolicy", "ecr:DescribeRepositories", "ecr:ListImages", "ecr:BatchGetImage" ], "Resource": [ "*" ] } ] }}

可以看到這裡面有一些 Statement,每個對應了一組 Permission。

接下來給 Node 增加一個 Permission Statement:

kops edit cluster

然後 .spec 中增加:

spec: additionalPolicies: node: | [ { "Action": ["ec2:*"], "Effect": "Allow", "Resource": "*" } ]

然後運行

kops update cluster --yes

再次運行

aws iam list-role-policies --role-name nodes.${NAME}

結果變了:

{ "PolicyNames": [ "additional.nodes.${NAME}", "nodes.${NAME}" ]}

其中 additional.nodes.${NAME} 這個 policy inline 地包含了我們剛剛增加的 permission,驗證一下:

aws iam get-role-policy --role-name nodes.${NAME} --policy-name additional.nodes.${NAME}

結果為

{ "RoleName": "nodes.${NAME}", "PolicyName": "additional.nodes.${NAME}", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "ec2:*" ], "Resource": "*" } ] }}


4. 坑

kube-dns 的 scale 問題

運行一段時間後發現有些網路問題,pod 之間互相訪問不到,最後發現可能是 DNS 解析失敗了,究其原因是 kube-dns pod 數量不夠。

直接改 scale 之後發現會被 cluster-proportional-autoscaler 改回去,這是因為這個 autoscaler 是根據集群規模去修改 kube-dns 的規模的,主要參考 node 數量和 cpu 核心數量,所以想增加 kube-dns,就要修改 autoscaler 的配置。

HPA 不工作

有這個 issue,需要部署 metrics-server。

如果不想部署,那可以在 ClusterSpec 里定義 KubeControllerManagerConfig.horizontalPodAutoscalerUseRestClients


5. 常見問題

LoadBalancer Service 怎麼使用

本來以為可以直接把 pod 路由到 ELB 上,試了一下發現還是創建了 NodePort,然後讓 ELB 監聽 NodePort。這樣做有方便的地方,比如不需要自己去管理 ELB 到 Auto Scaling Group 的關係,也可以自動配上 ssl 證書。

但是,有幾點需要注意

  • HTTPS 的問題

一個 Service,如果配置了兩個埠,並且配置了 SSL 證書,那麼默認情況下這兩個埠都會變成 HTTPS 的。如果想要只有指定埠使用 HTTPS,其餘埠走 HTTP,需要設置 service.beta.kubernetes.io/aws-load-balancer-ssl-ports

  • 如何使用 Application Load Balancer(ALB)

不能支持,目前只能建立 Classic 的 ELB,所以也有後面的 WebSocket 問題

  • 如何支持 WebSocket?

不方便支持,因為目前 kops 只能建出 ELB 而建不出 ALB,除非打開 Proxy Protocol,但是這樣配置起來很麻煩。

支持 WebSocket 最好的辦法是使用 ALB,方法:

  1. 建一個 NodePort 類型的 Service 而不是 LoadBalancer 類型的 Service
  2. 把適當的 instance-group 對應的 auto-scaling-group 指到一個 Target Group 上
  3. 把一個 Application LB 指到這個 Target Group 上
  • 手動配置被重置的問題

一個 Service 生成的 ELB,如果去 AWS 控制台上加了一些自定義的配置,例如增加埠,那麼在 master 被重啟或者 rollingUpdate 時,ELB 上的配置會被重置為 Service 自動生成的配置。所以不要去手動修改 ELB 的配置,否則後期會難以升級集群。

  • 一個 TargetPort 必須對應一個 NodePort

有時候希望一個 TargetPort 映射到一個 NodePort,一個 NodePort 映射到 ELB 的兩個埠,這件事做不到。

update 和 rolling-update 操作

rolling-update 是 kops 最棒的特性之一,可以在盡量不傷害集群的情況下對集群進行滾動升級,但是 kops 的新手經常不知道什麼時候應該運行 rolling-update 操作。

總的來說,如果變更在 node 重啟前無法生效,就必定需要 rolling-update。

所幸,不管是 update 還是 rolling-update,都需要加上 --yes 才會實裝,所以如果不確定,可以先跑一次 kops rolling-update cluster --name $NAME 看看結果。

以下是一些提示:

  • 有些操作可能出乎意料地需要 rolling update,例如給 IG 里的 node 加 label
  • rolling update 的默認操作間隔很長,可以通過參數調整:--master-interval 和 --node-interval
  • rolling update 如果中斷,不用擔心,可以 resume,直到集群達到預定的目標
  • 如果要強制進行 rolling update,可以加上 --force

為什麼 docker storage-driver 使用 overlay 而不是 overlay2

AMI 里的 docker 版本是 17.03.2,為什麼使用的 storage-driver 是 overlay 而不是 overlay2:stretch 才推薦 overlay2。

為什麼默認 CIDR 是 100.64.0.0/10 ?

K8S 內的網路(包括 Pod 和 Service)使用的 CIDR 默認是 100.64.0.0/10,這是故意的,當然在建立集群的時候也可以改。

如何啟用 bastion?

使用 Bastion 主機作為跳板機,可以把集群中的節點與公網隔離開。

可以在建集群的時候加上 --bastion 選項,但是 kops 會要求 master 和 node 的 topology 都是 private 的。如果已經新建了 public 的 master 或 node,那麼可以在集群建立以後再加入 bastion:

kops create instancegroup bastions --role Bastion --subnet ${UTILITY-SUBNET}

UTILITY-SUBNET 前文中提到過。

不考慮 IPVS 嗎?

K8S 1.9 中 IPVS 模式已經進入 beta。實際上我們嘗試過 kube-router,但是網路測試的結果非常不理想。儘管快的時候很快,但是穩定性非常差,看起來還不能用於生產環境。除此以外還有很多隱藏的坑,不在此詳述了。


作者:若瑜(知乎 && 即刻)

參考:

Kubernetes 官方文檔

Kops 官方文檔


推薦閱讀:

(1 條消息)AWS EC2一個月免費750小時,為什麼還被扣費?
安恆上阿里雲了,這事兒大家怎麼看?
AWS所選用戶密匙未在遠程主機上註冊,請問該怎麼解決?
Amazon AWS 註冊時的一美元到哪去了?能退回來嗎?
如何評價AWS中國?

TAG:Kubernetes | AmazonWebServicesAWS | DevOps |