EKSWorkshopを途中までやってみた

手動 or kopsを使ってなんとなく構築したことはあったけどeksctlはなかったので試してみた
今回試したKubernetesのバージョンは1.13なので若干古い

内容がめちゃくちゃ多かったのでとりあえずSpotInstanceを利用するところまで

Step1

  • Workshop用のユーザーアカウントの作成(admin権限)
  • Cloud9の環境作成(東京リージョンきててよかった)

Step2

  • Cloud9を起動する(Adblock系を入れてるとリクエスト弾いてローディング終わらないので気をつけたほうがよさそう)
  • kubectlをおとす + 実行権限付与
  • 必要なパッケージをインストール
  • 必要なバイナリにパスが通ってるかチェック

Step3

  • 指示に従ってEC2用のIAMRoleを発行する(admin権限)
  • 先程起動したCloud9のインスタンスのロールを新しく作ったものに切り替える
  • Cloud9でEC2の権限のリセットをする
  • ACCOUNT_IDとREGIONをプロファイルに書き込んで、Roleが先程アタッチしたものに切り替わったことを確認する

Step4

  • EKSdemo用のリポジトリを落としてくる
  • ssh-keygenでSSH鍵作成する
  • 公開鍵をAWSのEC2のキーペアに登録する

Step5

  • eksctlを落として、パスの通ったところに配置する
  • 自身のroleを再確認して、eksクラスターを作成する(MLやりたい場合はP3インスタンスで構築する)
  • 15分ぐらいかかるっぽいのでお茶を飲む(EKS側の画面で確認すると色々なリソース作成されてるのがわかる)
    • CIDRは192.168.0.0/16
    • SubnetはAZ(1a, 1d, 1c)毎にPublic/Privateで構築される
    • SGはインバウンド閉じててアウトバウンドがAll
    • Roleは管理ポリシーが
      AmazonEKSClusterPolicy/AmazonEKSServicePolicy
      インラインポリシーが
      eksctl-eksworkshop-eksctl-cluster-PolicyCloudWatchMetrics(CloudWatch)/eksctl-eksworkshop-eksctl-cluster-PolicyNLB(EC2/ELB(1,2))
  • マスターが構築されると詳細が増える
    • APIサーバのエンドポイントの発行
    • OpenID Connect Providerの発行
    • nodeが3台構築される
      • m5.large
      • それぞれのAZのPublic Subnet
      • SGはインバウンド(1025 – 65535, 443)でアウトバウンドはAll
      • Roleは管理ポリシーがAmazonEKSWorkerNodePolicy/AmazonEC2ContainerRegistryReadOnly/AmazonEKS_CNI_Policy
        インラインポリシーが
        eksctl-eksworkshop-eksctl-nodegroup-ng-xxxxxxxxx-PolicyALBIngress(Certificate Manager/EC2/ELB(1,2)/IAM/ResourceGroupTagging/WAF/WAFRegional)
      • PublicIP有

Step6

  • kubectl get nodesでnode3台たったことを確認する
  • INSTANCE_PROFILE_ARNとROLE_NAMEをプロファイルに書き込む
  • MasterとWorkerの構築自体はここで終わり(コピペで済むので非常に楽)

Step7

ダッシュボードの構築をする

  • ダッシュボードの作成
  • terminalでproxyを立ち上げてCloud9のブラウザから接続する(https://hogehoge.ap-northeast-1.amazonaws.com/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/)
  • 別のターミナルでtoken取得してtoken認証してアクセスする(Cloud9のポップアウト機能便利)

Step8

RBACについて

  • namespaceの作成
  • 作ったnamespaceにnginxのデプロイをする
  • IAMUserを新規作成して、アクセスキーを発行する
  • rbac-user切り替え用のshellを作る
  • k8sのuserを作成する(先程作ったIAMUserにmappingする)
  • 新規userに切り替えて情報を見る(userに対してロールがバインドされていないのでエラーが返ることを確認する)
  • 管理者userに戻ってロールとロールバインドを作成する
  • 新規userに切り替えて先程失敗したコマンドが成功することを確認する

Step9

Sampleのマイクロサービスをデプロイする

  • 先程おとしたリポジトリから各種サンプルをデプロイする(一部のサービスでCLBが構築される)
  • external ipを使ってアクセスする(どこからでも見れるはず)
  • バックエンドのscale outを試す(これでバックエンドが分散される)
  • 再度external ipを使ってアクセスする(読み込み続けて異なるバックエンドにアクセスしていることがわかる)
  • フロントエンドのscale outを試す(これでフロントエンドが分散される)
  • 再度external ipを使ってアクセスする(読み込み続けて異なるフロントエンドにアクセスしていることがわかる)
  • デプロイしたものを掃除する

Step10

Helmについて

  • サービスアカウントを作成してロールをバインドする
  • helm initで作成したサービスアカウントを指定する

Step11

Helmでnginxをデプロイする

  • デフォルトリポジトリを更新
  • bitnamiのリポジトリを追加する
  • nginxのchartを探す
  • helm経由でnginxをデプロイする
  • external ipを使ってアクセスする
  • デプロイしたものを掃除する

Step12

Helmでマイクロサービスをデプロイする

  • eksdemoのchartを作成する
  • それぞれのimageを柔軟に指定できるように変更する
  • 作成したyamlに問題がないかをdryrunで確認する
  • helm経由でデプロイする(deploy後のdashboard)
  • external ipを使ってアクセスする(前回と同様に見える)
  • 存在しないイメージを指定してデプロイする(ImagePullBackOff)
  • ロールバックを実行する
  • デプロイしたものを掃除する

Step13

ヘルスチェックについて

  • livenessProbeを定義したpodを作成する
  • SIGKILLを送ってコンテナを殺してヘルスチェックに失敗するようにして再起動させる
Events:
  Type     Reason     Age                   From                                                      Message
  ----     ------     ----                  ----                                                      -------
  Normal   Scheduled  4m35s                 default-scheduler                                         Successfully assigned default/liveness-app to ip-192-168-7-96.ap-northeast-1.compute.internal
  Warning  Unhealthy  2m9s (x3 over 2m19s)  kubelet, ip-192-168-7-96.ap-northeast-1.compute.internal  Liveness probe failed: Get http://192.168.12.165:3000/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
  Normal   Pulling    99s (x2 over 4m34s)   kubelet, ip-192-168-7-96.ap-northeast-1.compute.internal  pulling image "brentley/ecsdemo-nodejs"
  Normal   Killing    99s                   kubelet, ip-192-168-7-96.ap-northeast-1.compute.internal  Killing container with id docker://liveness:Container failed liveness probe.. Container will be killed and recreated.
  Normal   Pulled     97s (x2 over 4m32s)   kubelet, ip-192-168-7-96.ap-northeast-1.compute.internal  Successfully pulled image "brentley/ecsdemo-nodejs"
  Normal   Created    97s (x2 over 4m32s)   kubelet, ip-192-168-7-96.ap-northeast-1.compute.internal  Created container
  Normal   Started    96s (x2 over 4m32s)   kubelet, ip-192-168-7-96.ap-northeast-1.compute.internal  Started container
  • redinessProbeを定義したpodを作成する
  • healthcheck用のファイルを削除する
  • unavailableになったことを確認する & 再度ファイルを作成してavailableになることを確認する
  • デプロイしたものを掃除する

Step14

Horizontal Pod Autoscaler(HPA)及びCluster Autoscaler(CA)でAutoscalingを実装する

  • helm経由でmetrics-serverをデプロイする
  • サンプルアプリをデプロイしてHPAリソースの作成
  • 内部でCPU負荷のかかるコマンドを実行してPodがスケールされることを確認する
workshop:~/environment $ kubectl get hpa -w
NAME         REFERENCE               TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache   0%/50%    1         10        1          75s
php-apache   Deployment/php-apache   466%/50%   1     10    1     106s
php-apache   Deployment/php-apache   466%/50%   1     10    4     2m1s
php-apache   Deployment/php-apache   466%/50%   1     10    8     2m16s
php-apache   Deployment/php-apache   466%/50%   1     10    10    2m31s
  • CAを行う為にASGの設定を変更する
  • yamlを修正する
  • Policyを作成してロールに付与されていることを確認する
  • CAをデプロイする
  • nginxをデプロイしてスケールアウト(replica:10)すると、Node自体もスケールアウトされるのを確認する
I1001 01:10:17.019865       1 clusterstate.go:193] Scale up in group eksctl-eksworkshop-eksctl-nodegroup-ng-2bb840cd-NodeGroup-XXXXXXXX finished successfully in 1m45.091674072s
  • デプロイしたものを掃除する

Step15

EKSでスポットインスタンスを使用する

  • ワーカーノードのインスタンスプロファイルARNとセキュリティグループ名を取得する
  • LaunchボタンからCfnを起動する
  • 各種項目を設定する(Kubernetesのバージョンに注意する, 最新は1.14だった)
  • 無事にうまくいけばSpotInstanceが追加される(元々がv1.13.8で新しく追加されたのはv1.13.11)
workshop:~/environment $ kubectl get nodes
NAME                                                STATUS   ROLES    AGE    VERSION
ip-192-168-59-161.ap-northeast-1.compute.internal   Ready    <none>   13m    v1.13.8-eks-cd3eb0
ip-192-168-66-37.ap-northeast-1.compute.internal    Ready    <none>   120m   v1.13.8-eks-cd3eb0
ip-192-168-7-96.ap-northeast-1.compute.internal     Ready    <none>   120m   v1.13.8-eks-cd3eb0
workshop:~/environment $ kubectl get nodes
NAME                                                 STATUS   ROLES    AGE    VERSION
ip-192-168-143-88.ap-northeast-1.compute.internal    Ready    <none>   53s    v1.13.11-eks-5876d6
ip-192-168-175-210.ap-northeast-1.compute.internal   Ready    <none>   45s    v1.13.11-eks-5876d6
ip-192-168-59-161.ap-northeast-1.compute.internal    Ready    <none>   15m    v1.13.8-eks-cd3eb0
ip-192-168-66-37.ap-northeast-1.compute.internal     Ready    <none>   121m   v1.13.8-eks-cd3eb0
ip-192-168-7-96.ap-northeast-1.compute.internal      Ready    <none>   121m   v1.13.8-eks-cd3eb0
ip-192-168-98-112.ap-northeast-1.compute.internal    Ready    <none>   58s    v1.13.11-eks-5876d6
SpotInstances

workshop:~/environment $ kubectl get nodes --show-labels --selector=lifecycle=Ec2Spot
NAME                                                STATUS   ROLES    AGE     VERSION               LABELS
ip-192-168-143-88.ap-northeast-1.compute.internal   Ready    <none>   2m20s   v1.13.11-eks-5876d6   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=m4.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=ap-northeast-1,failure-domain.beta.kubernetes.io/zone=ap-northeast-1c,kubernetes.io/hostname=ip-192-168-143-88.ap-northeast-1.compute.internal,lifecycle=Ec2Spot
ip-192-168-98-112.ap-northeast-1.compute.internal   Ready    <none>   2m25s   v1.13.11-eks-5876d6   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=c4.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=ap-northeast-1,failure-domain.beta.kubernetes.io/zone=ap-northeast-1a,kubernetes.io/hostname=ip-192-168-98-112.ap-northeast-1.compute.internal,lifecycle=Ec2Spot
OndemandInstances

workshop:~/environment $ kubectl get nodes --show-labels --selector=lifecycle=OnDemand
NAME                                                 STATUS   ROLES    AGE     VERSION               LABELS
ip-192-168-175-210.ap-northeast-1.compute.internal   Ready    <none>   2m49s   v1.13.11-eks-5876d6   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=m4.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=ap-northeast-1,failure-domain.beta.kubernetes.io/zone=ap-northeast-1d,kubernetes.io/hostname=ip-192-168-175-210.ap-northeast-1.compute.internal,lifecycle=OnDemand
  • スポットのinterrupt handlerをデプロイする(Spot使う上で一番気にしないといけない, 回収される際に正常に終了させる)
  • daemon setとして動かす(spotのnode毎に作成)
workshop:~/environment/spot $ kubectl get daemonsets

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR       AGE
spot-interrupt-handler   2         2         2       2            2           lifecycle=Ec2Spot   2m11s
  • frontendのyamlのaffinityにEC2Spotを指定して再デプロイする
  • 無事SpotのNode上にfrontendがデプロイされたことを確認する
workshop:~/environment/ecsdemo-nodejs (master) $  for n in $(kubectl get nodes -l lifecycle=Ec2Spot --no-headers | cut -d " " -f1); do kubectl get pods --all-namespaces  --no-headers --field-selector spec.nodeName=${n} ; done
default       ecsdemo-frontend-766987586b-xgfkd   1/1   Running   0     4m5s
default       spot-interrupt-handler-kvfz8        1/1   Running   0     8m45s
kube-system   aws-node-4wwfj                      1/1   Running   0     18m
kube-system   kube-proxy-knmjp                    1/1   Running   0     18m
default       spot-interrupt-handler-cbwxf   1/1   Running   0     8m46s
kube-system   aws-node-jng42                 1/1   Running   0     18m
kube-system   kube-proxy-r5zl8               1/1   Running   0     18m
  • デプロイしたものを掃除する