サブドメイン分割で複数サイトをEKSクラスタに構築してみた(ALB Ingress Controller, ExternalDNS, 独自ドメイン, HTTPS対応)

EKSクラスタ内で動作しているpodを外部公開するためのエンドポイントとしてALBを使う場合の構築手順をまとめておく。
ALB Ingress Controllerを使うことで、ingressをapplyしたときに自動でALBを構築してくれるのですごく便利だった。
また、ingressで任意のドメインを指定するとALBのhost-based routingに反映してくれるので、サブドメインを変えて複数のサイト公開も楽にできた。
加えて、ExternalDNSを使うと自分が管理しているドメインのレコード追加を自動でやってくれる。
SSL証明書をACMに用意しておくと、簡単にHTTPS化できたので、HTTPS対応についても最後にまとめておく。

アーキテクチャ

今回使うアプリケーションはDjangoとその前段にリバースプロキシとしてnginxを配置したシンプルなもの。EKSクラスタ内にはその他にALBにルール反映してくれるALB Inress ControllerとRoute 53にレコード反映してくれるExternalDNSのpodが起動している。SSL証明書発行にはACMを利用。

アーキテクチャ

EKSクラスタ構築

公式サイトの方法を参考にクラスタ構築。
eksctl で構築するのが便利。

eksctl create cluster \
--name test-cluster \
--version 1.14 \
--region ap-northeast-1 \
--nodegroup-name standard-workers \
--node-type t3.medium \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4 \
--managed

(参考)EC2インスタンスのタイプによって付与可能なIPアドレス数が異なるため、podをたくさん起動する場合はworker nodeのインスタンスタイプに注意すること。
IPアドレス数の一覧はこちら

ALB Ingress Controllerのデプロイ

基本は公式サイトの手順通りに実施する。

EKSクラスタのサブネットにタグが付いていることを確認

  • VPC 内のすべてのサブネット
    • kubernetes.io/cluster/<cluster-name>shared
  • VPC のパブリックサブネット
    • kubernetes.io/role/elb1
  • VPC 内のプライベートサブネット
    • kubernetes.io/role/internal-elb1

IAM ポリシーを作成

IAMポリシーファイルをダウンロード

curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.3/docs/examples/iam-policy.json

IAMポリシーを作成

aws iam create-policy \
--policy-name ALBIngressControllerIAMPolicy \
--policy-document file://iam-policy.json

EKSクラスタのワーカーノードの IAM ロール名を取得

kubectl -n kube-system describe configmap aws-auth

出力結果の<アカウントID>と<ロール名>を控えておく。

Name:         aws-auth
Namespace:    kube-system
Labels:       <none>
Annotations:  <none>

Data
====
mapRoles:
----
- groups:
  - system:bootstrappers
  - system:nodes
  rolearn: arn:aws:iam::<アカウントID>:role/<ロール名>
  username: system:node:{{EC2PrivateDNSName}}

mapUsers:
----
[]

Events:  <none>

EKSクラスタのワーカーノードにIAMポリシーをアタッチ

aws iam attach-role-policy \
--policy-arn arn:aws:iam::<アカウントID>:policy/ALBIngressControllerIAMPolicy \
--role-name <ロール名>

ALB Ingress Controller で使用するサービスアカウント、クラスターロール、クラスターロールバインディングを作成

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.3/docs/examples/rbac-role.yaml

ALB Ingress Controller をデプロイ

ここは公式サイトとは違う方法でデプロイする。
公式サイトではmanifestをデプロイした後に環境に合わせて編集しているが、今回は最初にデプロイ用manifestをダウンロードし、ファイルを編集した後にデプロイする。

ALB-Ingress-Controllerのmanifestをダウンロード

curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.3/docs/examples/alb-ingress-controller.yaml

Manifestを編集する。以下のパラメータのコメントアウトを外し、書き換える。

    spec:
      containers:
      - args:
        - --cluster-name=test-cluster
        - --aws-vpc-id=<VPC ID>
        - --aws-region=ap-northeast-1

(参考)VPC IDを確認するには以下のコマンドを実行する。

aws ec2 describe-vpcs

デプロイ

kubectl apply -f alb-ingress-controller.yaml

以下のコマンドを実行してエラーが表示されなければデプロイ成功

kubectl logs -n kube-system -f $(kubectl get po -n kube-system | egrep -o 'alb-ingress-controller[A-Za-z0-9-]+')

ExternalDNSのデプロイ

ALBのエンドポイントを自分が管理しているRoute53にailiasとして反映させてくれる ExternalDNS というOSSがあるので使ってみる。

IAM ポリシーを作成

IAMポリシーファイルを作成。公式サイトのチュートリアルから流用

{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Effect": "Allow",
     "Action": [
       "route53:ChangeResourceRecordSets"
     ],
     "Resource": [
       "arn:aws:route53:::hostedzone/*"
     ]
   },
   {
     "Effect": "Allow",
     "Action": [
       "route53:ListHostedZones",
       "route53:ListResourceRecordSets"
     ],
     "Resource": [
       "*"
     ]
   }
 ]
}

IAMポリシーを作成

aws iam create-policy \
--policy-name ExternalDNSIAMPolicy \
--policy-document file://external-dns-iam-policy.json

EKSクラスタのワーカーノードにIAMポリシーをアタッチ

aws iam attach-role-policy \
--policy-arn arn:aws:iam::<アカウントID>:policy/ExternalDNSIAMPolicy \
--role-name <ロール名>

ExternalDNS をデプロイ

ExternalDNSのmanifestをダウンロード

curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.3/docs/examples/external-dns.yaml

Manifestを編集。example.comには自分で管理しているFQDNに変更すること。ダウンロードしたファイルではpolicyオプションがupsert-onlyになっているが、これはレコード追加だけすることを表す。今回はingressの内容に合わせて適宜レコード削除もしてほしいのでpolicyオプションは使わない。

args:
- --domain-filter=example.com
#- --policy=upsert-only # コメントアウトまたは削除

デプロイ

kubectl apply -f external-dns.yaml

確認

kubectl logs -f $(kubectl get po | egrep -o 'external-dns[A-Za-z0-9-]+')

以下のように表示されればOK

time="2019-12-13T14:48:31Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"

app1のmanifestを作成

ここからようやくアプリケーションをデプロイすることができる。
まずはdeploymentとserviceとingressのmanifestを用意する。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: app1-deployment
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
        - image: tetsis/simple-nginx-django-app
          name: app1
          ports:
            - containerPort: 8080
          env:
            - name: APPLICATION_NAME
              value: app1
        - image: tetsis/simple-nginx-django-proxy
          name: app1-proxy
          ports:
            - name: app1-port
              containerPort: 80
apiVersion: v1
kind: Service
metadata:
  name: app1-service
spec:
  ports:
    - port: 80
      targetPort: app1-port
      protocol: TCP
  type: NodePort
  selector:
    app: app1
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
  labels:
    app: ingress
spec:
  rules:
    - host: app1.example.com
      http:
        paths:
          - path: /*
            backend:
              serviceName: app1-service
              servicePort: 80

example.comには自分が所有するドメインを指定する。

app1のデプロイ

kubectl apply -f app1-deployment.yaml 
kubectl apply -f app1-service.yaml 
kubectl apply -f ingress.yaml

ALB Ingress Controllerで作られたALBを確認

kubectl get ingress
NAME      HOSTS              ADDRESS                                                                     PORTS   AGE
ingress   app1.example.com   d7f12bb1-default-ingress-e8c7-1478980459.ap-northeast-1.elb.amazonaws.com   80      38s

Route 53にレコードが作られたことを確認

コンソール画面もしくは以下のコマンドでAliasレコードが作成されていることを確認する。

aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/<ホストゾーンID>

ページにアクセスしてみる

ALBの起動とRoute 53のDNSレコード情報が反映されるまで少し待って、ブラウザで「http://app1.example.com」にアクセスし、以下のように表示されればOK。
(example.comは自分のドメインを指定)

This application is "app1".

サブドメイン分割でapp2を追加

さっきはapp1.example.comというドメインでアプリケーションをデプロイしたが、次はapp2.example.comドメインでアプリケーションを追加デプロする。
deploymentとserviceはapp1と同様にmanifestを作成する。
ingressについては 、app1とapp2で同じALBを使うので先程作成したmanifestに追記する。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: app2-deployment
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: app2
    spec:
      containers:
        - image: tetsis/simple-nginx-django-app
          name: app2
          ports:
            - containerPort: 8080
          env:
            - name: APPLICATION_NAME
              value: app2
        - image: tetsis/simple-nginx-django-proxy
          name: app2-proxy
          ports:
            - name: app2-port
              containerPort: 80
apiVersion: v1
kind: Service
metadata:
  name: app2-service
spec:
  ports:
    - port: 80
      targetPort: app2-port
      protocol: TCP
  type: NodePort
  selector:
    app: app2
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
  labels:
    app: ingress
spec:
  rules:
    - host: app1.example.com
      http:
        paths:
          - path: /*
            backend:
              serviceName: app1-service
              servicePort: 80
    - host: app2.example.com
      http:
        paths:
          - path: /*
            backend:
              serviceName: app2-service
              servicePort: 80

app2のデプロイ

kubectl apply -f app2-deployment.yaml 
kubectl apply -f app2-service.yaml 
kubectl apply -f ingress.yaml

ページにアクセスしてみる

ブラウザで「http://app2.example.com」にアクセスして以下の文字が表示されればOK。(もちろんドメインは環境に合わせること)

This application is "app2".

HTTPS化

公式ページを参考に、ingress.yamlの annotations 句に一行追加する。
尚、使用するドメインのSSL証明書は事前にACMで発行しておく。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' # 追加
  labels:
    app: ingress
spec:
  rules:
    - host: app1.example.com
      http:
        paths:
          - path: /*
            backend:
              serviceName: app1-service
              servicePort: 80
    - host: app2.example.com
      http:
        paths:
          - path: /*
            backend:
              serviceName: app2-service
              servicePort: 80

忘れずにapplyする。

kubectl apply -f ingress.yaml

ブラウザで「https://app1.example.com」、 「https://app2.example.com」 にアクセスして、ページが表示されることを確認する。

リソースの削除

EKSクラスタは起動しておくだけでお金がかかるので、検証利用の場合は忘れずにクラスタを削除を実施する。


K8sリソースを削除

kubectl delete -f ingress.yaml
kubectl delete -f app1-service.yaml
kubectl delete -f app2-service.yaml
kubectl delete -f app1-deployment.yaml
kubectl delete -f app2-deployment.yaml

IAMポリシーをデタッチ

aws iam detach-role-policy --policy-arn arn:aws:iam::<アカウントID>:policy/ExternalDNSIAMPolicy --role-name <ロール名>
aws iam detach-role-policy --policy-arn arn:aws:iam::<アカウントID>:policy/ALBIngressControllerIAMPolicy --role-name <ロール名>

IAMポリシーを削除

 aws iam delete-policy --policy-arn arn:aws:iam::<アカウントID>:policy/ExternalDNSIAMPolicy
 aws iam delete-policy --policy-arn arn:aws:iam::<アカウントID>:policy/ALBIngressControllerIAMPolicy

EKSクラスタを削除

eksctl delete cluster --region=ap-northeast-1 --name=test-cluster