EKSで作成したALBでgRPCのパスベースルーティングする
概要
- IFにgRPCを採用したマイクロサービスをEKS化する手順
- マイクロサービスは複数あり、ALBからパスベースルーティングでリクエスト
- EKSの作成からgRPCクライアントによる疎通まで一気通貫でまとめ
- 内容の勉強も兼ねて
eksctl
は使わずに可能な限りマネジメントコンソール上で作成します
参考にした記事
-
https://www.blogaomu.com/entry/alb-grpc-on-eks
- エッセンスは大体こちらに書いてあります
-
https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html
- AWS公式のEKSクラスターとノードの作成手順
-
https://qiita.com/NaokiIshimura/items/60f90d9d925ca2b103bb
- マネジメントコンソールでEKSクラスターを作成してハマったときに参考にしました
-
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/deploy/installation/
- AWS Load Balancer Controllerインストール手順
-
https://qiita.com/M-Yamashii/items/a72cc3ef8c64cce96327
- externalDNSについて参考にしました
大まかな流れ
- やることが多くて全体感が掴みにくいのでざっくりと、EKS準備、EKS作成、ALB準備、Service準備、Service作成、動作確認で分けて考えます
EKS準備
- EKSを操作する(
kubectl
を実行する)EC2の準備 - EKSを動作させるVPC、サブネット等の準備
EKS作成
- EKSクラスターの作成
- OIDCプロバイダーの作成
- EKSクラスターへアドオンを追加
- ノードグループの作成
ALB準備
- EKSクラスターへAWS Load Balancer Controllerをインストール
- ALB用のドメイン取得
- ALBに登録するTLS証明書の作成
- externalDNS設定
Service準備
- サービスアカウントのIAMロールとポリシーの作成
- DockerレジストリへマイクロサービスのコンテナイメージをPush
Service作成
- EKSにマイクロサービスをデプロイ
- Ingressをデプロイ
- 自動的にALBが生成されます
- Route53にDNS登録もされます
動作確認
- gRPCクライアント(Python)からのポスト
EKS準備
EC2の作成
- マシンパワーは不要なので
t3.micro
で作成 - OSはAmazon Linux2
- 用途はEKSクラスタの作成、
kubectl
による操作、ECRレポジトリへのプッシュ
IAMロールの作成
- 「IAM」→「ロール」→「ロールの作成」
- ユースケースでEC2を選択
- AWS管理のポリシーから
AmazonEC2ContainerRegistryPowerUser
を選択- このEC2でECRレポジトリへのプッシュをしないのであれば不要
- インラインポリシーで以下を追加
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "eks:DescribeCluster", "eks:CreateCluster" ], "Resource": "*" } ] }
必要なアプリのインストール
-
AWS公式によると
This guide requires that you use version 2.3.7 or later or 1.22.8 or later.
とのことなのでAWS CLIを更新curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install aws --version
-
kubectl
インストールcurl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl kubectl version --client --short
-
docker
インストールsudo yum update -y sudo yum install -y docker sudo usermod -aG docker $USER sudo systemctl start docker.service sudo systemctl enable docker docker -v
-
(ここでは使いませんが必要になったときのために念のため)
eksctl
インストールcurl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp sudo mv /tmp/eksctl /usr/local/bin eksctl version
VPC等の準備
- VPC及び複数AZにサブネットを作成します
- EKSクラスターを作成するには2つ以上のAZでサブネットを用意する必要があります
- 注意するのは以下の点
- パブリックサブネットのタグに以下を設定
- Key:
kubernetes.io/role/elb
- Value: 1
- Key:
- プライベートサブネットのタグに以下を設定
- Key:
kubernetes.io/role/internal-elb
- Value: 1
- Key:
- プライベートサブネットにNATゲートウェイへのルートを設定する
- EKSコントロールプレーンはVPCの外にあるので、こうしないとノード作成に失敗します
-
https://dev.classmethod.jp/articles/eks-managed-node-group-public-ip-assignment-behavior-is-changed/
ノードグループをプライベートサブネット上に配置した場合、EKSコントロールプレーンに対する通信は「NATゲートウェイ」経由で行うことになります。
- パブリックサブネットのタグに以下を設定
EKS作成
クラスター作成
クラスター用のロール作成
- マネジメントコンソールで「IAM」→「ロール」→「ロールの作成」
- ユースケースの選択で「EKS - Cluster」を選択
- 自動で信頼関係の作成もAmazonEKSClusterPolicyのアタッチもされる
- ユースケースの選択で「EKS - Cluster」を選択
クラスター作成
-
EC2上で以下のように実行(clusterやroleの名称、subnetIdsは適宜変更)
aws eks create-cluster \ --region ap-northeast-1 \ --name eks-cluster-name \ --kubernetes-version 1.21 \ --role-arn arn:aws:iam::xxxxxxxxxxxx:role/eks-cluster-role \ --resources-vpc-config subnetIds=subnet-05bd5fc3f34a8e495,subnet-0c8c82ba3f745b7f5,subnet-0ce8461ce308040a0,subnet-091ee2c5a14b5713d,subnet-0382411db769d634e,subnet-0d1de987a451d32b8
-
マネジメントコンソールからEKSクラスターを作成すると、そのユーザーが作成者となるため操作用のEC2から手出しできなくなるのでEC2で作成
- 操作用のEC2に同じユーザーを設定できるなら問題ないはずです
- ConfigMapをちゃんとする場合はこちらを参考に
-
kubeconfigを作成し、EC2からEKSをコントロールできるようにします
aws eks update-kubeconfig \ --region ap-northeast-1 \ --name eks-cluster-name
-
状態が取得できることを確認
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 10m
IAM OIDC プロバイダーを作成する
-
この後のCNIプラグインやAWS Load Balancer Controller、externalDNSといったサービスのIAMロールで使うため先に作成します
- OpenIDを使ってIAMロールを引き受けて各種アクセス権を取得する感じ
-
マネジメントコンソールで「EKS」→「クラスター」→「作成したクラスター」を選択し、詳細画面から
OpenID ConnectプロバイダーURL
をコピーします
-
マネジメントコンソールで「IAM」→「IDプロバイダ」→「IDプロバイダを作成」を選択し、OpenID Connectを選び、上記のURLをコピーし、サムプリントを取得
- 対象者に
sts.amazonaws.com
を入力
- 対象者に
クラスターへアドオンを追加
CNIプラグイン追加
-
VPCからPodにIPアドレスを設定するため、EKS標準のamazon-vpc-cni-k8sを使います
- 詳しくはこちら
-
ロールの作成
- 「IAM」→「ロール」→「ロールの作成」で以下のようにする(IDプロバイダーはEKSクラスターのOIDCプロバイダーURL)
- ポリシーは
AmazonEKS_CNI_Policy
をアタッチ
- 名前をつけてロールを作成し、再度そのロールの画面を開きます
- 「信頼関係」タブから「信頼関係の編集」をクリック
-
aud
をsub
に変更 -
sts.amazonaws.com
をsystem:serviceaccount:kube-system:aws-node
に変更
-
- 「IAM」→「ロール」→「ロールの作成」で以下のようにする(IDプロバイダーはEKSクラスターのOIDCプロバイダーURL)
-
マネジメントコンソールからEKSクラスターを開き、「設定」→「アドオン」→「新規を追加」をクリックし作成したロールを設定します
coredns と kube-proxy 追加
- CNI同様にマネジメントコンソールから追加します
- こちらはロールは不要
ノード作成
ノード用のロール作成
- マネジメントコンソールにて「IAM」→「ロール」→「ロールの作成」
- 信頼されたエンティティは「AWSサービス」のまま、ユースケースで「EC2」を選択
- 次のステップでAWS管理ポリシーから以下をアタッチします
AmazonEKSWorkerNodePolicy
AmazonEC2ContainerRegistryReadOnly
ノードグループ作成
- マネジメントコンソールにて「EKS」→「クラスター」→「作成したクラスター名」
- 設定→コンピューティングから「ノードグループの追加」をクリック
- ノードIAMロールに上で作成したノード用のロールを設定
- インスタンスタイプやスケール設定はよしなに
確認
- 以下コマンドで作成されたことを確認
$ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-172-31-127-7.ap-northeast-1.compute.internal Ready <none> 23s v1.21.5-eks-bc4871b ip-172-31-46-89.ap-northeast-1.compute.internal Ready <none> 35s v1.21.5-eks-bc4871b
ALB準備
AWS Load Balancer Controllerインストール
IAMポリシー作成
- 「IAM」→「ポリシー」→「ポリシーを作成」
- JSONタブを選択しこちらの内容をコピペ
- 名前を付けて「ポリシーの作成」を押下
- JSONタブを選択しこちらの内容をコピペ
IAMロール作成
-
「IAM」→「ロール」→「ロールの作成」で以下のようにする(IDプロバイダーはEKSクラスターのOIDCプロバイダーURL)
-
ポリシーは上記で作成したものをアタッチ
-
名前をつけてロールを作成し、再度そのロールの画面を開きます
-
「信頼関係」タブから「信頼関係の編集」をクリック
- 信頼先に
"oidc.eks.ap-northeast-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:sub": "system:serviceaccount:kube-system:aws-load-balancer-controller"
を追加- IDは↑のOIDCプロバイダーURLのID
- IDは↑のOIDCプロバイダーURLのID
- 信頼先に
サービスアカウント作成
-
aws-load-balancer-controller-service-account.yaml
を作成apiVersion: v1 kind: ServiceAccount metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/name: aws-load-balancer-controller name: aws-load-balancer-controller namespace: kube-system annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT-ID:role/AmazonEKSLoadBalancerControllerRole
- 適用
kubectl apply -f aws-load-balancer-controller-service-account.yaml
AWS Load Balancer Controllerインストール
-
cert-managerインストール
kubectl apply \ --validate=false \ -f https://github.com/jetstack/cert-manager/releases/download/v1.5.4/cert-manager.yaml
-
コントローラのyaml取得
curl -Lo v2_3_0_full.yaml https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.3.0/v2_3_0_full.yaml
-
上記yamlを修正
- 480行目あたりにある下記のServiceAccount sectionを削除します(さきほど適用したサービスアカウントが上書きされてしまうため)
apiVersion: v1 kind: ServiceAccount metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/name: aws-load-balancer-controller name: aws-load-balancer-controller namespace: kube-system
- 739行目の
cluster-name
を今回作成したクラスター名にします - 下手にservice名(aws-load-balancer-controller)をいじらないほうがよいです(それで一度失敗しました)
- 480行目あたりにある下記のServiceAccount sectionを削除します(さきほど適用したサービスアカウントが上書きされてしまうため)
-
適用と確認
kubectl apply -f v2_3_0_full.yaml kubectl get deployment -n kube-system aws-load-balancer-controller
ドメイン取得
- ALBでgRPCを使う場合はリスナー側をHTTPSにする必要があるため、ドメインを取得して証明書を発行します。
-
freenomで無料のドメインを取得
- ここでは
albeksgrpc.tk
というドメインを取得
- ここでは
TLS証明書の作成
- Route53にパブリックホストゾーン登録
- 「Route53」→「ホストゾーン」→「ホストゾーンの作成」
- 作成されたホストゾーンを選択し、作成されたNSレコードを確認
- 「Route53」→「ホストゾーン」→「ホストゾーンの作成」
- freenomのページに戻りNameserversを追加
- 右上のメニューの「Services」→「My Domains」
- 作成したドメインの「Manage Domain」をクリック
- 「Management Tools」→「Nameservers」
- 「Use custom nameservers (enter below)」を選択
- Route53で作成されたNSを登録
- ACMで証明書をリクエスト
- 「AWS Certificate Manager」→「証明書」→「証明書をリクエスト」→「パブリック証明書をリクエスト」
- ドメイン名を設定しDNS検証を選択してリクエスト
- 作成した証明書は検証の保留中になっているので、証明書の画面に入り「Route 53でのレコードの作成」をすることでRoute53にCNAMEが追加されます
- 作成した証明書のARNは後ほど、Ingressを作成する際に必要になります
externalDNSのデプロイ
- ALBのDNSとドメインをいい感じに関連付けてくれるサービスです
- 公式の手順はこちら
- yamlの書き方とか変わる可能性があるので注意
IAMポリシー作成
- externalDNSサービスがRoute53を更新できるようにポリシーを作成します
- 「IAM」→「ポリシー」→「ポリシーを作成」で以下のJSONをコピペ
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": [
"arn:aws:route53:::hostedzone/*"
]
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": [
"*"
]
}
]
}
サービスアカウントのIAMロール作成
- externalDNSサービス用のロールを作成します
- 「IAM」→「ロール」→「ロールの作成」で以下のようにする(IDプロバイダーはEKSクラスターのOIDCプロバイダーURL)
- ポリシーは上記で作成したものをアタッチ
- 名前をつけてロールを作成し、再度そのロールの画面を開きます
- 「信頼関係」タブから「信頼関係の編集」をクリック
- 信頼先に
"oidc.eks.ap-northeast-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:sub": "system:serviceaccount:default:external-dns"
を追加- IDは↑のOIDCプロバイダーURLのID
- namespaceを変える場合は、
default
の部分を変更
- 信頼先に
externalDNSインストール
- external_dns.yamlを作成
- RBACありの想定の書き方
- 念のため
kubectl api-versions | grep rbac.authorization.k8s.io
で確認するとよい
- 念のため
-
IAM-SERVICE-ROLE-NAME
は一つ前の手順で作成したロールの名前 -
domain-filter
は取得したドメイン名
apiVersion: v1 kind: ServiceAccount metadata: name: external-dns annotations: # Substitute your account ID and IAM service role name below. eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["extensions","networking.k8s.io"] resources: ["ingresses"] verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] verbs: ["list","watch"] --- apiVersion: rbac.authorization.k8s.io/v1 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 spec: serviceAccountName: external-dns containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.7.6 args: - --source=service - --source=ingress - --domain-filter=albeksgrpc.tk # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones - --provider=aws - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization - --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
- RBACありの想定の書き方
- 反映する
kubectl apply -f external_dns.yaml
Service準備
サービスアカウントのIAMロール作成
- Pod内のコンテナ用のIAMロールを作成します
- 今回のマイクロサービスでは特に権限は不要だったのでスキップします
- やる場合はAWS Load Balancer ControllerやexternalDNSのIAMロール作成と同じ手順
コンテナイメージ作成
-
gRPCサーバ側の処理を作成します
- かの有名なRouteGuideServiceを例にします
git clone https://github.com/grpc/grpc.git cd grpc/examples/python/route_guide
- TLSはALBで終端され、マイクロサービス側はHTTPになるので
route_guide_server.py
は下記のようにinsecureのままです(なのでソース修正はありません)
server.add_insecure_port('[::]:50051')
-
Dockerfile を作成して上記のポートをEXPOSEします
FROM ubuntu:18.04 RUN apt-get update && \ apt-get install -y \ python3-pip RUN pip3 install -U pip RUN pip3 install \ grpcio \ grpcio-tools RUN mkdir /usr/local/route_guide COPY ./ /usr/local/route_guide EXPOSE 50051 CMD ["bash", "-c", "cd /usr/local/route_guide; python3 route_guide_server.py"]
-
ECRのリポジトリを作成
- 「ECR」→「リポジトリ」→「リポジトリを作成」
- 「ECR」→「リポジトリ」→「リポジトリを作成」
-
コンテナイメージを作成し、ECRへプッシュする
- コマンドは先ほど作成したリポジトリを選択し「プッシュコマンドの表示」から確認できるのでそちらをコピペして使います
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com docker build -t sample/route_guide . docker tag sample/route_guide:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/sample/route_guide:latest docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/sample/route_guide:latest
Service作成
マイクロサービスをデプロイ
- マニフェスト作成(イメージのURLは上記で作成したECRリポジトリのURL)
- 注意するのはコンテナでEXPOSEしたポート番号と合わせること
- (nameに
_
が使えないのも注意)
apiVersion: v1 kind: Service metadata: name: route-guide-service labels: app: route-guide spec: selector: app: route-guide ports: - port: 50051 targetPort: 50051 type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: name: route-guide labels: app: route-guide spec: replicas: 3 selector: matchLabels: app: route-guide template: metadata: labels: app: route-guide spec: containers: - name: route-guide image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/sample/route_guide:latest imagePullPolicy: Always resources: limits: memory: "1024Mi" cpu: "500m" ports: - containerPort: 50051 nodeSelector: kubernetes.io/os: linux
- 適用
kubectl apply -f route_guide.yaml
Ingressのデプロイ
- ingress.yaml を作成します
-
external-dns.alpha.kubernetes.io/hostname
: 取得したドメイン名 -
alb.ingress.kubernetes.io/certificate-arn
: ACMで作成した証明書のARN -
path: /routeguide.RouteGuide/
はprotoのpackage名.service名
と完全一致させます- Prefixなので
routeguide
とかで十分だろうなどと思っていたらうまくいきませんでした
- Prefixなので
- 複数のマイクロサービスを登録する場合は、同じ手順でポート番号を変えてbackendに追加していきます(以下は3つ登録する場合)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: alb-name
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/backend-protocol-version: GRPC
external-dns.alpha.kubernetes.io/hostname: albeksgrpc.tk
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:XXXXXXXXXXXX:certificate/12345678-96c7-431a-b781-361a23c48d87
spec:
rules:
- http:
paths:
- backend:
service:
name: route-guide-service
port:
number: 50051
path: /routeguide.RouteGuide/
pathType: Prefix
- backend:
service:
name: keyvaluestore-service
port:
number: 50052
path: /keyvaluestore.KeyValueStore/
pathType: Prefix
- backend:
service:
name: helloworld-service
port:
number: 50053
path: /helloworld.Greeter/
pathType: Prefix
- 適用
kubectl apply -f ingress.yaml
ALBの確認
- ingressをデプロイするとALBが自動で作成されます
- 「EC2」→「ロードバランサー」で確認
- 作成されない場合は
kubectl describe ingress alb-name
などで確認
- 作成されない場合は
- 「リスナー」タブから「ルールの表示/編集」でルーティングの設定を確認
- 「EC2」→「ロードバランサー」で確認
DNSの確認
- external-dnsのPod名を確認
$ kubectl get all | grep pod/external-dns pod/external-dns-845fd6cbbc-82f4w 1/1 Running 0 20h
- 上記PodのlogからDNSの更新がされたことを確認
$ kubectl logs external-dns-845fd6cbbc-82f4w
- 以下のようなメッセージがあればOK
time="2021-12-10T09:12:14Z" level=info msg="2 record(s) in zone albeksgrpc.tk. [Id: /hostedzone/Z093644125CXTO2QHYRBL] were successfully updated"
- 「Route53」→「ホストゾーン」でALBのDNSが登録されています
動作確認
クライアント
-
route_guide_client.py の
run
関数を例にします- オリジナル
def run(): with grpc.insecure_channel('localhost:50051') as channel: stub = route_guide_pb2_grpc.RouteGuideStub(channel)
- HTTPSなのでセキュアに変更します
def run(): cred = grpc.ssl_channel_credentials(root_certificates=None, private_key=None, certificate_chain=None) with grpc.secure_channel('albeksgrpc.tk:443', cred) as channel: stub = route_guide_pb2_grpc.RouteGuideStub(channel)
- あとはこの
stub
に対して関数コールすれば疎通できます - (補足)その他の50052や50053ポートのマイクロサービスに対してもこの
channel
を使い回せます
あとがき
- 当初思っていたよりも膨大なボリュームになりました;
- 今回はinternet-facingのALBでしたが、次はinternalで作成する予定です
- クラスタエンドポイントもプライベートにして、NATゲートウェイも不要にしたいです
- 以下を見る限りNATゲートウェイを使わずに構築できそうでしたが、そう簡単にはできなかったので今後試してみます
Discussion