Closed76

EKS Workshop で EKS と k8s を学ぶ

kazokmrkazokmr

まずは Introduction から

Setup方法が2種類あって AWS event の方が こういった workshop 用の学習環境を提供してくれるようなので手っ取り早く準備するならオススメっぽい。
が、今回はより実践的に行いたいので自分の個人用の AWSアカウントを使うことにする。もちろんサービス利用料が掛かってしまうのでできるだけ1日学んだら環境は削除することにする

https://www.eksworkshop.com/docs/introduction/setup/your-account/

kazokmrkazokmr

勉強用環境は us-west-2 リージョンを使う。 ap-northeast-1 では動作確認が取れていないらしいので。

kazokmrkazokmr

cloudshellを使って cloudformationのマニフェストファイルをダウンロードし、AWS の Web IDE C
loud9 を起動する。
このCloud9には workshopで起動する サンプルアプリケーションのマニフェストファイルが含まれている

kazokmrkazokmr

以降は このCloud9を使って作業を行う。Cloud9のTerminalからも必要なコマンド実行が行える

kazokmrkazokmr

ちなみにこのCloud9のIDEを起動するための EC2インスタンスが作成されているので、CloudFormationのStackから最後に削除を行うのを忘れないこと

aws cloudformation delete-stack --stack-name eks-workshop-ide

これは cloudshell から行ったほうが良さそう

kazokmrkazokmr

eksctl を使って EKS Cluster を作成する

cloud9のterminal を利用して、EKSクラスタを作成する。 マニフェストはこちらのページにあるものをgithubからダウンロードして、eksctl で クラスタを作成する
https://www.eksworkshop.com/docs/introduction/setup/your-account/using-eksctl

作成完了まで20分位かかる

kazokmrkazokmr

マニフェストを見ていると Node の インスタンスタイプは EC2になりそう? manageNodeGroup を指定した場合はそうなりそう。
また設定内容を見ると、ノードインスタンスは3つ作られて最大で6つなる模様。 クラスタが作られたら確認する

kazokmrkazokmr

30分以上かかったせいか、タイムアウトエラーが発生した。

ただ クラスターは作られていてドキュメントに書かれている以下は作成・設定されていた

  • Create a VPC across three availability zones
  • Create an EKS cluster
  • Create an IAM OIDC provider
  • Add a managed node group named default
  • Configure the VPC CNI to use prefix delegation

気になるのはNodeGroup と Nodeが一つも作られていない点。一旦削除してもう一度作成してみる

https://www.eksworkshop.com/docs/introduction/setup/your-account/using-eksctl#cleaning-up-steps-once-you-are-done-with-the-workshop

kazokmrkazokmr

再度実行したけどエラーになった。 CloudFormationを見ると ManagedNodeGroupの作成時にこんなエラーが出ている

Resource handler returned message: "[Issue(Code=NodeCreationFailure, Message=Instances failed to join the kubernetes cluster, ResourceIds=[i-0485edbb6659be70d, i-064255470e0c74cdb, i-0f1c9a7b0e7fbe49b])] (Service: null, Status Code: 0, Request ID: null)" (RequestToken: fee2af60-033b-58e4-04ac-547ec84e63f3, HandlerErrorCode: GeneralServiceException)

kazokmrkazokmr

途中の経過などを見ているとNode用の EC2インスタンスなども3台起動していたりしているんだけど、NodeGroupからEC2インスタンスへのアクセスができなかった??


AssumeRole 関連でこんなイベントログが頻発していた。確かに us-west-2 なんて普段使わないから STSのactivateはしていない

STS is not activated in this region for account:************. Your account administrator can activate STS in this region using the IAM Console.

というわけで、 IAM -> Account settings で、 US West(Oregon) の STS status を Active にして再チャレンジ

kazokmrkazokmr

NodeGroup はできているけど、Nodeは設定されていない。
けど Node用と思われる EC2インスタンスは3台起動している

kazokmrkazokmr

見れた。

roleを作っていなかったので自分のIAMユーザーに対して権限を付与した。


  1. eks に対するアクセスを許可するpolicy については、今回はフルアクセスを持っていたので省略
  2. eks-console-full-access.yaml を apply したので、 eks-console-dashboard-full-access-group が group指定になる
  3. マッピングは Userを指定した。 (本来の運用は user指定ではなく roleとするべき)
kazokmrkazokmr

Getting started

まずは Workshopで使うサンプルアプリケーションとk8sのアーキテクチャの説明。

アプリケーションはよくあるECサイトって感じで、フロントエンドがあってその後ろに 「商品カタログ(Catalog)」「カート(Cart)」「支払い(Checkout)」「注文(Orders)」といったAPIサービス と「静的ファイルの管理(Assets)」を配信する5つのサービスで提供される。

UIと各バックエンドサービスのアプリケーションはPodリソースで提供され、DeploymentリソースによってReplicasetリソースを介して冗長化してデプロイする。冗長化されたPodはServiceリソース経由でロードバランシングする。

静的ファイル以外のバックエンドサービスは、それぞれサービス専用のDB (MySQL, Redis, DynamoDB)を持っていて今回はこれらのDBもk8sのPodで構築されStatefullリソースによって永続化する

UIと各バックエンドサービスはそれぞれ専用のNamespaceを割り当てる

詳細は割愛するのでワークショップのページを参照すること

https://www.eksworkshop.com/docs/introduction/getting-started/

kazokmrkazokmr

サンプルアプリケーションは、どうも go, java17, nodejs で提供されているみたいだけど、今回のWorkshopでは deployment.yaml を読む限り java アプリケーションのように見える。
しかも healthcheck を見ていると Spring-bootじゃないかな。actuatorってのも見えた

kazokmrkazokmr

アプリをデプロイする

サンプルアプリケーションは、Kustomize で マニフェストファイル群を管理し変更を適用する。

https://www.eksworkshop.com/docs/introduction/getting-started/first

まずは Namespace の確認。これらは全て プリインストールされているシステムコンポーネント用だそう

devopskokmr:~/environment $ kubectl get namespace
NAME              STATUS   AGE
default           Active   45m
kube-node-lease   Active   45m
kube-public       Active   45m
kube-system       Active   45m

catalogサービスのマニフェストファイルの確認

evopskokmr:~/environment $ ls -al ~/environment/eks-workshop/base-application/catalog
total 36
drwxrwxr-x  2 ec2-user ec2-user  222 Jul  7 04:59 .
drwxrwxr-x 10 ec2-user ec2-user  143 Jul  7 04:59 ..
-rw-rw-r--  1 ec2-user ec2-user  155 Jul  7 04:59 configMap.yaml
-rw-rw-r--  1 ec2-user ec2-user 2263 Jul  7 04:59 deployment.yaml
-rw-rw-r--  1 ec2-user ec2-user  260 Jul  7 04:59 kustomization.yaml
-rw-rw-r--  1 ec2-user ec2-user  114 Jul  7 04:59 namespace.yaml
-rw-rw-r--  1 ec2-user ec2-user  133 Jul  7 04:59 secrets.yaml
-rw-rw-r--  1 ec2-user ec2-user   62 Jul  7 04:59 serviceAccount.yaml
-rw-rw-r--  1 ec2-user ec2-user  357 Jul  7 04:59 service-mysql.yaml
-rw-rw-r--  1 ec2-user ec2-user  349 Jul  7 04:59 service.yaml
-rw-rw-r--  1 ec2-user ec2-user 1479 Jul  7 04:59 statefulset-mysql.yaml

というわけで Catalogサービスをデプロイする。実行した後が早い

devopskokmr:~/environment $ kubectl apply -k ./eks-workshop/base-application/catalog/
namespace/catalog created
serviceaccount/catalog created
configmap/catalog created
secret/catalog-db created
service/catalog created
service/catalog-mysql created
deployment.apps/catalog created
statefulset.apps/catalog-mysql created

このあとは、このページに従って kubectl で色々実行してみた

https://www.eksworkshop.com/docs/introduction/getting-started/first

kazokmrkazokmr

Kustomizeについても学んでおく(使っているので)

https://www.eksworkshop.com/docs/introduction/kustomize/

kubectl apply -k 'directoy' ってやると、directory 配下にある kustomization.yaml の内容に従って実行してくれるみたい。

また入れ子的にチェーン実行してくれるみたいだな。 サンプルアプリケーションだと

  1. kubectl apply -k ./eks-workshop/base-application を実行する
  2. base-application 配下の kustomization.yaml には、resourceディレクティブで アプリケーションのルートフォルダをリストで指定している
  3. アプリケーションのルートフォルダにも kustomization.yaml が置いてあり、その resource ディレクティブでは アプリケーション内の リソースのマニフェストファイルがリスト化されているので、そのマニフェストに基づいてリソースが作られる。

これがアプリを立ち上げた時に起こったこと。そのほか patchで マニフェストの内容を上書きすることもできる。

  1. kubectl apply -k ./eks-workshop/modues/introduction/kustomize を実行する
  2. このディレクトリ配下の kustomization.yaml では、resouces に 上書き対象の マニフェストへの相対パスが指定され、さらに patches で、 上書きする マニフェストへの相対パスが指定されている
  3. patchで指定された deployment.yaml では 上書き対象部分のみが宣言されている。今回の場合だと replicas の 数値を変えている
kazokmrkazokmr

ところで、resources の listは リソースの依存関係を考慮した順番にするべきなんだろうか?
サンプルアプリの kustomaization.yaml はそんな感じの並びに見えるんだけど、調べても 実行順に関する説明が見当たらない

kazokmrkazokmr

Helmについても学ぶ (使っているので)

https://www.eksworkshop.com/docs/introduction/helm/

ざっくりだけどこんな感じ

  1. k8s のパッケージマネージャで、様々なソフトウェアをリソースで起動するためのマニフェスト?をダウンロードして実行することができる。 このマニフェストは Helm chart と呼ばれる
    2. 例えば、postgresql とか Nginx を podとして利用するようなリソースなら 標準的な設定を記述した Helm chart が公開されているので それをダウンロードして 実行することで リソースが作成できる
  2. Helm chart をベースにしつつ、一部の設定を変更して利用したい場合は カスタマイズ用の values.yaml を作成して適用することができる
kazokmrkazokmr

Intoroductionは終わった。
概要は理解できた。 用意されている リソースのマニフェストの説明がほとんどないので、これは恐らく以降のチャプターで説明があると期待する

kazokmrkazokmr

Fundamentals

https://www.eksworkshop.com/docs/fundamentals/

Introductionで作成したアプリを公開するためにより実用的な内容になりそう

  • インターネットに公開するための設定
  • Workerノードの設定
  • Fargateを使ったdeploy
  • EBSやEFSなどのを使ったステートフルアプリの構築
kazokmrkazokmr

アプリケーションを公開する

UIサービスを公開するための設定を行う
EKSではこのような場合に、 AWS Load Balancer Controller というコントローラーを利用して、k8sクラスタ用に AWS ELB を管理しやすくする。
AWS Load Balancer Controller は以前は AWS ALB Ingress Controller と呼ばれていたらしい

ロードバランサコントローラは、「k8sのIngressリソースをALB」として割り当てたり「ServiceリソースをNLB」として割り当てる

ちなみに Ingressは L7のアプリケーション層におけるロードバランシング、Serviceは L4 の トランスポート層におけるロードバランシングを行う

L7は例えば異なる複数のアプリやサービスに対して リクエストパスなどに応じて振り分けを行い、L4はあるサービスに対する冗長化構成に対してリソースを振り分ける負荷分散を行う。

kazokmrkazokmr

「ServiceリソースをNLB」として割り当てる

正確には type: loadBalancer の Service を宣言した場合に、AWSのNLBを割り当てる。 type: loadBalancer は クラスタ外からのトラフィックを受ける場合に利用されるリソース。

Introductionで使った、type: ClusterIP は クラスタ内のトラフィックを受ける場合の標準的なリソース

kazokmrkazokmr

まずは AWS Load Balancer Controller をインストールする。これは Helm chat を利用する

バージョンは現時点での最新 2.8.1 を使う
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.8/

と思ったんだけど実行すると 2.8.1は存在しない。って出力されるので --version を指定せずにインストールした

devopskokmr:~/environment $ helm upgrade --install aws-load-balancer-controller eks-charts/aws-load-balancer-controller --namespace "kube-system"   --set "clusterName=${EKS_CLUSTER_NAME}"   --set "serviceAccount.name=aws-load-balancer-controller-sa"   --set "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"="$LBC_ROLE_ARN"   --wait                                                                                  
Release "aws-load-balancer-controller" does not exist. Installing it now.
NAME: aws-load-balancer-controller
LAST DEPLOYED: Sun Jul  7 07:42:14 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!

でここまでの serviceリソースは ClusterIP を使っていたので、これを Loadbalancer Controller を利用して AWS NLB (type: loadBalancer) に変更する
まず既存のServiceリソースに追加する形で、nlb用のリソースを用意する。(元のServiceのtypeを上がくのではなく新たにリソースを作る)これはkustomizationがすでに用意されているのでこれをapplyする

ポイントかどうかわからないけど、元の ClusterIP の場合 targetPort(podにアクセスするポート)は、 deployment.yaml で宣言している port ディレクティブで name: http, containerPort: 8080, protocaol: TCP で定義していて、Serviceの方では この http を指定しているが、 変更後は Serviceのマニフェストでも 8080 を直接している。この違いはなんだろう?

とにかくリソースを作り、 kubectl get svc -n ui を実行すると service リソースが2つ作られるが、今作った ui-nlb の方は external-ip としてDNSが用意されている。
つまりこのALBには外部からアクセスするためのpublicIP が提供されていることになる

また AWS コンソールで ALBを見るとNLBが作成されていることと、targetGroupとしてクラスタノードである3つのEC2インスタンスが指定されている

これで今 UI には ClustIP と Loadbalancer の 2つのServiceリソースにつながっており、生成されたIPでブラウザからアプリケーションを表示することができる

kazokmrkazokmr

次に Ingress リソースを作る
これもあらかじめ用意されている kustomizeで新規リソースとして生成する

しばらくすると Loadbalancer と同じく ブラウザからアプリケーションを表示できる様になる。

ちなみに ingressリソースのマニフェストはこんな感じ
healthcheck-path として /actuator/health/liveness が指定されているのでやっぱりSpring Bootアプリかな

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ui
  namespace: ui
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/healthcheck-path: /actuator/health/liveness
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: ui
                port:
                  number: 80
kazokmrkazokmr

リクエストパスによって、振り分けルールを複数用意する場合に、ingressリソースのマニフェストをサービスごとに用意して ALBとしては統合することができる。

その場合それぞれの ラベル group.name で同じグループを指定すればよく、workshopだと catalog APIへのアクセス用のリクエストパスも用意している

ちなみに AWS が出力する IP は apply する度に変わるのね

kazokmrkazokmr

ちなみに AWS が出力する IP は apply する度に変わるのね

実際のアプリケーションなら ドメインを取得して、ALBもしくはCloudFrontにプロビジョニングして使うと思うので、その場合の使い方がわからないな

アノテーションの alb.ingress.kubernetes.io/schemainternet-facing から internal にすると 内部だけに制限されるっぽい。
ただこの後、alb に dns をプロビジョニングしたり、cloudfrontからこのalbに接続させる必要がありそう

targetGroupBinding というリソースを使う方法もある? この場合は ingressリソースを作ってpodに接続するのではなく、別途ALBを用意してtargetGroup と このリソースを紐づけるのかな?
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.7/guide/targetgroupbinding/targetgroupbinding/

kazokmrkazokmr

k8sの外部公開に関しては色々な方法があるんだと理解した。以下自分が認識したServiceとIngress と 各Typeの概念(上手く言語化できていないので間違っているかも

  • Service : ReplicaSetで冗長化したPodのIP/Portに対してロードバランシングを行う(L4)
    • ClusterIP: k8sクラスタ内に公開される仮装IP
      • ExternalIP: クラスタ外からアクセスするためにノードに対する外部 IP/Portを公開する
    • NodePort: k8s の 全ノードに対して外部 IP/Portを公開する
    • LoadBalancer: k8sクラスタ外のLoadBalancerを利用してクラスタのPodにアクセスする
      • 今回のworkshopで言うと、AWS の Load Balancer controller を 利用して AWS NLB を作成し、指定したクラスタ内の Service (type: ClusterIP) への ルーティング を行っている
      • インターネットに公開されているのは この AWS NLB の
  • Ingress: クライアントからのリクエストホストやパス、クライアントIPを元にロードバランシングを行う(L7
    • AWS や GCP の ALB サービス のような クラスタ外のロードバランサを利用する
      • 今回の workshop だと AWS Load Balancer controller を 利用して AWS ALB を作成する。Ingress Resource (マニフェスト) を元に ALB の ルールとターゲットが生成される。
      • ターゲットには クラスタ内の Service (type: ClusterIP) が指定される
      • インターネットに公開されているのは この AWS ALB の IP だと考えている
    • クラスタ内に ingress を設置することもできる
      • 代表的なのは Nginx-ingress や Traefik (トラフィック) などがある
      • クラスタ内に用意するので、クラスタ外からアクセスするために この ingress に対する Serviceは必要になる
        • 要は Ingressなんだけど クラスタ内に podを Deployment(ReplicaSet)で作成し、このReplicaSetを外部公開するためのServiceも必要になるということ。これに AWS NLB が必要になったりする
kazokmrkazokmr

上記は、Service(LoadBalancer) と Ingress のどちらにおいても リソース(マニフェスト)で、AWS の ELB (NLB/ALB) を管理することになる。

課題として実際のインフラ設定を考えると、この ELB の IPに対して DNSでドメインを割り当てる。あるいは前段に CloudFrontを用意する場合は Distribution の一つとして ELBリソースを指定するのだが、AWS ではなく k8sリソースで制御すると リソースが変わって更新する度に DNS や CloudFron の Distributionの再設定が必要になる。

このため、AWS ELB は k8s の 管理外にし、ELB の Target Group と に対して k8s リソースを紐づける Target Group Binding というカスタムリースを使うと、k8s側のService リソースが変わっても Target Group と Service を紐づけてくれる模様。

kazokmrkazokmr

Storage

とりあえず、EBS と EFS の接続を試す。
EBSは EC2 のみ利用可能で、EFSはEC2とFargateも利用可能

ここでは特に Pod の Volume について Persistence Volume (PV) と呼ばれる 外部のStorageリソースを使う方法を説明している。
PodのVolumeは通常だと ephemeral volume のように Podに紐づけられた一時的なVolume領域が割り当てられるので Podが削除(rolloutも含む)されると削除されたり、複数Podから共有したりすることができない。
これを PV として AWS の EBSやEFSを利用してデータの永続化や共有化を実現する

EBS

EBSは k8s の StatefulSets として定義する。 StatefulSets は データの永続化の仕組みを有するリソースらしい
Workshopのアプリケーションでは Catalogサービスの MySQL用のストレージとして Stateful を利用している

何だけど今現在 このVolumeには EmptyDir という Podのライフタイムが共有されるものが指定されている。なので、Podが削除されるとこの emptyDirも削除されてしまうため RollUpなのでPodが更新される場合にVolumeも削除されるのでデータベースの永続化ボリュームとしては適さない
emptyDir は ephemeral volumes (一時的なVolume) の一種だそう

このため Dynamic Volume Provisioning を利用した 永続化Volumeに更新する。 これを実現するために Kubenetes Container Storage Interface (CSI) を使う

まず、EBS CSI Driver を EKSクラスタにインストールする
セットアップが行われると daemonset リソースとして クラス上のノードに1つずつCSIドライバーが配置される
また、EBSを用いた StorageClass オブジェクトも配置される

既存の StatefulSet で 作成された MySQL の VolumeをこのEBSに付け替える

元の StatefulSet リソースでは 以下のようにVolume指定していた

    volumes:
        - name: data
          emptyDir: {}

これを次のように変更する

 volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: gp2
        resources:
          requests:
            storage: 30Gi

これによりMySQLの永続化VolumeとしてEBSが利用され、今度はPodを削除して新しいPodにRolloutしてもVolumeのデータが残ることが確認できた

kazokmrkazokmr

ここの Persistent Volume について勘違いしてて 永続化ボリュームはPod と 1 対 1 に紐づいている模様(永続化はしているのでPodが削除されても残る) また、accessModes が ReadWriteOnce になっているので この Podからしか書き込みも読み込みもできないらしい。
※厳密にもノードに対して 1対1なので 同一ノードにPodが複数あればそれらのPodからはアクセスできる。

となると気になるのは Database のようなStorageとして使う場合に ReplicaSetで複数ノードにPodを配置してしまうとPodごとにデータが変わってしまうのではないだろうか?
accessMode は 他には ReadOnlyMany(複数Pod/nodeからの読み込みは可能)ReadWriteMany(複数Pod/nodeからの書き込み・読み込みが可能) があるが、ReadWriteMany は EKSではサポートしていないという記述もあり。。。

まぁ実際にはAWSを使っている場合は DatabaseもRDSを使うから別にいいけど、Podに紐づく Persistence Volume であることは意識しておきた

kazokmrkazokmr

あと、ここではDynamic Provision を用いて 自動的に EBS Volumeを作成しつつ Podに割り当てている。
StatefulSet の場合は Podごとに Volumeを割り当てるので、Dynamic Provision を用いると Podを生成するタイミングで PersistenceVolumeClaimを発行するタイミングで該当するPVを作成してくれるらしい

kazokmrkazokmr

EFS

今度は Amazon Elastic File System を使ったワークショップ

こちらは サンプルアプリの静的資材(assets)の保管場所として利用する。
まずは EBSと同じく元のVolumeは empltyDir を使っている。
注意するべきはEBSとは異なり Resourceは StatefulSetではなくてDeployment(でPodのレプリカも管理)している点であること。要するにPodのVolumeとして ephemeral(empltyDir) か persistence かという視点で学んでいっている

今回はPodの削除ではなくスケールした場合の挙動を確認しており、最初はAssets podのreplica数が1で Volumeの中にはいくつかの .jpgファイルが格納されている
この状態で、assets pod の replica数を2つに増やす

ここで起こることは、emptyDir が 特定のPodと1:1で結びついているので、あるPodに対して 新しい静的資材を格納しても 別のレプリカPodのemptyDirには登録されないためデータの不整合が発生することである
これを EFSを使って レプリカPodで共通のVolumeを参照させる

考え方は EBS と同じで、 今度は EFS の CSI ドライバーをクラスにインストールする
以降もEBSと同じで、Storage Object インスタンスとしてAWS EFS Volumeが作成され、各PodからはDaemonsetでインストールされたCSIドライバを介してEFSにアクセスできるようになる。これによってレプリカセット内で静的ファイルが共有される様になる

kazokmrkazokmr

考え方は同じじゃなかった。 EBS (StatefulSet) の場合は StatefulSet リソースとして Volume を volumeClaimTemplates に置き換えることで、Persistence Volume としてEBS を用意して、PersistenceVolumeClaim によって StatefulSet の VolumeをPVに置き換える形で割り当てていた。
さらにDynamicProvisionによってStatefulSetのPodに対するPVを自動作成する様にしていた

対して EFSを使った場合は、上記と異なる

  • PVは全Podで共有のものを使うので DynamicProvisionによってPodごとにPVを自動作成しない様にする
  • このため予め StorageClass を 事前に定義し 単一のEFS Volumeを宣言しておく
  • PersistenceVolumeClaimリソースも事前に用意し、ここで宣言したStorageClassを紐づけておく
  • Deploymentでは PodのVolum Specとして 前述のPersistenceVolumeClaimを指定しておく

こうすることで、DynamicProvisionを使ってPodにPVを割り当ててもマニフェストで指定した共通のPV(EFS)が割り当てられる様になる

kazokmrkazokmr

ちなみにこれは EBS と EFS の違いというよりは、 StatefulSet と Pod の違いという意味でもある。
StatefulSet は 名前の通り 状態をもつPodのため、元々データの永続化を意識しているので VolumeClaimTemplate というSpec Template項目が用意されている。

対して Podにはデータ永続化に対するSpec項目が無いので、Volume Specとして PersistenceVolumeClaim を定義している

なおAWSにおけるEBSとEFSの使い分けとして、EBSはVolume Blockとして EC2インスタンスにアタッチする形を取るので、StatefulSetの永続化ボリュームとして相性が良い。(だから Fargate では利用できない?)
EFSはサーバーレスのNFSで Node とアタッチするという考え方ではなくネットワークを通してアクセスする共有ストレージとして利用ができる (だからFargateでも利用ができる?)

kazokmrkazokmr

Fargate

これまでは Podの起動環境に EC2インスタンスを使っていたが、これを AWS Fargateに変更してみる。
EC2だとインスタンスタイプを指定したり、Multi AZ環境にWorkerNodeを配置してAutoScalingを設定するなどの設定・管理が必要だったがFargateだと全ていい感じに対応してくれる

まず、EKS Cluster で Fargateを利用できる様にするため、ClusterConfigに fargate profile を定義する
今回の設定では checkout namespace に対して fargate を有効にしているので、checkoutサービスのpodが対象となる。が、labelに fargate="yes" が付いている場合とする
また fargateを配置する VPCのサブネットも指定する。Podの実行ロールには ECRからのイメージPullとCloudWatchへのログの書き込みなどを許可するポリシーが含まれている

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: $EKS_CLUSTER_NAME
  region: $AWS_REGION

fargateProfiles:
  - name: checkout-profile
    selectors:
      - namespace: checkout
        labels:
          fargate: "yes"
    subnets:
      - $PRIVATE_SUBNET_1
      - $PRIVATE_SUBNET_2
      - $PRIVATE_SUBNET_3
    podExecutionRoleARN: $FARGATE_IAM_PROFILE_ARN

これで Fargateの利用が有効になったが、checkoutのpodは まだFargateで起動していない。これは manifest に ラベル fargate=yes が付いていないからのため マニフェストを更新して fagateで起動させる

checkout の manifest を apply -> rollout すると PodがFargateで起動する。 起動後に podの describe で Event をみると EC2インスタンスから Fargate に切り替わっているのがわかる。また Podが起動されている Nodeも fargateになっている

Events:
  Type     Reason           Age   From               Message
  ----     ------           ----  ----               -------
  Warning  LoggingDisabled  99s   fargate-scheduler  Disabled logging because aws-logging configmap was not found. configmap "aws-logging" not found
  Normal   Scheduled        57s   fargate-scheduler  Successfully assigned checkout/checkout-5f7978cb5c-j4t2s to fargate-ip-10-42-178-197.us-west-2.compute.internal
  Normal   Pulling          56s   kubelet            Pulling image "public.ecr.aws/aws-containers/retail-store-sample-checkout:0.4.0"
  Normal   Pulled           29s   kubelet            Successfully pulled image "public.ecr.aws/aws-containers/retail-store-sample-checkout:0.4.0" in 27.38s (27.38s including waiting)
  Normal   Created          29s   kubelet            Created container checkout
  Normal   Started          28s   kubelet            Started container checkout
kazokmrkazokmr

次の様に アノテーションを検出することで podの リソースが確認できる

kubectl get pod -n checkout -l app.kubernetes.io/component=service -o json | jq -r '.items[0].metadata.annotations'

{
  "CapacityProvisioned": "0.25vCPU 1GB",
  "Logging": "LoggingDisabled: LOGGING_CONFIGMAP_NOT_FOUND",
  "prometheus.io/path": "/metrics",
  "prometheus.io/port": "8080",
  "prometheus.io/scrape": "true"
}

これらのリソースはマニフェストで変更することができる

もちろん Replicaの数も変更できる

kazokmrkazokmr

Fundamentals の 章は 飛ばした内容もあるけど 今の業務では抑えておきたい箇所は学んだので次に行く

kazokmrkazokmr

Observability

EKSと連携して使えるAWS Observability ソリューションの説明

EKSコンソールのResourcesビュー

まずは簡単に AWS マネジメントコンソールで、EKSによってホストしている k8s クラスタ内の リソースを見る所から始める

Workload

クラスタ上で実行するコンテナのための基本構築部分となるリソース群

  • Pods
    • namespace で絞り込むことができる
    • Podの情報を構造的に確認ができる Structured view と JSON形式で確認ができる Raw view の2種類のViewがある
  • ReplicaSets
    • これも Podsと似たような情報が確認できる
  • Deployment
    • pod と replicaSet に対する宣言的な更新を提供するオブジェクト
    • 詳細情報では Rollup戦略 が確認できたり、Annotationを見ると現在のdeployment の revision なども確認できる
  • DaemonSet
    • 全てのノードに一つずつPodのコピーを配置して実行できるReplicaSetの派生オブジェクト
      • 先の章だと EBSやEFEと接続するための OCIドライバが DaemonSetで作られどのPod(ノード)からでもPersistence Volume にアクセスできる様になっていた
    • なのでViewを見ると 基本的に ノード数分の Podが割り当てられている

Cluster

ノードやNamespaceといった クラスタ構造に関するコンポーネントリソースが確認できる

  • Nodes
    • EKSクラスタ上で実行されている仮想的あるいは物理的なマシンのことを指す
    • Workshopのデモアプリでは3つのノードを生成している
    • インスタンスタイプとしてEC2インスタンスのタイプ または Fargate かが確認できる
    • ノードの詳細Viewを開くと OSのArchitectureやImage,Kernelバージョンなども確認できる
    • EC2インスタンスの場合は、CPUやメモリ、搭載可能なPod数の使用量が確認できる
    • ノード上で実行されているPodの一覧も表示されている
  • Namespaces
    • k8s クラスタ内で仮想的に分離された領域
    • ViewではNamespaceの一覧が確認できるが、それぞれのNamespaceの詳細についてはそんなに特別な情報は表示されない(Namespaceに属する他のオブジェクト、リソースがわかるわけではない)

Service and Networking

ここでは Service や Ingress などによって ポッド上で実行しているアプリケーションとして公開する

  • Services and Endpoints
    • 実行しているポッドをアプリケーションとして公開しているサービスの一覧を表示する
    • Serviceの詳細では対象のPodに紐づけている Selector がわかる
    • 公開しているプロトコルやポート番号が確認できる
    • またEndpointでは、そのServiceが公開している エンドポイントのIPやホスト名、ノードの名前なども確認できる
      • ここのIPは Serviceに割り振られた内部IP(private ip) なので このIPでインターネットからアクセスできるとは限らない

Config and Secrets

Workloadを設定するための センシティブデータを扱うSecrets や 機密情報ではないデータを扱う ConfigMap がある

  • ConfigMaps
    • 環境変数やコマンドラインの引数、アプリケーションの設定 といった、ポッド内にデプロイされたアプリケーションから参照する設定情報を管理する
    • ConfigMapはVolume内に設定ファイルとして格納することもできる
    • 詳細ViewではConfigMapに設定されている情報を Key-Value 形式で確認することができる
  • Secrets
    • ConfigMapsと同様、Key-Value形式で設定値を管理するが、こちらはセンシティブな情報を管理する
    • 詳細Viewで見ると ValueはBase64形式でエンコードされた状態で確認することができる
      • ただし DecodeトグルでONにするとデコードされた文字列で確認ができてしまうので要注意

Storage

クラスタ上で管理される Volumeオブジェクト

  • PersistentVolumeClaims
    • Podからアクセスまたはアタッチする PersistentVolume(EFS/EBS) を宣言するリソース
    • 宣言先のPersistence Volumeの情報などが確認できる
  • PersistentVolumes
    • 生成されている PersistentVolumeの情報が確認できる
  • StorageClasses
    • PersistentVolumeの実態となるStorage情報が確認できる
  • VolumeAttachement
    • Node(Pod)からアタッチされている Volumeの情報が確認できる
  • CSIDrivers
    • 公開されているVolumeに接続するためのCSI Driversの情報が確認できる
  • CSI Node
    • CSIプラグインがデプロイされているNode情報が確認できる

Authentication

Service Accountリソースを管理するセクション

  • Service Accounts
    • Pod内で実行するプロセスを識別するための情報を提供する
    • 通常はPodを作成すると Namespaceに割り当てられているデフォルトのService Accountが付与される

Authorization

EKSクラスタ内の参照や実行を行うための IAM Role 等を管理する

  • Roles
    • Roleは基本的にNamespaceに紐づいて作成する
    • roleの詳細Viewでは Name spaceが確認でき Resourceに対して実行できる権限(Verbs)が確認できる
    • NamespaceではなくクラスタレベルのRoleを定義する場合は ClusterRolesを利用する
  • RoleBindings
    • UserやService Account に対して Role の割り当てを確認できる

Policy

k8s オブジェクトをデプロイするときの 制限などの定義が確認できる
このResourceで制限されている値や設定を超えるような リソースは作成が行えない様にできる?

Extenstions

k8s と 連携して拡張できるソフトウェアコンポーネント

  • CustomResourceDefinitions
    • カスタムリソースが確認できる
  • Webhook Configuration
    • API リクエストに対して 受け入れるか拒否するかを定義するもの
kazokmrkazokmr

Logging in EKS

まずは EKS controle plane に関するログをCloudWatch Logsに出力する
controle plane の ログとして出力できる種類には以下がある

  • Kubernetes API server component logs
  • Audit
  • Authenticator
  • Controller manager
  • Sheduler

ワークショップの通り有効化したんだけど、EKSコンソールに Loggingタブが表示されないな
updateタブを見ると、それぞれのLogがONになったことはわかるんだけど...
とりあえず実際はそれぞれのログを出力するかはON/OFF選択できるらしい

cloudwatch を表示して、ワークショップのクラスタのLog groupを選択すると沢山の Logが出力されていた

kazokmrkazokmr

次は Podのロギング。
デフォルトでは Nodeの /var/logs にログが出力されるらしい。これをCloudwatchに出力する方法を学ぶ

今回はFluentBitをDaemonsetで使うことで ノードレベルで podのログを収集する
で AWS 向けの FluentBitイメージがありそれを使うのだが、なんかこの章を始めるときに行った設定で事前に Daemonsetが作成されていた。この辺も実際に動かしたかったなぁ

さらに ConfigMapも追加で作成されており、/var/log/containers/*.log に出力されたログが CloudWatch にも出力される様になっていて logGroup /eks-workshop/worker-fluentbit-logs-vLHNPp から確認できる
この時点ではアプリケーションにアクセスしていないのでログは出力されない。

代わりにアプリへのアクセスではなくて、ui podの再作成によってui アプリのフレームワークであるSpring Bootの起動ログをpodのログとしてCloudwatchに出力する

再起動の方法はマニフェストファイルを更新しないので apply は使わずに、pod を deleteした後に deployment を 再度 rolloutする
このあと、kubectl logs -n ui deployment/ui を実行するとコンソールにログが出力される

Cloudwatchでは ロググループ /aws/eks/fluentbit-cloudwatch/workload/ui にこの時の起動ログが出力される

うーんちょっと消化不良だが次に進む

kazokmrkazokmr

Fargate の場合、FluentBit をベースにしたログルータが利用できるそう
これを有効にするには専用のConfigMapを適用すれば良い。
このConfigMapでは FluentBit の INPUTセクションはデフォルト値が利用され変更不可らしい。つまりFagate内に出力されたログを FILTER と PARSER, OUTPUT を定義するだけで良いみたい

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/fargate-logging.html

kazokmrkazokmr

Observability with OpenSearch

AWS OpenSearch を使った observability を学ぶ (OpenSearchの環境作るのに30分以上掛かった...)

今回は EKS で実行した k8s イベント を event exporter を使って opensearchに出力し ダッシュボードで表示させる。
event exporter は helmを使ってインストールする

その後、あえて warning eventなどが出力される deployment を applyして eventを opensearch の ダッシュボードで観測する。 が、ダッシュボードが表示されない。というか作成されていない???

確認したいが時間がかかりそうなので後回しにする。(むしろ次の AWS Distro for OpenTelemetryの方を確認したいので)

kazokmrkazokmr

EKS open source observability

AWS Distro for OpenTelemetry (ADOT) で メトリクスを収集し、AWSのマネージドサービスとして提供されている Prometheus に保存し 同じく AWS マネージドサービスの Grafana で可視化する

ADOT で EKSクラスタのMetricsをスクレイプする

ここでは事前に以下のリソースが作られている

  • Namespace: other
  • otherに対するServiceAccount
  • other 上に ADOTポッドをデプロイする deployement

まずは、ADOTがEKSクラスタでMetricsを収集するための ロールを ClusterRoleリソースで作成する。ServiceAccountにこのClusterRoleを追加する。
その後、deployment/adot-collector を rollout

AWS Prometheus に Metrics を保存する

これも環境がすでに構築されている。で、remote write URL や query URL も提供されているので、adot collector から の書き込み や 収集したメトリクスの問い合わせは行える様になっているが AWS Prometheusには 専用のDashboardはなさそう。
ということで次のGrafanaで可視化することになる

Grafanaにアクセスする

最初の説明で AWS Grafana を使っているのかと思ったんだけど、今回のワークショップでは これとは別にk8sクラスタ内に Grafanaポッドが作成されていて Ingressでブラウザからアクセスできる様になっていたのでこれを使う模様
アクセスするとDatasourceとして Prometheus にもアクセス済みの状態だった

Dashboardも2つ作成済みで、そのうちの Kubernetes cluster monitoring が Prometheus で収集したMetricsを表示する内容になっていた

もう1つのダッシュボードは Order Service Metrics となっていて、アプリケーションのMetricsを収集してくれる。
このダッシュボードにMetricsを表示させるために、ワークショップに従って 各アプリのPod からMetricsが収集できる様になっている
ここで負荷用のPodを作成して、アプリケーションに定期的にアクセスして商品を購入すると、そのリクエストに基づいてMetricsがダッシュボードに表示される様になる

kazokmrkazokmr

今回は Metrics 部分のみの話になっている。
このあとは Logについては収集する模様

kazokmrkazokmr

ちなみにここでスクレイピングしていたメトリクスは Cluster全体のものになる。次は PodレベルのMetricsも取得する

kazokmrkazokmr

Container Insight on EKS

次は PodレベルのMetrics と Logs を取得する。取得には adot-collector を使うみたいだが格納先は Prometheus ではなく Cloudwatch となるみたい

先ほどと同じようにまずは、adot-collector pod が配置される namespace の ServiceAccount に対して adot-collector が EKSクラスタ上で Metricsが収集できるClousterRoleを用意して割り当てる

CloudWatchには Container Insights という コンテナ向けのモニタリングダッシュボードがあり今回はこれを使って、Performanceモニタリングを行う。
Logの方は Logs Insights から確認が行える

これで 先ほどの Prometheus/Grafana の時と同じように Webアプリに対する負荷環境ポッドを起動して状況を確認する

しばらく経つとダッシュボートなどにMetricsやログが出力される様になる

kazokmrkazokmr

Costに関するワークショップはスキップして、次の章へ

kazokmrkazokmr

Security

ここでは EKS Best Practices Guid を元に EKSに関連するセキュリティについて学ぶ

Cluster Access Management API

Cluster Access Managerment API の使い方を学習し、EKSクラスタへの認証・認可を提供する新しいモデルの提供を理解する

Cluster Access Management API は、AWS IAM と Kubernetes RBAC(Role Based Access Controls) を紐付けやすくするのを実現している
Cluster Access Management API に 二つの基本的な概念がある

  • Access Entries (Authentication): AWS IAM が 指定の EKS Cluster にアクセスできるか
  • Access Policies (Authorization): EKS Cluster に対する操作を制限するためのポリシー

代表的な Access Policy と RBAC のマッピング例

  • AmazonEKSClusterAdminPolicy <-> cluster-admin
  • AmazonEKSAdminPolicy <-> admin
  • AmazonEKSAdminViewPolicy <-> view
  • AmazonEKSEditPolicy <-> edit
  • AmazonEKSViewPolicy <-> view
  • AmazonEMRJobPolicy <-> N/A
kazokmrkazokmr

例えば ReadOnlyなRoleを用意し、access-scope として Cluster全体や 特定のnamespaceだけに絞る。そのRoleを使って delete や 異なるNamespaceのPodを参照しようとするとforbidden とすることができる

また Roleリソースとして定義し bindingすることでも同様の制御ができる

従来だと ConfigMapリソースとして aws-auth を用意することで アクセス制御ができていたがこれは非推奨になっており今後は access-policy を用いて制御すること

kazokmrkazokmr

Service Accounts と IAM Role

Service Account に IAM Role を割り当てることで、EKSのPodから AWSの 各種サービスへのアクセスを許可させることができる?

サンプルアプリの cartsサービスは AWS DynamoDBにアクセスしてCart情報を保存する。このため PodからDynamoDBへのアクセスが必要。

ワークショップでの実験として初期状態では Carts Namespace の ConfigMap で DynamoDBにアクセスするための AccessKeyやエントリポイントが定義されていてそれを元にCartsPodから接続していたがConfigMapの定義からこれらの接続設定を無くしたことでアクセスできない様にした
エラーログを見ると、これまではDynamoDBにアクセスできるRoleにAssumeしていた様だが、AccessKeyが参照できなくなったことでAssume Role が出来なくなった。DynamoDB側ではアクセスを許可されていないRoleで接続しされたので400エラー(AccessDenied)としている

ここから Service Account に IAM Role を付けていくが、そのためには IAM OIDC Identity Provider を作成して クラスタに割り当てる必要がある。
今回はすでにこのProviderは作成済み。

また DynamoDBにアクセスして読み書きするためのPolicy と IAM Role も用意されている。 このIAM Role には 信頼できる関係として OIDC プロバイダが割り当たっており これにより Assume Role が 可能な状態になっている。
そこで cart に割り当てられている Service Account に この IAM Roleをアサインすることで OIDC プロバイダ経由で DynamoDBにアクセスするためのRoleをAssumeする

Service AccountにIAMロールをアサインしたら deployment を リスタートする

結果として AccessKeyの設定を必要とせずにDynamoDBへのアクセスができる様になった

kazokmrkazokmr

Amazon EKS Pod Identity

OIDC provider ではなく EKS Pod Identity という機能を使ってアクセス権を付与するのかな?
さっきと同じく Cart サービスと DynamoDB の接続を利用するので まずはAccessKeyをConfigMapから除去しDynamoDBへ接続できない状態にする

今度は eks-pod-identity-agent を アドオンとして EKS クラスタにインストールする。インストールすると kube-system name spaceに eks-pod-identity-agent が daemonSetリソースとして作成される。

あとは DynamoDBにアクセスするPolicy を持つ IAM Role に 信頼する関係として Pod Identity を用いてこのRoleにAssumeする権限をつける
そしてこのRoleを Service Account に付与する。

Service Accout に IAM Roleを付与するのは変わらず、AssumeRole を付与する実行者(Principal)が OIDC Provider か Pod Identity かの違いの様に見えた

kazokmrkazokmr

Secrets Management

k8s の Secret Resource は センシティブなデータを Key - Value を管理するが 実仕様を考えると以下の様な課題がある

  • Manifest には Key - Value な値を定義する必要がある。Gitで管理するとこのような情報が漏れる
  • EKS コンソール 上では 値は Base64でエンコードされて表示されるが、容易にDecode出来てしまう

そこで、Secretリソースを用いるのではなく AWS の Secret Manager を使って センシティブ情報を EKS クラスタ外で管理させる試み

外部に格納したSecretにアクセスするための Kubernetes Secrets Store CSI Driver があるが、AWSにはこのための `AWS Secrets and Configuration Provider (ASCP) が提供されている。
これをDaemonSet リソースとして作成することで 各ノードからSecret Managerに接続できる様にする
(Secrets Store CSI Driver は 専用のNamespaceで作成され、ASCPは kube-system namespace に作成される)

CSIドライバーが SecretManagerに アクセスするために 専用の SecretProviderClass リソースを作成し、この中で アクセスするSecretオブジェクトを指定する。SecretのKeyは複数指定することができる。
作成した SecretProviderClass と CSI ドライバー を利用するように Deployementを修正することでSecretManagerから値を参照できる様になる

kazokmrkazokmr

AWS Secrets and Configuration Provider (ASCP) とは別に External Secrets Operator (ESO) という仕組みがある

これは SecretManagerにアクセスできる Service Account と そのServiceAccountからアクセスできる ClusterSecretStore リソースを用意する。
この ClusterSecretStoreをDeploymentで指定することで、専用のServiceAccountのRoleでSecretを取得できる様になる
大きな違いはわからないけど、こうすることで 複数のPodから同じSecretを参照したいときに指定しやすいのかも

kazokmrkazokmr

Amazon GuardDuty for EKS

GuardDuty は AWSアカウント、ワークロード、S3内に格納されたデータを保護し、脅威の検出を定期的にモニタリングしてくれる。また、CloudTrail Event, VPC Flow Logs, DNS Logsから ネットワークのアクティビティを分析も行ってくれる

EKSに対してGuardDutyが保護してくれることについて確認をする。
EKSに対しては 監査ログのモニタリングと 実行時のモニタリングすることでEKSクラスタを保護する

GuardDutyはAWS コンソールから有効にする。今回は EKS Protectionを有効にする。30日間はフリートライアルが可能らしい
ワークショップの説明とちょっと変わっていて、EKS Protection では Audit Log のモニタリングのみ有効にでき、Runtimeのモニタリングは Runtime Monitoring から有効にする。ただRuntime MonitoringでEKSを有効にしても CoverageにClusterが出てこないのは謎

k8sクラスタには amazon-guardduty namespace に agent Podが生成されている

この状態で あえて kube-system namespace に生成した Pod でコマンドを実行すると 監査対象のKubeSystemPod内での実行ログが検出されGuardDutyに取り込まれる(反映まで少々時間が掛かる)

他にも DefaultのService Acountへのアクセスや EKS クラスタダッシュボードが公開されていたりするとGuardDutyで検知される

またRuntimeモニタリングだと例えば default Namespaceで crypto コマンドなどを実施すると検知される

細かい設定はできないが セキュリティ強化を考えたときには 設定しておきたいサービスではある

kazokmrkazokmr

Pod Security Standards

Pod Secruity Standard(PSS) は、セキュリティに関する3つの異なるポリシーを定義することで幅広くカバーすることを目的としている

  • Privileged(特権)
  • Baseline(基準)
  • Restricted(制限)

また Pod Security Admission(PSA) によって 3つのモードで それぞれのPSSポリシーを実装する

  • enforce (強制)
  • audit (監査)
  • warn (警告)
kazokmrkazokmr

Networking

EKS では Clusterのネットワークである Podネットワークは Amazon VPC CNI という Kubernetes CNI プラグインが利用されている

Network Policies

Pod間の通信に制限が無いのがデフォルトで、NetworkポリシーによってPod間やNamespace、IPブロック(CIDR)の制御が行える。 Networkポリシーは仮想ファイアウォールとして ingress(incoming) と egress(outgoing) のネットワークトラフィックルールを指定することでClusterをセキュアにする

Network Policies の マニフェスト例
role:db ラベルの Pod に対して、通信を許可する IPブロック、Namespace、Pod を指定し、かつ 有効なポート番号を指定している

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - ipBlock:
            cidr: 172.17.0.0/16
            except:
              - 172.17.1.0/24
        - namespaceSelector:
            matchLabels:
              project: myproject
        - podSelector:
            matchLabels:
              role: frontend
      ports:
        - protocol: TCP
          port: 6379
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/24
      ports:
        - protocol: TCP
          port: 5978
kazokmrkazokmr

ここからサンプルアプリケーションを使ったNetworkポリシーの設定に入る。
サンプルアプリのデフォルでは 各サービスごとにNamespaceで区切ってはいるものの サービス間の通信は特に制限なく行える。 例えば、catalog サービスから checkout サービスへのアクセスができたり。
ただサンプルアプリのアーキテクチャとしては、ユーザーとの窓口になる UIサービスを介してバックエンドの各サービスと通信をするが、バックエンドサービス間は checkout -> Orders 以外は通信が不要
な構成

Egress 制御

まずは Podから外部への通信を制御する。サンプルアプリの UI サービスからの通信を制御してみる
この マニフェストは、適用するPodを制限していないのでUIを含む全てのPodに対して適用される。また Egress の定義だが 許可するものものない。つまりこのマニフェストを UI Namespace に対して apply すると UI Namespace内の 全ての Podに対して 別のPodへの通信ができなくなる

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: default-deny
spec:
  podSelector:
    matchLabels: {}
  policyTypes:
    - Egress

kubectl apply -n ui -f ~/environment/eks-workshop/modules/networking/network-policies/apply-network-policies/default-deny.yaml
これで UI Namespace内の全てのPodからの通信がブロックされた

ここから UI の pod に対して、 ラベルに service を付けた Pod あるいは kube system namespace への通信のみ許可する マニフェストで applyする

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  namespace: ui
  name: allow-ui-egress
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: ui
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
          podSelector:
            matchLabels:
              app.kubernetes.io/component: service
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system

なお、このマニフェストでは metadata.namespace で ui を指定しているので、applyで -n ui を指定しなくても UI Namespace以外には適用されない。さっきの denyの方は マニフェストでは Namespaceが指定されていなかったので、 apply 時に指定していた

これにより、service ラベルがついた、 catalogサービスのPodにはアクセスできる様になった。 ただし catalog の 背後にある DB Pod (MySql) には service ラベルが付与されていないので 直接 アクセスはできないまま。 また 外部インターネットのWebページなどにもアクセスではできない

kazokmrkazokmr

Ingress 制御

次に Ingress の設定を学ぶ
Catalog Namespace において Catalog サービスは UI ネームスペースからの通信のみ許可する。また Catalog データベースは Catalog サービスからの通信しか許可しない

今は、Catalogサービスは UIサービスだけでなく、orders などのui 以外のサービスからの通信も許可された状態

ここで Catalog Namespace に対して、 ui namespace の uiラベルが付与された Podからのアクセスのみを許可する NetworkPolicy をapplyする

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  namespace: catalog
  name: allow-catalog-ingress-webservice
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: catalog
      app.kubernetes.io/component: service
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ui
          podSelector:
            matchLabels:
              app.kubernetes.io/name: ui

これで ui 以外の Namespace からの通信が遮断された
ただこれは、ingress(inbound) の対象が catalog service だけであり、 catalog database はまだ他のnamespaceから通信できる状態なので catalog database に対しても ingress を設定し、catalog service からの通信のみ許可する

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  namespace: catalog
  name: allow-catalog-ingress-db
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: catalog
      app.kubernetes.io/component: mysql
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app.kubernetes.io/name: catalog
              app.kubernetes.io/component: service
kazokmrkazokmr

このあと ネットワークのデバッグ方法についてのセクションがあり、Pod内のログを見る方法が説明されているが見られなかったのでスキップ

ただこの実験からわかることとして、許可するサービスを指定する場合は PodだけでなくそのNamespaceも一緒に指定しないとダメな模様

kazokmrkazokmr

Security Groups for Pods

Security Group を使って クラス上のPodに同じネットワーク設定を適用する?
サンプルアプリにおいて、Catalog Namespace には Catalog Service と Catalog Database となる MySQLの2つが存在している。このCatalog Database を RDS に入れ替えてみる

catalog の ConfigMap では Databaseの接続先に EKSクラスタの catalog database が指定されているので、ConfigMap を更新して RDS に接続させてみる。

apiVersion: v1
data:
  DB_ENDPOINT: ${CATALOG_RDS_ENDPOINT}
  DB_NAME: catalog
  DB_READ_ENDPOINT: ${CATALOG_RDS_ENDPOINT}
kind: ConfigMap
metadata:
  name: catalog
  namespace: catalog

まず ConfigMapリソースを更新した後、catalog service pod が更新後の configMap を参照するために podを再起動(今のPodを削除して rollout を自動実行)する... が、Podの起動が失敗する?

これは想定通りで、kubectl -n catalog logs deployment/catalog でログを確認すると RDSへの接続に失敗している模様。で Security Groupg を確認すると 次のような RDSに割り当てられたSecurityGroupが存在する。これは事前にWorkshopの環境設定で作られたものになる。
このSecurityGroup の `IpPermissions を見ると ある別のセキュリティグループから 3306ポートへのアクセスのみ許可されていることがわかる

{
  "SecurityGroups": [
    {
      "Description": "Catalog RDS security group",
      "GroupName": "eks-workshop-catalog-rds-20240720034911538800000002",
      "IpPermissions": [
        {
          "FromPort": 3306,
          "IpProtocol": "tcp",
          "IpRanges": [],
          "Ipv6Ranges": [],
          "PrefixListIds": [],
          "ToPort": 3306,
          "UserIdGroupPairs": [
            {
              "Description": "MySQL access from specific security group",
              "GroupId": "sg-0d7ce9728aa2b5c5a",
              "UserId": ""
            }
          ]
        }
      ],
      "OwnerId": "",
      "GroupId": "sg-0fe1c618f74bba223",
      "IpPermissionsEgress": [],
      "Tags": [
        {
          "Key": "Name",
          "Value": "eks-workshop-catalog-rds"
        },
        {
          "Key": "env",
          "Value": "eks-workshop"
        },
        {
          "Key": "created-by",
          "Value": "eks-workshop-v2"
        }
      ],
      "VpcId": "vpc-07d459c112e4ab24b"
    }
  ]
}

このもう一つの SecurityGroupは catalog service 向けのものとした作成されている

{
  "SecurityGroups": [
    {
      "Description": "Applied to catalog application pods",
      "GroupName": "eks-workshop-catalog",
      "IpPermissions": [
        {
          "FromPort": 8080,
          "IpProtocol": "tcp",
          "IpRanges": [
            {
              "CidrIp": "10.42.0.0/16",
              "Description": "Allow inbound HTTP API traffic"
            }
          ],
          "Ipv6Ranges": [],
          "PrefixListIds": [],
          "ToPort": 8080,
          "UserIdGroupPairs": []
        }
      ],
      "OwnerId": "",
      "GroupId": "sg-0d7ce9728aa2b5c5a",
      "IpPermissionsEgress": [
        {
          "IpProtocol": "-1",
          "IpRanges": [
            {
              "CidrIp": "0.0.0.0/0",
              "Description": "Allow all egress"
            }
          ],
          "Ipv6Ranges": [],
          "PrefixListIds": [],
          "UserIdGroupPairs": []
        }
      ],
      "Tags": [
        {
          "Key": "created-by",
          "Value": "eks-workshop-v2"
        },
        {
          "Key": "env",
          "Value": "eks-workshop"
        }
      ],
      "VpcId": "vpc-07d459c112e4ab24b"
    }
  ]
}

なので、 catalog service に対して SecurityGroupPolicyリソースを用意し applyする

apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: catalog-rds-access
  namespace: catalog
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/component: service
  securityGroups:
    groupIds:
      - ${CATALOG_SG_ID}

このあと、先ほど同じように catalog Podを削除して 自動Rolloutが実行されると RDSに接続できる様になる。

kazokmrkazokmr

podを再起動(今のPodを削除して rollout を自動実行)する...

Pod を削除すると Rolloutが実行される理由だけど、 Deploymentリソースである deployment/catalog で Replicaに指定したしたPod数を維持しようとするので、Podを削除することで rollout によるPodの再作成が行われると理解している

kazokmrkazokmr

Custom Networking

VPC CNI の デフォルトでは、プライマリサブネットのIPアドレスがPodに割り当てられる。 プライマリサブネットのCIDRが小さいと Podに割り当てるIPアドレスが不足する可能性があり、特にEKS IPv4クラスタで問題になる。よって対応方法を学ぶ

サンプルアプリにおいて、現在のVPC の CIDR ブロックは以下の通り

  • プライマリ: 10.42.0.0/16 (10.42.0.1 ~ 10.42.255.254)
  • セカンダリ: 100.64.0.0/16 (100.64.0.1 ~ 100.64.255.254)
    ※ 0.0 はネットワークアドレス、255.255 はブロードキャスアドレスなので除外

サブネットは次の9つ

  • Public Subnet
    • 10.42.0.0/19 (10.42.0.1 ~ 10.42.31.254)
    • 10.42.32.0/19 (10.42.32.1 ~ 10.42.63.254)
    • 10.42.64.0/19 (10.42.64.1 ~ 10.42.95.254)
  • Private Subnet
    • Primary
      • 10.42.96.0 (10.42.96.1 ~ 10.42.127.254)
      • 10.42.128.0 (10.42.128.1 ~ 10.42.159.254)
      • 10.42.160.0 (10.42.160.1 ~ 10.42. 191.254)
    • Secondary
      • 100.64.0.0/19 (100.64.0.1 ~ 100.64.31.254)
      • 100.64.32.0/19 (100.64.32.1 ~ 100.64.63.254)
      • 100.64.64.0/19 (100.64.64.01 ~ 100.64.95.254)

Custom Network を有効にするため、 DaemonSet リソースに対して 環境変数 AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG を true にする。そして ENIConfig リソースを Podがデプロイされているサブネット様にapplyする

EKS の Node Group を作成する時に このカスタムネットワークを指定できる様になる。 で 例えば checkout deployment において、作成したNodeGroupを指定するように変更して applyすると、checkout namespace 上の pod に カスタムネットワークのIPが割り当てられる

kazokmrkazokmr

ただなんで checkout service と checkout redis がそれぞれ 別の private subnet に割り当てられたのかが不明。

kazokmrkazokmr

Automation

EKS の 自動デプロイの仕組みを学ぶ。今回は GitOps のみ

Flux

Flux システムを使うと GitHub で 管理したEKSの構成をウォッチし、変更がPushされるのをとりがにして自動的に更新してくれる模様

今回のサンプルでは リポジトリは AWS Code Commit を使う

gitリポジトリに対して flux の bootstrap を実行する

flux bootstrap git \
  --url=ssh://${GITOPS_IAM_SSH_KEY_ID}@git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/${EKS_CLUSTER_NAME}-gitops \
  --branch=main \
  --private-key-file=${HOME}/.ssh/gitops_ssh.pem \
  --components-extra=image-reflector-controller,image-automation-controller \
  --network-policy=false \
  --silent

これで今、Gitリポジトリの mainブランチのコミットに対してrevisionがフラれた状態になっていて、EKSクラスターと同期が取られているらしい

まず、UIコンポーネントを Flux でデプロイするため既存のコンポーネントを削除する。この削除はコンポーネント全体なので podだけでなく deployment や service, configmap, namespace など全てが削除され UIがクラスタ上から削除された状態となる

その後、CodeCommitのレポジトリから flux-system の コードをクローンし、元のUIソースを flux-systemにコピーする。
これで UIコンポーネントをflux-system で GitOpsする準備が整う。
この状態で CodeComit に push を行う。
しばらくすると kustomizationリソースに apps が表示される。 この時に flux-system で行われているのが reconcile (すり合わせ) と呼ばれる処理で Gitの内容を反映していると思われる。

apps が表示されると、これまで通り ui component として deployment や pod が作られている

kazokmrkazokmr

今度は buildspec.yml を使って、component の Docker Image を build し ECRにPushさせる。
必要なファイルを更新して 先ほどと同じように CodeCommitへPushすると CodePipeline が実行されてイメージビルドが始まる。 実行開始するまで数分かかる。

で、ECRにイメージができていたので、これを使ってデプロイをしてみる。
ここで deployeするDocker Imageを更新したときに GitOpsが走りpodが更新される様にFluxで 対象のImageReposity, ImagePolicy, ImageUpdateAutomation の各マニフェストを設定しておく

ここでブラウザでサンプルアプリを表示しタイトルを確認する。
このタイトルを変更して再度コミット・Pushを行う

CodePipelineでイメージをビルドしてPushした後、しばらくすると自動的にImageが更新されており、ブラウザをリロードするとタイトルが変わった

kazokmrkazokmr

FluxCD と Flux って同じものを指しているっぽいな
Fluxについては公式サイトも見たほうがより理解ができそう

https://fluxcd.io/

kazokmrkazokmr

Argo CD

Argo CD を 使った GitOps もためす
ArgoCDはHelmを使ってインストールする
インストールが完了すると ArgoCDのコンソールにブラウザからアクセスできる様になる

セットアップとして、ArgoCDが参照するアプリケーションをGitリポジトリで用意して連携する
Fluxの時と同じように ui コンポーネント一式をEKSクラスタから削除し、マニフェストを ArgoCD用のプロジェクトにコピーしPushする
そうすると、ArgoCDによってUIコンポーネントがクラスタ上に作成された。またコンソールからもリソースの状態が見える

deployment マニフェストのReplica数を変更してGitにPushすると Replica数分のPodが作成されることも確認できた

kazokmrkazokmr

なんとなく、ArgoCDの方がFluxより簡単そうに見えなくもないが どうなんだろ??

kazokmrkazokmr

一通り、確認したいことは試すことができた。
全体的にEKSの仕組みをざっくりと理解することはできたが、マニフェストの細かい設定までは見切れていないので実際にもっと触っていじってみないといけないなとは感じた

このスクラップは3ヶ月前にクローズされました