Edited at

KubernetesでAWS ALBを自動作成する〜ついでにRoute53 Record Setも

More than 1 year has passed since last update.

kube-ingress-aws-controllerを使います。


kube-ingress-aws-controllerとは

Zalandoが公開している、Kubernetes用のIngress Controllerの一つです。

ZalandoはKubernetes界隈では著名な、ヨーロッパでファッションECをやっている企業です。

Kubernetesコミュニティへの様々な形で貢献していて、今回紹介するkube-aws-ingerss-controllerや先日紹介したexternal-dnsもその一つです。


何かできるのか

KubernetesユーザがAWSを全く触らずとも


  • ALBの自動作成

  • ALBに割り当てるTLS証明書(ACM管理)を自動選択

をしてくれます。


使い方

KubernetesのIngressリソースを普段通りつくります。

kubectl create -f myingress.yaml

すると、1~2分ほどでIngressリソースに書いたホスト名でインターネットからアクセスできるようになります。

open https://myingress.exapmle.com


Ingress Controllerとは

KubernetesのIngressはL7ロードバランサのスペックのようなもので、そのスペックから実際にL7ロードバランサをセットアップするのがIngress Controllerの役割です。


coreos/alb-ingress-controllerとの違い


coreos/alb-ingress-controller


  • Ingressリソース一つに対して、1 ALBをつくります


zalando-incubator/kube-ingress-aws-controller


  • ACM証明書のドメイン一つに対して、ALBを割り当てます

  • 同じドメイン名に対するルートを含むIngressリソースは、一つのALBにまとめられます

  • ALBのターゲットグループにEC2インスタンスを割り当てるところまでしかやってくれない!ので、実際にIngressとして利用するためには他のIngress Controllerを併用する必要があります


kube-ingress-aws-controllerのセットアップ手順


Security Groupの作成

kube-ingress-aws-controllerはALBに割り当てるSecurity Groupまでは自動作成してくれないので、AWSコンソール等で作成します。

kube-ingerss-aws-controllerのドキュメントにはCloudFormationを使った手順がかいてあります。

同等のSecurity Groupをawscliで作る場合は以下のようなコマンドを実行します。

CLUSTER_NAME=...

VPC_ID=vpc-...

aws ec2 create-security-group \
--description ${CLUSTER_NAME}-kube-aws-ingress-controller-alb \
--group-name ${CLUSTER_NAME}-kube-aws-ingress-controller-alb \
--vpc-id $VPC_ID | tee sg.json

SG_ID=$(jq -r '.GroupId' sg.json)

aws ec2 create-tags --resources $SG_ID --tags \
"Key=\"kubernetes.io/cluster/$CLUSTER_NAME\",Value=owned" \
"Key=\"kubernetes:application\",Value=kube-ingress-aws-controller"

aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--ip-permissions '[{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}, {"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}]'

aws ec2 describe-security-groups --group-id $SG_ID

# あとで: 不要になったらクラスタやVPCの削除前に以下のように削除
aws ec2 delete-security-group --group-id $SG_ID


IAMポリシーの割当

kube-ingerss-aws-controllerのドキュメントにIAM Policy Statementの一覧がかいてありますが、要約すると


  • CloudFormationスタックのCRUD権限

  • ACM証明書、VPC、RouteTable、Subnet、Security Group、AutoScalingGroup、EC2 InstanceのGet/List/Describe権限

  • ALBのCRUD権限

が必要です。

kube-awsのcluster.yamlの場合は、以下のように書きます。

worker:

nodePools:
- iam:
policy:
statements:
- effect: Allow
actions:
- "autoscaling:DescribeAutoScalingGroups"
- "autoscaling:AttachLoadBalancers"
- "autoscaling:DetachLoadBalancers"
- "autoscaling:DetachLoadBalancerTargetGroup"
- "autoscaling:AttachLoadBalancerTargetGroups"
- "elasticloadbalancing:AddTags"
- "elasticloadbalancing:DescribeLoadBalancers"
- "elasticloadbalancing:CreateLoadBalancer"
- "elasticloadbalancing:DeleteLoadBalancer"
- "elasticloadbalancing:DescribeListeners"
- "elasticloadbalancing:CreateListener"
- "elasticloadbalancing:DeleteListener"
- "elasticloadbalancing:DescribeTags"
- "elasticloadbalancing:CreateTargetGroup"
- "elasticloadbalancing:DeleteTargetGroup"
- "elasticloadbalancing:DescribeTargetGroups"
- "elasticloadbalancingv2:DescribeTargetGroups"
- "elasticloadbalancingv2:DescribeLoadBalancers"
- "elasticloadbalancingv2:CreateLoadBalancer"
- "elasticloadbalancingv2:DeleteLoadBalancer"
- "elasticloadbalancingv2:DescribeListeners"
- "elasticloadbalancingv2:CreateListener"
- "elasticloadbalancingv2:DeleteListener"
- "elasticloadbalancingv2:DescribeTags"
- "elasticloadbalancingv2:CreateTargetGroup"
- "elasticloadbalancingv2:DeleteTargetGroup"
- "ec2:DescribeInstances"
- "ec2:DescribeSubnets"
- "ec2:DescribeSecurityGroup"
- "ec2:DescribeRouteTables"
- "ec2:DescribeVpcs"
- "acm:ListCertificates"
- "acm:DescribeCertificate"
- "iam:ListServerCertificates"
- "iam:GetServerCertificate"
- "cloudformation:Get*"
- "cloudformation:Describe*"
- "cloudformation:List*"
- "cloudformation:Create*"
- "cloudformation:Delete*"
resources:
- "*"


kube-aws-ingress-controllerをデプロイ

$ kubectl apply -f kube-aws-ingress-controller.yaml


kube-aws-ingress-controller.yaml

apiVersion: apps/v1beta1

kind: Deployment
metadata:
name: kube-ingress-aws-controller
namespace: kube-system
labels:
application: kube-ingress-aws-controller
component: ingress
spec:
replicas: 1
selector:
matchLabels:
application: kube-ingress-aws-controller
component: ingress
template:
metadata:
labels:
application: kube-ingress-aws-controller
component: ingress
spec:
containers:
- name: controller
image: registry.opensource.zalan.do/teapot/kube-ingress-aws-controller:latest
env:
- name: AWS_REGION
value: ap-northeast-1


併用するIngress Controllerをデプロイ

今回はskipperを使ってみます。

apiVersion: extensions/v1beta1

kind: DaemonSet
metadata:
name: skipper-ingress
namespace: kube-system
labels:
component: ingress
spec:
selector:
matchLabels:
application: skipper-ingress
updateStrategy:
type: RollingUpdate
template:
metadata:
name: skipper-ingress
labels:
component: ingress
application: skipper-ingress
spec:
hostNetwork: true
containers:
- name: skipper-ingress
image: registry.opensource.zalan.do/pathfinder/skipper:latest
ports:
- name: ingress-port
containerPort: 9999
hostPort: 9999
args:
- "skipper"
- "-kubernetes"
- "-kubernetes-in-cluster"
- "-address=:9999"
- "-proxy-preserve-host"
- "-serve-host-metrics"
- "-enable-ratelimits"
- "-experimental-upgrade"
- "-metrics-exp-decay-sample"
- "-kubernetes-https-redirect=true"
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 25m
memory: 25Mi
readinessProbe:
httpGet:
path: /kube-system/healthz
port: 9999
initialDelaySeconds: 5
timeoutSeconds: 5


WorkerノードのSecurity Group設定変更

今回はskipperをつかうことにしたので、kube-ingress-aws-controllerが作成したALBからアクセスする先はskipper(がいるEC2インスタンス)になります。

Security GroupへALBからskipperがいるEC2インスタンスへの通信をブロックしたままだとGateway Timeoutになってしまいます。そうならないように、ALB用につくったSGから、WorkerノードのSGへの9999番ポート(kube-ingress-aws-controllerと組み合わせて使うskipperのhostPortに指定した)の通信を許可しましょう。

ALB側SGのOutboundを絞っていないのであれば、Worker側SGのInboundを追加すればOKです。


Ingressリソースの作成

apiVersion: extensions/v1beta1

kind: Ingress
metadata:
name: nginx
spec:
rules:
- host: nginx-ingress.example.com
http:
paths:
- backend:
serviceName: nginx
servicePort: http

---

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
selector:
app: nginx

---

apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80


ログの確認

$ k logs kube-ingress-aws-controller-6bbd8f9d6c-bcqph

2017/12/06 12:33:52 starting /bin/kube-ingress-aws-controller
2017/12/06 12:33:54 controller manifest:
2017/12/06 12:33:54 kubernetes API server:
2017/12/06 12:33:54 Cluster ID: k8s3
2017/12/06 12:33:54 vpc id: vpc-12345678
2017/12/06 12:33:54 instance id: i-07e29f841f676ca00
2017/12/06 12:33:54 auto scaling group name: k8s3-Nodepool1-MMF7MXKI9350-Workers-BZWB5IAV7JW8
2017/12/06 12:33:54 security group id: sg-8368a6fa
2017/12/06 12:33:54 private subnet ids: []
2017/12/06 12:33:54 public subnet ids: [subnet-12345678 subnet-23456789]
2017/12/06 12:33:54 Start polling sleep 30s

30秒経過後、以下のようにCloudFormationスタックが作成される。

2017/12/06 12:34:24 Found 1 ingresses

2017/12/06 12:34:24 Found 0 stacks
2017/12/06 12:34:24 Have 1 models
2017/12/06 12:34:24 creating stack for certificate "arn:aws:acm:ap-northeast-1:myawsaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" / ingress ["kube-system/nginx"]
2017/12/06 12:34:25 stack "arn:aws:cloudformation:ap-northeast-1:myawsaccountid:stack/k8s3-b9dbfe3/caf1f3a0-da81-11e7-9e21-500c28b97482" for certificate "arn:aws:acm:ap-northeast-1:myawsaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" created
2017/12/06 12:34:25 Start polling sleep 30s


作成されたAWSリソースの確認

1〜2分待ってスタックがCREATE_COMPLETE状態になれば成功。

コンソールでCloudFormationスタックのResourcesの内容を見ると、何がつくられたのかがわかる。

つくられるのは以下の4つ。


  • HTTPListener: 80番ポート用のListener

  • HTTPSListener: 443番ポート用のListener

  • LB: ALB

  • TG: kube-ingress-aws-controllerがデプロイされているノードが登録されたTargetGroup


ALB


リスナー

443番ポート用のListenerには、Ingressリソースに書いたドメインに対応するACM証明書が選択されています。

今回は*.example.com用のワイルドカード証明書を事前に用意しておいたのですが、Ingressにnginx-ingress.example.comというホスト名を設定したところ、ちゃんとワイルドカード証明書を探し出してくれました(かしこい)。


ターゲットグループ

kube-aws-ingress-controllerがデプロイされたノードのASGをコンソールでみてみると、ターゲットグループに割り当てられていました。EC2インスタンスを直接TargetGroupに登録していくような方法だとインスタンスが落ちた場合などが怖いですが、ちゃんとしてますね。


Route53 RecordSetの作成

これだけだとALBが作成されただけなので、nginx-ingress.example.comでアクセスできないはずです。

しかし、昨日デプロイしたexternal-dnsがIngressリソースとALBを検知して、勝手にRecordSetをつくってくれていました。


stern_external-dns.log

external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Desired change: CREATE nginx-ingress.example.com A"

external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Desired change: CREATE nginx-ingress.example.com TXT"
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Record in zone example.com. were successfully updated"

ちゃんとALBへのA(lias)レコードを作成してくれていますね。


 インターネットからアクセスしてみる

nginx-ingress.example.comにブラウザからアクセスしてみて、以下のようなnginxのウェルカムページが表示されてば成功です。おつかれさまでした。


まとめ

kube-ingress-aws-controllerを使うと、Kubernetesユーザはkubectl createするだけでALBとRecordSetをよしなにセットアップしてくれます。

ALBの作成・管理やRoute53 RecordSetの作成のためにいちいちインフラエンジニアを呼び出したくない!というようなセルフサービス好きの会社さんでは特に役立つのではないでしょうか?!


トラブルシューティング


unable to get details for instance "i-0346d738155e965d8"

IAMポリシーが足りないときに出るエラーです。

$ k logs kube-ingress-aws-controller-7f7974ff58-6bvv8

2017/12/06 07:11:51 starting /bin/kube-ingress-aws-controller
2017/12/06 07:11:53 unable to get details for instance "i-0346d738155e965d8": NoCredentialProviders: no valid providers in chain. Deprecated.
For verbose messaging see aws.Config.CredentialsChainVerboseErrors


required security group was not found

Security Groupがないか、またはSecurityGroupのタグが間違っているか、EC2インスタンスにkubernetes.io/cluster/クラスタ名=ownedというタグがついていない場合のエラーです。

$ k logs kube-ingress-aws-controller-7f7974ff58-xqgrq

2017/12/06 08:10:40 starting /bin/kube-ingress-aws-controller
2017/12/06 08:10:41 required security group was not found


CloudFormationで「At least two subnets in two different Availability Zones must be specified」

KubernetesのWorkerノードのASGが単一のAZに割り当てられているときのエラー。ALBの仕様で、最低2つのAZが必要。

kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:06 Start polling sleep 30s

kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:36 Found 1 ingresses
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 Found 0 stacks
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 Have 1 models
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 creating stack for certificate "arn:aws:acm:ap-northeast-1:myaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" / ingress ["kube-system/nginx"]
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 stack "arn:aws:cloudformation:ap-northeast-1:myaccountid:certificate:stack/k8s3-b9dbfe3/360e1830-da7d-11e7-99f7-500c596c228e" for certificate "arn:aws:acm:ap-northeast-1:myaccountid:certificate:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" created


instance is missing the "aws:autoscaling:groupName" tag

ASG以外で作ったEC2インスタンスにkube-ingress-aws-controllerがデプロイされてしまったときのエラー。

$ k logs kube-ingress-aws-controller-7f7974ff58-m6ss2

2017/12/06 12:25:59 starting /bin/kube-ingress-aws-controller
2017/12/06 12:25:59 instance is missing the "aws:autoscaling:groupName" tag

kube-aws-ingress-controllerは、デフォルトではASGに設定されたSubnetをALBのSubnetに流用する。

そのためにASGを探すとき、EC2インスタンスについたaws:autoscaling:groupNameというASGが自動的につけてくれるタグをヒントにするため、ASG以外でつくったEC2インスタンスではこのエラーが出てしまう。

Ref: Spot Fleet support · Issue #105 · zalando-incubator/kube-ingress-aws-controller

Issueも出ているが、まだASG以外は対応していない。ワークアラウンドとしては、kube-ingress-aws-controllerのaffinityでASGでつくったノードにだけスケジュールされるようにすることが考えられる。

kube-awsの場合、awsNodeLabels機能をオンにすると、ASGでつくったノードには"kube-aws.coreos.com/autoscalinggroup"というラベルが付与されるので、それを前提にすると以下のようなaffinityをかけばOK。

              affinity:

nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "kube-aws.coreos.com/autoscalinggroup"
operator: "Exists"


504 Gateway Time-out

ALB経由でnginxにアクセスしようとしてこのエラーがかえってきた場合、ALB用につくったセキュリティグループからkube-aws-ingress-controllerが動いているEC2インスタンスへのアクセスを許可できていない可能性があります。

EC2インスタンス側のSGに、ALB用SGからの9999番ポート(kube-ingress-aws-controllerと組み合わせて使うskipperのhostPortに指定した)への通信をを許可するようなInboundルールを追加しましょう。