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で作成します。ホストベースでもパスベースでもどちらでもルーティングしてくれるので。。

本記事丁寧に説明するのですごく長くなりそうです。。。


アーキテクト


やっていく


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 
以上で3つのIAMロールの準備ができました。


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 
kube2iamのオプションパラメーター(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 
AWS ALB Ingress Controller本体をデプロイします。
公式の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> 
        # ここまで 
ymlファイルを保存してデプロイします。

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 
external-dnsのオプションパラメーター(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 
# コマンドが出力されるのですべてコピーして貼り付け 
repositoryUriは後で使うのでコピーしておいてください。


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"] 
イメージを作成してECRにプッシュします。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 
target2も同じように作成してECRにプッシュしてください。


パスベースのルーティング

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 
test.example.com/target1とtest.example.com/target2にアクセスして挙動を確かめてください。


ホストベースでのルーティング

先ほど作成した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/2031d67c715b5bb50357
https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/walkthrough/echoserver/#kube2iam-setup
https://qiita.com/mumoshu/items/bd82bd69525bc8feb4da

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)