最近勉強がてら AWS EKS を使ってKubernetes をさわってます!
もし間違ってるところがあれば @hatappi まで教えていただけると嬉しいです。
今回はどんなことがしたかったか
- ALBを使いたい!
- 2ドメインを使ってホストベースでルーティングしたい
- ALBは自動で作るけど Routes 53 のレコードは手動ですみたいなことはしたくない
- やっぱり今の時代は https でしょ! (ACM)
事前準備
まずは検証環境がないといけないので、クラスターの作成をおこないます。
クラスターは以前書いた記事で使用した eksctl
を使用して作成していきます。
例えばこんな感じでクラスターを作成します。
$ eksctl create cluster --name test --region ap-northeast-1 --nodes 2 --nodes-min 1 --nodes-max 2 --node-type t2.small --version=1.11
これで kubectl
でEKSで構築したクラスターを扱うための準備ができました。
ALB を良い感じに作ってくれるその名もAWS ALB Ingress Controller
CLBを作るのであれば LoadBalancer Service を使えば作ってくれる。
しかし今回やりたいことに書いたようにホストベースでルーティングをしていきたい。
なのでALBを使いたい!!!!
ということで使うのが ↓ AWS ALB Ingress Controller です。
AWS ALB Ingress Controller をpodで起動しておくことで特定のannotationがついた Ingress リソースをみつけるとそのannotationの情報に応じて ALB を作成してくれます。
詳しくは公式のドキュメントを見てください。
ここであえて説明するとすれば AWS ALB Ingress Controller に付与するポリシーをどうするかです。
というものAWS ALB Ingress Controller は ALB の作成をしてくれるので、 公式の例だとこんな感じのポリシーが必要になります。
このポリシーをどこにアタッチするのかを考えないといけないです。
通常?の方法であればノード(EC2)に割り当てられてるロールに対して上記のポリシーをアタッチします。
しかしノードに付与するということはノード配下のpodに対しても同様の権限が与えられてしまいます。
このままいくと神ノードができてしまいますし、今後複数のサービスが混在することを考えるとこのポリシーって外して良いのかみたいなことに迷いそうですよね。。。
ということで使ったのが次に紹介する kube2iam です。
pod に IAM Role を付与できる! その名も kube2iam
神ノードができないための解決策の1つがこの kube2iam です。
これはノードではなく pod に対して権限を付与するので神ノードができることが防ぐことができます。
ポリシーなどもそれぞれの pod に対して付与するので、必要なくなったポリシーを外しやすいのではないでしょうか。
kube2iam の類似プロダクトとしては kiam というものがあります。
これもアプローチとしては kube2iam と同じく pod に対して IAM Role をアタッチします。
なぜ kube2iam を選んだかというと今回クラスタの作成の際に使った eksctl が今後出すバージョンで kube2iam をアドオンとしてサポートするようなことが書いてあったためです。 https://github.com/weaveworks/eksctl/issues/271
というのも kube2iam は使う際にノードに対して↓のようなポリシーを付与する必要があります。
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" ], "Effect": "Allow", "Resource": "*" } ] }
eksctl ではクラスタに紐づくノードの作成も行うのですが、現状は kube2iam をサポートしていないので生成されたノードに後から kube2iam で必要なポリシーをアタッチする必要があります。
eksctl でアドオンとしてサポートされればきっと、この作業も必要がなくなるはずです!
あとkube2iamは割り当てたいロールの信頼関係の Principal にノードを追加してあげる必要があります。
僕はTerraform で作成してます。
demonset は↓な感じで設定しました。
apiVersion: apps/v1 kind: DaemonSet metadata: name: kube2iam namespace: kube-system labels: app: kube2iam spec: selector: matchLabels: name: kube2iam template: metadata: labels: name: kube2iam spec: serviceAccountName: kube2iam hostNetwork: true containers: - image: jtblin/kube2iam:latest name: kube2iam args: - "--auto-discover-base-arn" - "--iptables=true" - "--host-ip=$(HOST_IP)" - "--node=$(NODE_NAME)" - "--host-interface=eni+" env: - name: HOST_IP valueFrom: fieldRef: fieldPath: status.podIP - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName ports: - containerPort: 8181 hostPort: 8181 name: http securityContext: privileged: true
あとは README 通り apply して任意のpodに割り当てたい Role を annotation として定義すればいけるはずです。
template: metadata: labels: app: alb-ingress-controller annotations: iam.amazonaws.com/role: arn:aws:iam::1111:role/hoge/alb-ingress-controller spec: containers:
正しく実装されていることを確認するためにS3にアクセスするようなイメージでテストしてみます。
まずは S3 にアクセスができるようなロールを作成します。
$ trustPolicy=$(cat << EOS | jq { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }, { "Sid": "", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::11111:role/node" }, "Action": "sts:AssumeRole" } ] } EOS ) $ aws iam create-role --role-name s3-test-role --assume-role-policy-document ${trustPolicy} $ aws iam attach-role-policy --role-name s3-test-role --policy-arn "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
このロールを使用してS3のバケット一覧を表示するイメージを次のように定義して apply します。
apiVersion: v1 kind: Pod metadata: name: aws-cli-sample labels: name: aws-cli-sample annotations: iam.amazonaws.com/role: arn:aws:iam::11111111:role/s3-test-role spec: containers: - image: fstab/aws-cli command: - "/home/aws/aws/env/bin/aws" - "s3" - "ls" name: aws-cli-sample
次のコマンドでログを出して実行してうまくkube2iamが動いていれば、S3のバケットのリストが表示されるはずです。
$ kubectl log pod/aws-cli-sample
これで ALB を作る準備ができました。
ただこれだと ALB は作ったはいいけど 自分が使いたいドメインに設定するときは Route 53 を手動もしくは ALB のドメインを使って Terraform なりスクリプトを実行しないといけないです。
それはやりたくなかったので使ったのが次に紹介する external-dns です。
良い感じに各プロパイダのDNSレコードを作ってくれる! その名もexternal-dns
externa-dnsはKubernetes Incubatorの1つです。
条件にあった Ingress や Service リソースが作成された時に AWS であれば AWS Route53, GCP であれば Google CloudDNS で DNS レコードを作成してくれます。
なので ALB Ingress Controller と連携できるように設定すれば ALB が作られてそのドメインを ALIAS とした Route 53 のレコードを作成してくれます!!
最高だ
マニフェストは↓のように定義しました。
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: arn:aws:iam::1111111:role/hoge/external-dns 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
サンプルアプリケーションをデプロイする
最後に今まで説明したものをあわせてアプリケーションをデプロイしていく。
今回は Ruby で書いた簡単な http server を使います。
Dockerfile
FROM ruby:2.6 EXPOSE 8080 COPY server.rb . CMD ruby server.rb
プログラムはこちら
中身はシンプルでアクセスした際にどのホストでのアクセスかを表示するだけ!
require 'webrick' s = WEBrick::HTTPServer.new(Port: 8080) s.mount_proc('/') do |req, res| res.status = 200 res.body = "Hello!!!\nhost is #{req.host}" end Signal.trap('INT') { s.shutdown } s.start
同じものは Docker Hub にあがっています。
https://cloud.docker.com/u/hatappi/repository/docker/hatappi/sample-ruby-http-server
アプリケーションのnamespaceを作成してデプロイまでは次のマニフェストで行いました。
apiVersion: v1 kind: Namespace metadata: name: sample-app --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: sample-app namespace: sample-app spec: replicas: 1 template: metadata: labels: app: sample-app spec: containers: - image: hatappi/sample-ruby-http-server imagePullPolicy: Always name: sample-app ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: sample-app namespace: sample-app spec: ports: - port: 80 targetPort: 8080 protocol: TCP type: NodePort selector: app: sample-app
Ingress はこちら
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: sample-app namespace: sample-app annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/subnets: subnet-000000,subnet-111111,subnet-22222 alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:1111111111:certificate/111111-111111-111111-22222 alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' spec: rules: - host: hoge.example.com http: paths: - path: /* backend: serviceName: ssl-redirect servicePort: use-annotation - path: / backend: serviceName: sample-app servicePort: 80 - host: fuga.example.com http: paths: - path: /* backend: serviceName: ssl-redirect servicePort: use-annotation - path: / backend: serviceName: sample-app servicePort: 80
https化を行うために alb.ingress.kubernetes.io/certificate-arn
にACMのARNを指定します。
ACMは事前にDNS認証を使用して Terraform で作成しました。
resource "aws_acm_certificate" "cert" { domain_name = "example.com" subject_alternative_names = ["*.example.com"] validation_method = "DNS" lifecycle { create_before_destroy = true } } resource "aws_route53_record" "cert_validation" { name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}" type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}" zone_id = "Z111111111" records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"] ttl = 60 }
他には annotation に alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
と記載されてますが、これは http -> https のリダイレクトを行うもので、公式にも記載されています。
これらを apply すれば https://hoge.example.com
にアクセスした際には Hello!!!\nhost is hoge.example.com
、 https://fuga.example.com
にアクセスした際には Hello!!!\nhoge is fuga.example.com
がレスポンスとしてかえってきます。
最後に
今回は EKS 上で ALB を作って Route 53 のレコードを自動生成して https でアクセスできるようにしました〜