AWSのKubernetesでサービスを公開する最高の方法~ALB,ACM,Route53の自動作成~
AWSのKubernetesでサービスを公開する最高の方法~ALB,ACM,Route53の自動作成~:
Kubernetesが流行っているので、これからEKSを使ってサービスを公開していこうと考えている方の参考になれば嬉しいです。
AWSのEKSを使って構築しています。
EKSでサービスを公開したいけど、ロードバランサとか証明書とかの設定面倒くさいと考えている方が多いと思います。
今回の記事のゴールはymlファイルをKubernetesにデプロイするだけで、ALB作成、Route53にレコードセット追加、ACMの証明書をALBに割当の作業を自動でできるようにします。
EKSのGetting StartではCLB(Classic Load Balancer)を使用していますが、L7ロードバランサがいいのでALBで作成します。ホストベースでもパスベースでもどちらでもルーティングしてくれるので。。
本記事丁寧に説明するのですごく長くなりそうです。。。
3つのロールを準備する必要があります。
まずはWorker Node用のIAMロールに対して権限を付与します。
eksctlを使ってクラスタを構築した場合、
このロールに対して
ALB用のIAMロールと同じようにexternal-dns用のIAMロールも作成していきます。
以上で3つのIAMロールの準備ができました。
ここであえて説明するとすれば AWS ALB Ingress Controller に付与するポリシーをどうするかです。
AWS ALB Ingress ControllerはALBの作成をしてくれるので、公式手順 だとこんな感じのポリシーが必要になります。
公式手順ではこのポリシーをノードのロールにアタッチしていますが、これをするとノード配下のPodに対しても同様の権限が与えられるというセキュリティ上恐ろしいことが起こってしまします。
そこでkube2iamを使ってノードに権限を与えるのではなくPodに権限を与えます。
類似プロダクトとしてkiamというものもあるみたいです。
Kubernetes(のアクセス管理機能)とIAMの間に入ってアクセス権限の管理を行ってくれるのが「kube2iam」や「kiam」です。
下記の
デプロイコマンド
kube2iamのオプションパラメーター
ロールのARNのベース部分(arn:aws:iam::XXXXXXX:role/)を自動検出します。
「true」を指定した場合、ホスト(EC2)のiptablesに必要な設定を自動登録します。
「--iptables」を指定する場合は「--host-ip」もセットで必ず指定する必要があります。
「Getting Started」の手順に従ってEKS環境を構築した場合はPod Networkingとして「amazon-vpc-cni-k8s」が採用されるため、このオプションは「eni+」を指定する必要があります。
詳細なログを出力します。
以上でkube2iamの準備は完了です。
続いてAWS ALB Ingress Controllerをやっていきます。
AWS ALB Ingress Controller本体をデプロイします。
公式のymlファイルをダウンロードして2箇所変更します。
ymlファイルを保存してデプロイします。
AWS ALB Ingress Controllerは自動でクラスタのサブネットを見つけてくれるのですが、そのためにはPublicサブネットにタグを付与する必要があります。
Keyに
Keyに
これでAWS ALB Ingress Controllerの準備は完了です。
example.comと*.example.comで証明書を取得してください。
手順はこちらを参考にしていただければと思います。
https://daichan.club/aws/78593
ExternalDNSを使用するとKubernetesのYAML内でドメイン名を指定するだけで、Route53のHosted Zoneにレコードが登録できて非常に便利です。
下記のmanufest.ymlファイルのexample.comと
デプロイコマンド
external-dnsのオプションパラメーター
どのリソースをもとにroute53のレコードセットを作成するかを決めます。今回はServiceとIngressを指定しています。
HostedZoneに対応するドメイン名に置き換えます。
AWSを使うのでAWSで大丈夫です。他にもGoogleやAzureなどが指定できるようです。
syncにすると削除も反映されます。作成のみにしたい場合はupsert-onlyにしてください。
txtレコードの登録を行います。
ここは適当にしましたが問題なく動きました。
すべての準備ができましたので、サンプルアプリケーションをデプロイしてみます。
サンプルアプリケーションはClassMethodさんのサイトのものを使っています。
ECRを作成してログインします。
repositoryUriは後で使うのでコピーしておいてください。
アクセスしたらPod名を返却するWebサーバを作成します。
イメージを作成してECRにプッシュします。
target2も同じように作成してECRにプッシュしてください。
test.example.com/target1にアクセスしたらtarget1のPodに、test.example.com/target2にアクセスしたらtarget2のPodにアクセスするmanufest.ymlファイルを作成します。
デプロイコマンド
test.example.com/target1とtest.example.com/target2にアクセスして挙動を確かめてください。
先ほど作成したmanufest.ymlファイルのingressのspecの部分のみ変更しています。
これをデプロイすることでホストベースでもアクセス振り分けができます。
ymlファイルを書いたら長くなったので、あとでGithubにソース上げておきます。
https://qiita.com/koudaiii/items/2031d67c715b5bb50357
https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/walkthrough/echoserver/#kube2iam-setup
https://qiita.com/mumoshu/items/bd82bd69525bc8feb4da
Kubernetesが流行っているので、これからEKSを使ってサービスを公開していこうと考えている方の参考になれば嬉しいです。
AWSのEKSを使って構築しています。
はじめに
EKSでサービスを公開したいけど、ロードバランサとか証明書とかの設定面倒くさいと考えている方が多いと思います。今回の記事のゴールはymlファイルをKubernetesにデプロイするだけで、ALB作成、Route53にレコードセット追加、ACMの証明書をALBに割当の作業を自動でできるようにします。
EKSのGetting StartではCLB(Classic Load Balancer)を使用していますが、L7ロードバランサがいいのでALBで作成します。ホストベースでもパスベースでもどちらでもルーティングしてくれるので。。
本記事丁寧に説明するのですごく長くなりそうです。。。
アーキテクト
- AWS ALB Ingress Controller
ALBの自動デプロイをするいい感じのツール
AWS ALB Ingress Controller をpodで起動しておくことで特定のannotationがついた Ingress リソースをみつけるとそのannotationの情報に応じて ALB を作成してくれます。
https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/controller/config/ - kube2iam
PodからAWSリソースを操作するためのIAM権限の管理をするいい感じのツール
https://github.com/jtblin/kube2iam - external-dns
KubernetesのServiceやIngressをDNSプロバイダに対して同期するいい感じのツール
条件にあったIngressやServiceリソースが作成された時に AWSであればRoute53でDNSレコードを作成してくれます。
https://github.com/kubernetes-incubator/external-dns
やっていく
IAMロールの作成
3つのロールを準備する必要があります。
Worker Node用のIAMロール
まずはWorker Node用のIAMロールに対して権限を付与します。eksctlを使ってクラスタを構築した場合、
XXXXXXXX-NodeInstanceRole-YYYYYYYYYYYY
という名前のロールが作成されます。このロールに対して
config/node-role-permission-policy.json
のポリシーをアタッチします。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*" } ] }
#!/bin/sh NODE_INSTANCE_POLICY_ARN=$(aws iam create-policy --policy-name sts-assumerole-policy --policy-document file://config/node-role-permission-policy.json --query "Policy.[Arn]" --output text) NODE_INSTANCE_ROLE_NAME=$(aws iam list-roles --query "Roles[?contains(RoleName,\`NodeInstanceRole\`)].[RoleName][]" --output text) aws iam attach-role-policy --role-name $NODE_INSTANCE_ROLE_NAME --policy-arn $NODE_INSTANCE_POLICY_ARN
ALB用のIAMロール
config/ingress-iam-policy.json
は 公式手順にあるポリシー をダウンロードしています。export NODE_INSTANCE_ROLE_ARN=$(aws iam list-roles --query "Roles[?contains(RoleName,\`NodeInstanceRole\`)].[Arn][]" --output text) cat << EOF | jq > config/pod-role-trust-policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }, { "Effect": "Allow", "Principal": { "AWS": "${NODE_INSTANCE_ROLE_ARN}" }, "Action": "sts:AssumeRole" } ] } EOF aws iam create-role \ --role-name alb-ingress-controller \ --assume-role-policy-document file://config/pod-role-trust-policy.json INGRESS_POLICY_ARN=$(aws iam create-policy --policy-name ingressController-iam-policy --policy-document file://config/ingress-iam-policy.json --query "Policy.[Arn]" --output text) aws iam attach-role-policy --role-name alb-ingress-controller --policy-arn $INGRESS_POLICY_ARN
external-dns用のIAMロール
ALB用のIAMロールと同じようにexternal-dns用のIAMロールも作成していきます。config/route53-iam-policy.json
ファイルは次のようにしています。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets" ], "Resource": [ "arn:aws:route53:::hostedzone/*" ] }, { "Effect": "Allow", "Action": [ "route53:ListHostedZones", "route53:ListResourceRecordSets" ], "Resource": [ "*" ] } ] }
export NODE_INSTANCE_ROLE_ARN=$(aws iam list-roles --query "Roles[?contains(RoleName,\`NodeInstanceRole\`)].[Arn][]" --output text) cat << EOF | jq > config/pod-role-trust-policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }, { "Effect": "Allow", "Principal": { "AWS": "${NODE_INSTANCE_ROLE_ARN}" }, "Action": "sts:AssumeRole" } ] } EOF aws iam create-role \ --role-name route53-externaldns-controller \ --assume-role-policy-document file://config/pod-role-trust-policy.json ROUTE53_POLICY_ARN=$(aws iam create-policy --policy-name route53Controller-iam-policy --policy-document file://config/route53-iam-policy.json --query "Policy.[Arn]" --output text) aws iam attach-role-policy --role-name route53-externaldns-controller --policy-arn $ROUTE53_POLICY_ARN
kube2iam
仕組み
ここであえて説明するとすれば AWS ALB Ingress Controller に付与するポリシーをどうするかです。AWS ALB Ingress ControllerはALBの作成をしてくれるので、公式手順 だとこんな感じのポリシーが必要になります。
公式手順ではこのポリシーをノードのロールにアタッチしていますが、これをするとノード配下のPodに対しても同様の権限が与えられるというセキュリティ上恐ろしいことが起こってしまします。
そこでkube2iamを使ってノードに権限を与えるのではなくPodに権限を与えます。
類似プロダクトとしてkiamというものもあるみたいです。
Kubernetes(のアクセス管理機能)とIAMの間に入ってアクセス権限の管理を行ってくれるのが「kube2iam」や「kiam」です。
デプロイ
下記のmanufest.yml
ファイルをデプロイするとDaemonsetでPodが各ノードで動きます。--- apiVersion: v1 kind: ServiceAccount metadata: name: kube2iam namespace: kube-system --- apiVersion: v1 items: - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: kube2iam rules: - apiGroups: [""] resources: ["namespaces","pods"] verbs: ["get","watch","list"] - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kube2iam subjects: - kind: ServiceAccount name: kube2iam namespace: kube-system roleRef: kind: ClusterRole name: kube2iam apiGroup: rbac.authorization.k8s.io kind: List --- apiVersion: apps/v1 kind: DaemonSet metadata: name: kube2iam namespace: kube-system labels: app: kube2iam spec: selector: matchLabels: app: kube2iam template: metadata: labels: app: kube2iam spec: serviceAccountName: kube2iam hostNetwork: true containers: - name: kube2iam image: jtblin/kube2iam:latest imagePullPolicy: Always args: - "--auto-discover-base-arn" - "--iptables=true" - "--host-ip=$(HOST_IP)" - "--host-interface=eni+" - "--verbose" env: - name: HOST_IP valueFrom: fieldRef: fieldPath: status.podIP ports: - containerPort: 8181 hostPort: 8181 name: http securityContext: privileged: true
kubectl apply -f manufest.yml
(args:)
の意味は以下の通りです。--auto-discover-base-arn
ロールのARNのベース部分(arn:aws:iam::XXXXXXX:role/)を自動検出します。
--iptables=true
「true」を指定した場合、ホスト(EC2)のiptablesに必要な設定を自動登録します。
--host-ip=$(HOST_IP)
「--iptables」を指定する場合は「--host-ip」もセットで必ず指定する必要があります。
--host-interface=eni+
「Getting Started」の手順に従ってEKS環境を構築した場合はPod Networkingとして「amazon-vpc-cni-k8s」が採用されるため、このオプションは「eni+」を指定する必要があります。
--verbose
詳細なログを出力します。
以上でkube2iamの準備は完了です。
AWS ALB Ingress Controller
続いてAWS ALB Ingress Controllerをやっていきます。
仕組み
- ALB Ingress Controllerは、KubernetesのAPI ServerからのEventを監視し、該当のEvent を検知したらAWSのリソースを作成し始めます。
- annotationを指定することで、サブネットやインターネット向けか内部向けかも決めることもできます。
- リスナーは、ingressのannotationで指定したポート用に作成されます。ポートが指定されていない場合、80または443を使用。ACMも使用することもできます。
- 入力リソースで指定された各パスに対してルールが作成され、特定のパスへのトラフィックが正しい KubernetesのService にルーティングされる。
デプロイ
- ALB の向き先となるターゲットグループは、ingressに記述されたServiceごとにAWSで作成。
公式手順 通りにrbac-role.yaml
ファイルをデプロイします。
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.0.0/docs/examples/rbac-role.yaml
公式のymlファイルをダウンロードして2箇所変更します。
--cluster-name=
を自分のクラスタの名前に変更します。alb-ingress-controller.yaml
ファイルの下記の部分のappの下に追記します。template: metadata: creationTimestamp: null labels: app: alb-ingress-controller
template: metadata: creationTimestamp: null labels: app: alb-ingress-controller # ここに追記 annotations: iam.amazonaws.com/role: <ALB用のIAMロールのArn> # ここまで
kubectl apply -f alb-ingress-controller.yaml
サブネットにタグを付与
AWS ALB Ingress Controllerは自動でクラスタのサブネットを見つけてくれるのですが、そのためにはPublicサブネットにタグを付与する必要があります。Keyに
kubernetes.io/role/alb-ingress
を追加してValueは空。Keyに
kubernetes.io/role/elb
を追加してValueは1
にして保存。これでAWS ALB Ingress Controllerの準備は完了です。
ACMの準備
example.comと*.example.comで証明書を取得してください。手順はこちらを参考にしていただければと思います。
https://daichan.club/aws/78593
external-dns
仕組み
ExternalDNSを使用するとKubernetesのYAML内でドメイン名を指定するだけで、Route53のHosted Zoneにレコードが登録できて非常に便利です。
デプロイ
下記のmanufest.ymlファイルのexample.comと<external-dns用のIAMロールのArn>
をご自身の環境に変更してください。apiVersion: v1 kind: ServiceAccount metadata: name: external-dns namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: - "" resources: - "services" verbs: - "get" - "watch" - "list" - apiGroups: - "" resources: - "pods" verbs: - "get" - "watch" - "list" - apiGroups: - "extensions" resources: - "ingresses" verbs: - "get" - "watch" - "list" - apiGroups: - "" resources: - "nodes" verbs: - "list" --- 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: kube-system --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: external-dns namespace: kube-system spec: strategy: type: Recreate template: metadata: labels: app: external-dns annotations: iam.amazonaws.com/role: <external-dns用のIAMロールのArn> spec: serviceAccountName: external-dns containers: - name: external-dns image: registry.opensource.zalan.do/teapot/external-dns:v0.5.9 args: - --source=service - --source=ingress - --domain-filter=example.com - --provider=aws - --policy=sync - --registry=txt - --txt-owner-id=hoge
kubectl apply -f manufest.yml
(args:)
の意味は以下の通りです。--source
どのリソースをもとにroute53のレコードセットを作成するかを決めます。今回はServiceとIngressを指定しています。
--domain-filter
HostedZoneに対応するドメイン名に置き換えます。
--provider
AWSを使うのでAWSで大丈夫です。他にもGoogleやAzureなどが指定できるようです。
--policy
syncにすると削除も反映されます。作成のみにしたい場合はupsert-onlyにしてください。
--registry
txtレコードの登録を行います。
--txt-owner-id
ここは適当にしましたが問題なく動きました。
サンプルアプリケーション
すべての準備ができましたので、サンプルアプリケーションをデプロイしてみます。サンプルアプリケーションはClassMethodさんのサイトのものを使っています。
ECRの作成
ECRを作成してログインします。aws ecr create-repository --repository-name eks-test-app { "repository": { "repositoryArn": "arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/eks-test-app", "registryId": "XXXXXXXXXXXX", "repositoryName": "eks-test-app", "repositoryUri": "XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app", "createdAt": 1546156701.0 } } aws ecr get-login --no-include-email # コマンドが出力されるのですべてコピーして貼り付け
Dockerの準備
アクセスしたらPod名を返却するWebサーバを作成します。package main import ( "fmt" "log" "net/http" "os" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "healthy!") }) http.HandleFunc("/target1", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "/target1:" + os.Getenv("POD_NAME")) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
FROM golang ADD server.go /go/src/ EXPOSE 8080 CMD ["/usr/local/go/bin/go", "run", "/go/src/server.go"]
XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
はECRのrepositoryUriに変更してください。$ docker build -t eks-test-app:target1 . $ docker tag eks-test-app:target1 XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target1 $ docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target1
パスベースのルーティング
test.example.com/target1にアクセスしたらtarget1のPodに、test.example.com/target2にアクセスしたらtarget2のPodにアクセスするmanufest.ymlファイルを作成します。apiVersion: v1 kind: Namespace metadata: name: "test-app" --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "test-app-deployment-target1" namespace: "test-app" spec: replicas: 2 template: metadata: labels: app: "test-app-target1" spec: containers: - image: 155526509481.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target1 imagePullPolicy: Always name: "test-app-target1" ports: - containerPort: 8080 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "test-app-deployment-target2" namespace: "test-app" spec: replicas: 2 template: metadata: labels: app: "test-app-target2" spec: containers: - image: 155526509481.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target2 imagePullPolicy: Always name: "test-app-target2" ports: - containerPort: 8080 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name --- apiVersion: v1 kind: Service metadata: name: "test-app-service-target1" namespace: "test-app" spec: ports: - port: 80 targetPort: 8080 protocol: TCP type: NodePort selector: app: "test-app-target1" --- apiVersion: v1 kind: Service metadata: name: "test-app-service-target2" namespace: "test-app" spec: ports: - port: 80 targetPort: 8080 protocol: TCP type: NodePort selector: app: "test-app-target2" --- apiVersion: v1 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: "ingress" namespace: test-app annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/certificate-arn: <ACMのArn> alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' labels: app: test-app spec: rules: - host: test.example.com http: paths: - path: /target1 backend: serviceName: "test-app-service-target1" servicePort: 80 - path: /target2 backend: serviceName: "test-app-service-target2" servicePort: 80
kubectl apply -f manufest.yml
ホストベースでのルーティング
先ほど作成したmanufest.ymlファイルのingressのspecの部分のみ変更しています。spec: rules: - host: target1.example.com http: paths: - path: /target1 backend: serviceName: "test-app-service-target1" servicePort: 80 - host: target2.example.com http: paths: - path: /target2 backend: serviceName: "test-app-service-target2" servicePort: 80
ymlファイルを書いたら長くなったので、あとでGithubにソース上げておきます。
参考サイト
https://qiita.com/koudaiii/items/2031d67c715b5bb50357https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/walkthrough/echoserver/#kube2iam-setup
https://qiita.com/mumoshu/items/bd82bd69525bc8feb4da
コメント
コメントを投稿