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-filterHostedZoneに対応するドメイン名に置き換えます。
--providerAWSを使うのでAWSで大丈夫です。他にもGoogleやAzureなどが指定できるようです。
--policysyncにすると削除も反映されます。作成のみにしたい場合はupsert-onlyにしてください。
--registrytxtレコードの登録を行います。
--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
コメント
コメントを投稿