2021年12月26日 星期日

AWS EKS Setup: Create via Terraform and manage the settings

本篇記錄如何使用 Terraform 建立一個 AWS EKS 服務並進行相關服務設定管理。


整體架構

  1. 使用 Terraform Cloudposse 佈署 EKS
  2. 加註 EKS 相關 Policy 設定,讓 EKS 有操作 Route53, ECR, ...etc 相關權限
  3. 本地端完成 EKS, eksctl 設定
  4. 使用 Helm 安裝 dashboard, 並從 kubectl proxy 訪問
  5. 從外部訪問 EKS 的 Ingress, kubectl port-forward
  6. 安裝 AWS Load Balancer Controller, 自動控制 Route53, 可使用 ALB 資源等
  7. Kubernetes EKS to using LoadBalance helping expose the inbound gateway
  8. VPC 之間進入 EKS 的方法,使得 EKS 可以存取其他 AWS 內部服務 (在 VPC Peer Connection 也有紀錄)


使用 Clousposse EKS 進行 Deploy


自建 EKS Terraform 會遇到許多 config 的問題,在這個狀況下比較適合使用 Cloudposse 寫好的 modules 直接使用,官方範例是: 

https://github.com/cloudposse/terraform-aws-eks-cluster/tree/master/examples/complete

不過本篇的範例自己有建立一個專屬的模組,有套用到特殊的 Policy,讓 EKS 本身具有以下權限:

  • 支援 Elastic Container Repository 操作,在 k8s pull 私人映像倉庫
  • 支援 AWS Route 53
  • 支援 AWS Elastic Load Balancer

模組請參考這個 Repo: 

https://github.com/hpcslag/infrastructure_boilerplate/tree/main/terraform/modules/eks_node_group

在這個模組中,IAM 權限主要是在 node_instance_role_policy.tpl:

https://github.com/hpcslag/infrastructure_boilerplate/blob/main/terraform/policies/node_instance_role_policy.tpl

這個意思是 EKS 的實體 (EC2) 本身具有的權限,或是說 pod、service, deployment...etc 操作時使用到的權限,如果有安裝一些 kuberentes extensions 要記得把 policy 加在這裡,然後直接 apply 即可。

另外需要注意本篇文章自己寫的 Cloudposse EKS Module 裡面的 subnet, vpc 也都是用 cloudposse 的,這類模組通常會讓你失去一些調整空間,會用比較麻煩的方式進行調整,如果碰到狀況請記得自己手動替換掉,直接用 resource (aws_vpc) 手寫它會減少很多 try & error 的時間。

其使用方式為:

// to remove it, use: terraform destroy -target=module.my_eks
module "my_eks" {
  source = "./modules/eks_node_group"
  endpoint_public_access = true

  region = "ap-southeast-1"
  account_id = "${data.aws_caller_identity.current.account_id}"
  availability_zones = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"]
  enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]
  instance_types = [ "t2.small" ]
  kubernetes_version = "1.20"
  kubernetes_labels = {
    
  }
  desired_size = 2
  max_size = 2
  min_size = 1
  cluster_encryption_config_enabled = false
  cluster_encryption_config_kms_key_enable_key_rotation = false

  # let codebuild can use credential get into k8s
  # follow setting: https://itnext.io/continuous-deployment-to-kubernetes-eks-using-aws-codepipeline-aws-codecommit-and-aws-codebuild-fce7d6c18e83 and https://www.padok.fr/en/blog/codepipeline-eks-helm
  // map_additional_iam_users = [
  //   {
  //     userarn  = something.codebuild_role_arn
  //     username = something.codebuild_role_name
  //     groups   = [ "system:masters" ]
  //   }
  // ]
}


其中最下面的 map_additional_iam_users 是給 CodeBuild 使用的,這個選項會再另一篇文章: EKS / Kubernetes: Upgrade Deployment Container Version by Helm and Code Build 做紀錄使用步驟。


EKS 建置有分為兩種建立 Instance 的策略: Node Group, Worker Group,其中 Node Group 是讓 AWS 自己管理每一個節點,只要你給他數字即可, Worker Group 是讓你自己給他 EC2 機器跟他們的定義,這個設定會很 Detail,本篇文章使用的是 Node Group 的策略。

要調整策略,詳情可以看 Cloudposse EKS 文件的說明。

使用 Terraform 安裝後,需要等待大概 9 分鐘完成建置,要注意不要在 Terraform EKS Creating 期間刪除 iam role, policy,這樣可能會導致 eks 無法成功建置,需要重新建立。


*額外須注意,如果要刪除這個 EKS 資源,記得要先用  terraform destroy -target=module.my_eks 這個指令,再把該段 module 程式碼移除,免得直接砍該段程式碼會發生模組 provider 找不到的問題。


設定 Kubectl


完成建立後,要讓本地電腦的 Kubectl 可以直接控制的話,要做 .kube/config 的設定,指令是:

aws eks --region [ap-southeast-1/改成自己的] update-kubeconfig --name cluster


安裝 Kubernetes Dashboard


完成 Kubectl 設定之後,就可以設定一個 Dashboard,這個 Dashhboard 就可以用網頁來檢查 Monitoring 狀況。


kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.5/aio/deploy/recommended.yaml

這裡還需要額外套用 service account 去存取更高階的 monitoring 資源,檔名叫做: sa.yaml:


apiVersion: v1
kind: ServiceAccount
metadata:
  name: eks-admin
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: eks-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: eks-admin
  namespace: kube-system


完成後就直接套用:

kubectl apply -f sa.yaml

現在已經完成有關 Dashboard 的安裝,接下來要進入 Dashboard,要用 proxy 的方式進去,方式是在本地電腦中使用 kube proxy,打開瀏覽器找:

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#!/login

接著要輸入 Token,相關的 Token 要下指令去產生:


kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep eks-admin | awk '{print $1}')

複製貼上之後就可以進去 Web UI  了。


有關 Dashboard 相關安裝方式,也可以參考官方的文件說明:

https://docs.aws.amazon.com/eks/latest/userguide/dashboard-tutorial.html


EKS 如何將服務對外


在 EKS 中只有使用 ELB 相關服務才可以將服務對外公開, ELB 有幾種類型的 Load Balance 可以使用,這裡只介紹 3 種常用的:

  1. Classic Load Balancer (aws 已不推薦使用,請盡量不要使用)
  2. Network Load Balancer (可以使用特定 Port, Protocol,以及 IP Mode)
  3. Application Load Balancer (只能做 HTTP, API 類的服務)
基本上 k8s cluster 上的資源只會被 ingress, service 公開, 第一種 CLB (Classic Load Balancer) 的使用方式是:

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress-loadbalancer
  namespace: default
spec:
  type: LoadBalancer
  selector:
    app: xxxxxxx
  ports:
    - protocol: TCP
      port: 80 # From URL incomming: 80
      targetPort: 8080 # mapping to 8080


一但套用這個設定之後,就可以在 kubectl get svc 看到 elb 的 hostname,而且在 AWS 後台的 Load Balancer 服務會看到上面有建立,而且寫 Classic Load Balancer 標註,還會提醒你升級。

這個方法請盡量不要使用,建議使用 2, 3。

要特別注意的是,2, 3 要建立的話,一定一定要安裝 AWS Load Balancer 這個 k8s extension 到你的 cluster,而且還要做相關設定,以下是 AWS Load Balancer 安裝。


AWS LoadBalance Controller 安裝


k8s cluster 要讓 service, ingress 自動建立 Load Balancer 有幾個前置設定條件: 
  1. Terraform K8S Label 設定
  2. 有給 IAM Role 權限去設定 elb 相關服務

1. 指的 k8s label 我已經在我的模組中有標註好了,他在 providers.tf 中,每個 eks cluster 的 subnet 中要有這樣的設定:

public_subnets_additional_tags = {
    "kubernetes.io/role/elb" : 1
}
private_subnets_additional_tags = {
    "kubernetes.io/role/internal-elb" : 1
}


第二個設定也可以參考 node_instance_role_policy.tpl 中的 elasticloadbalancing 相關權限。

然後,接著要安裝 cert manager:


kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.5.3/cert-manager.yaml


再安裝 AWS Load Balancer Controller:


kubectl apply -f https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.3.1/v2_3_1_full.yaml

要稍微注意這個 apply 要是出錯,就再 apply 一次應該就會成功。

詳情安裝說明也可參考 Kubernetes-sigs: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/deploy/installation/

如此一來,只要服務套用到類似的設定,就會自動建立 2, 3 類型的 ELB ,以下以 ingress 為例子:


apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: xxxxx-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    external-dns.alpha.kubernetes.io/hostname: xxx.aaaa.com # 可以搭配 route 53 binding 使用,在下一節介紹
    # alb.ingress.kubernetes.io/target-type: ip # nlb
    
    # SSL Setting, 下下節介紹
    # https://aws.amazon.com/premiumsupport/knowledge-center/terminate-https-traffic-eks-acm/
    # https://www.stacksimplify.com/aws-eks/aws-alb-ingress/learn-to-enable-ssl-on-alb-ingress-service-in-kubernetes-on-aws-eks/
    # Must issue first on aws
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
    alb.ingress.kubernetes.io/certificate-arn: [需要填上 certificate-arn]
spec:
  rules:
    - host: xxxx.bbbb.com # 用來 proxy 用的,可以接當 nginx,從 aaaa.com 進來要用 CNAME 變成 xxxx.bbbb.com 才會被轉到下面,這可以用在 cloudflare
      http:
        paths:
          - path: /*
            backend:
              serviceName: xxxxx-service
              servicePort: 80


其餘 NLB-IP Mode 請另外參考文件說明:


套用設定之後,應該就可以在 aws 後台看到 nlb, alb 類型的 load balancer,在 kubectl get svc 裡面也可以看到已經成功被分配地址,如果開 kubectl get svc 顯示 Pendding,表示有地方設定錯誤,可能要檢查看看 kubectl describe svc 看看有沒有權限錯誤,使用 kubectl describe 檢查錯誤是一個好的方法。


AWS: Route 53 自動建立 DNS 紀錄 - 安裝 External DNS


這裡可以讓 ingress, service 自動幫你建立指定名稱的 dns 記錄到你的 route 53 上,這個就必須要安裝 External DNS,這類自動設定 DNS 的功能就叫做 External DNS。

這裡要特別說,如果要讓 EKS 自動處理 Rotue 53 紀錄,那你的網域整個就會被 AWS 綁走,因為你會需要把你的域名 nameserver 改到 AWS,建議自己另外買一個 domain name 來掛給 Route 53,然後在你的主域名用 CNAME 轉過來。

設定方式還是有點複雜,也很難找到好的說明,所以我在這邊會記錄的較詳盡一些。

  1. 先建立一組 Policy Name: AmazonEKSClusterPolicy
    {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": [
              "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
              "arn:aws:route53:::hostedzone/*"
            ]
          },
          {
            "Effect": "Allow",
            "Action": [
              "route53:ListHostedZones",
              "route53:ListResourceRecordSets"
            ],
            "Resource": [
              "*"
            ]
          }
        ]
    }
  2. EKS 建立完之後,對應的 ODIC Policy 才會被建立出來,所以一定要手動做,去 AWS IAM 裡面,找到 Role,選擇建立一個 Role。
  3. 選擇建立 Web Identity
  4. Provider 選擇 ODIC,選到 EKS 那組 ODIC
  5. 找到剛才建立的那組 AmazonEKSClusterPolicy,把它 attach 進去你這組 role
  6. Role 名稱這裡取名 AmazonEKSClusterODICRole
  7. 建立後,這個 arn 要複製一下,等下要使用

接著,建立一個 external-dns.yaml 檔案,並請對裡面內容進行修改:


apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  # If you're using Amazon EKS with IAM Roles for Service Accounts, specify the following annotation.
  # Otherwise, you may safely omit it.
  annotations:
    # Substitute your account ID and IAM service role name below.
    eks.amazonaws.com/role-arn: arn:aws:iam::[你的帳號 ID]:role/AmazonEKSClusterODICRole
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
      # If you're using kiam or kube2iam, specify the following annotation.
      # Otherwise, you may safely omit it.
      annotations:
        iam.amazonaws.com/role: arn:aws:iam::[你的帳號 ID]:role/AmazonEKSClusterODICRole
spec: serviceAccountName: external-dns containers: - name: external-dns image: ghcr.io/kubernetes-sigs/external-dns/external-dns:latest args: - --source=service - --source=ingress - --provider=aws - --policy=sync # upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization https://stackoverflow.com/questions/67408554/bitnami-external-dns-does-not-remove-route53 - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) - --registry=txt - --txt-owner-id=my-hostedzone-identifier securityContext: fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files


然後做 kubectl apply -f external-dns.yaml 就可以安裝,然後它就會自己根據你的 ingress 去幫你建立 DNS,每分鐘掃一次。


其中模式常見有兩個: sync, upsert-only,分別用途是 sync 會刪除、更新、建立, upsert-only 只會更新、建立不會刪除。


Ingress 有 HTTPS 憑證,使用 AWS 憑證: Certification Manager 服務


在 Load Balancer 那一節有看到 alb.ingress.kubernetes.io/certificate-arn 這樣的設定,這是給 load balancer 套用憑證使用的,如果不套用憑證,你的 Domain Name 就會呈現不安全的提示,因此可以使用 AWS Certification Manager 來套用,如果有把 Cloudflare Domain 做 CNAME 轉過來,也可以用來當作 Endpoint SSL。

由於 AWS Cerification Manger 設定繁複,假設你的網域放在 Route53,設定比較方便,我為此寫了一個腳本,可以發行你的 Domain 的 Sub Domain SSL,並取得 ARN。

這組 SSL 的 ARN 就是要給 https://alb.ingress.kubernetes.io/certificate-arn 使用的,它可以自動套用 AWS 上的憑證,網頁就不會再不安全。

基本上你第一件事就是到確定 Route53 上有你的 Domain Name,然後執行下方我寫的 shell script:


set -e


echo "Please Enter the domain name ([sub].YourDomain.com): "
read DOMAIN_NAME
# DOMAIN_NAME=xxxxx.YourDomain.com

echo "Please Enter the original validation domain name (YourDomain.com): "
read VALIDATION_DOMAIN_NAME
# VALIDATION_DOMAIN_NAME=YourDomain.com

echo "Issuing certificate $DOMAIN_NAME..."

CREATED_ARN="$(aws acm request-certificate --domain-name $DOMAIN_NAME --validation-method DNS | jq -r '.CertificateArn')"

echo "Certificate issued: ($CREATED_ARN),

wait 10 sec to get pendding validation info..."

sleep 10

echo "Auto validating..."

PENDDING_VALIDATION_JSON="$(aws acm describe-certificate --certificate-arn $CREATED_ARN | jq '.Certificate | .DomainValidationOptions | .[0].ResourceRecord' )"

RecordName="$(echo $PENDDING_VALIDATION_JSON | jq -r '.Name')"
RecordType="$(echo $PENDDING_VALIDATION_JSON | jq -r '.Type')"
RecordValue="$(echo $PENDDING_VALIDATION_JSON | jq -r '.Value')"

echo "Get your route53 host zone id..."

Route53HostedZoneId="$(aws route53 list-hosted-zones-by-name | jq -r '.HostedZones | .[] | select(.Name="$VALIDATION_DOMAIN_NAME*") | .Id')"

echo "Create route53 validation record..."

aws route53 change-resource-record-sets --hosted-zone-id $Route53HostedZoneId --change-batch "$(echo "{
    \"Comment\": \"RECORD FOR VALIDATE CERTIFICATE\",
    \"Changes\": [{
        \"Action\": \"CREATE\",
        \"ResourceRecordSet\": {
            \"Name\": \"$RecordName\",
            \"Type\": \"$RecordType\",
            \"TTL\": 300,
            \"ResourceRecords\": [{ \"Value\": \"$RecordValue\" }]
        }
 }]
}")"

echo "Validation record created..."

echo "Wait for validation done..., 65 is decribe in docs, said that it will check every 60 sec for 250 times"

sleep 65

aws route53 change-resource-record-sets --hosted-zone-id $Route53HostedZoneId --change-batch "$(echo "{
    \"Comment\": \"RECORD FOR VALIDATE CERTIFICATE\",
    \"Changes\": [{
        \"Action\": \"DELETE\",
        \"ResourceRecordSet\": {
            \"Name\": \"$RecordName\",
            \"Type\": \"$RecordType\",
            \"TTL\": 300,
            \"ResourceRecords\": [{ \"Value\": \"$RecordValue\" }]
        }
 }]
}")"


echo "Checking Validate status..."

aws acm describe-certificate --certificate-arn $CREATED_ARN | jq '.Certificate | .DomainValidationOptions[0].ValidationStatus'


直接執行它,它的第一個問的就是你要簽的 SSL Sub Domain Name (也可以是 Main Domain),第二個問的是 Route 53 上你的 Domain Name 是什麼,然後它就會自己幫你簽,簽一次要等 60 秒左右。


把取得到的 arn 放到 k8s yaml file,它就會自己生效 SSL 了。


把 Pod / Service / Deployment 轉回本地端進行測試


雲端上的 Pod, Service, Deployment 會有點難除錯一些服務問題,可以把它直接導入本地環境進行測試,可以使用 kubectl port-forward 來處理,就可以從 localhost:xxxx 訪問到遠端服務。


其指令是:

kubectl port-forward pods/xxxxxx 28015:27017

一但進行 port-forward 後,就可以從 localhost:28015 存取到遠端 xxxx:27017 的服務,詳情其他 pod, svc, deploy,... 使用方式可以參考文件:
https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/


除錯 Kubectl 問題的常用方式


使用 Describe, Log 這兩個指令查看權限、pull 失敗是一個非常好的方案,而如果 deployment 的 container 使用 log 那組 pod 太久沒有回應,可能是你的資料庫連不上或其他服務連不上導致你的 application 卡住,然後重啟,重啟就會看不到 log,如果多試幾次剛好在噴 log 的時候查看到,就可以剛好看到它是資料庫連不上。


使用 VPC Peer Connection 讓 Kubectl 存取到 RDS、其他網路的 EC2 服務


文章前面有提到使用了 Cloudposse 的 VPC, Subnet 來建立 EKS 內部的網路,所以其他 EC2, RDS 網路自然都會被區分,而特別要注意的是 Cloudposse VPC 沒法調整 CIDR,你的網段基本上已經被寫死分配了,這個時候要避免想要連線其他的服務的 CIDR 也相同。

意思是假設 Cloudposse EKS 分配了 172.16.0.0/16 整段 CIDR,那你的 RDS, EC2...etc 服務的 VPC CIDR 一定要錯開 172.16.0.0/16,可以取名 10.100.0.0/16, 192.168.5.0/16,建立 Peer Connection 才可以錯開。

我的模組會需要讓你自己自訂 RDS 前綴,就是為了避免這個狀況發生。


這個 VPC Peer Connection 的具體用例我寫在 peering 模組中,它的範例是:


module "peering_eks_to_rds" {
  source = "./modules/peering"

  namespace = "eks2rds_connector"

  this_vpc_id = module.my_rds.vpc_id
  this_vpc_route_table_ids = [module.my_rds.vpc_route_table_id]
  this_vpc_cidr = module.my_rds.vpc_cidr
  this_vpc_security_group_id = module.my_rds.db_security_group_id

  peer_vpc_id = module.my_eks.eks_vpc_id
  peer_vpc_route_table_ids = module.my_eks.vpc_all_route_table_ids
  peer_vpc_cidr = module.my_eks.vpc_cidr
  peer_vpc_security_group_id = module.my_eks.eks_cluster_security_group_id


  depends_on = [
    module.my_rds,
    module.my_eks
  ]
}


在這裡建立 Peer Connection 後,通常 kubectl 還是會走 private subnet 去撈,建議連線時給出 public 的資源網址,基本上就可以被 routing table 找到。




*建議: 沒事不要把 Kubernetes 安裝在地端,要用 Cluster 服務最好還是上雲。


References:

https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/

https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md

https://www.padok.fr/en/blog/external-dns-route53-eks

https://docs.aws.amazon.com/eks/latest/userguide/dashboard-tutorial.html

https://docs.aws.amazon.com/eks/latest/userguide/create-kubeconfig.html

https://github.com/cloudposse/terraform-aws-eks-cluster

https://www.cnblogs.com/Star-Haitian/articles/15308758.html

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/deploy/installation/#iam-permissions

https://aws.amazon.com/tw/premiumsupport/knowledge-center/eks-api-server-unauthorized-error/

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/deploy/installation/

沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014