LoadBalancer型 Service (type: LoadBalancer
) は、Pod群にアクセスするための ELB を自動的に作ってくれて便利なのだが、ELB に関する全ての設定をサポートしているわけではなく、Service を作り直す度に、k8s の外側でカスタマイズした内容もやり直さなければならないのはつらい。というわけで、type: LoadBalancer
を利用するのは止めて、ELB は Terraform で管理し、そこから NodePort型 Service に接続する方法を試してみた。
Kubernetes がサポートする ELB 設定
「ELB に関する全ての設定をサポートしているわけではなく」と書いたが、今現在どれぐらいサポートされているのだろうか? 改めて調べてみた。
Service 定義の metadata.annotations
に、以下の値を書くことで ELB の設定を行うことが出来る (v1.5現在)。
- Backend protocol
- TCP – default
- HTTP –
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
- HTTPS –
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https
- SSL Certificate
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "[cert-arn]"
- SSL Port
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
- The SSL certificate will only be used for 443 and not 80.
- Internal ELB
service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
- Security group
service.beta.kubernetes.io/load-balancer-source-ranges: [a comma separated list of CIDRs]
- Idle timeout
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: [seconds]
- Access logs
- Enabled –
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: [true|false]
- Emit interval –
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: [minutes]
- s3://bucket/prefix
- S3 bucket –
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: [bucket-name]
- S3 prefix –
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: [prefix]
- S3 bucket –
- Enabled –
- Cross-Zone Load Balancing
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: [true|false]
- Connection draining
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: [true|false]
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: [seconds]
- Proxy protocal
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
- Route53
こうやってまとめてみると、v1.5 においては、ELB の設定項目はほとんど網羅されてるような印象を受ける。Route53 についても Third party の拡張を使えばなんとかなるようなので、ほとんどのケースではわざわざ LoadBalancer を別管理にする必要はないのかもしれない。
それでもあえて、LoadBalancer を k8s の外側で管理する理由があるとすれば、
- k8s に問題が起きた時に、通常の EC2方式に戻せるようにしておきたい。
- Application Load Balancer (ALB) を使いたい。
- ELB に分かりやすい名前を付けたい。
- k8s側から作ると
a5902e609eed711e69a1986001d7b1fb
みたいなランダムな名前になる。 - Tag
kubernetes.io/service-name
みれば、どの Service のものかは分かるのだけど。
- k8s側から作ると
- Cloudwatch Alarm を Terraform で管理したい。
- ELB に Cloudwatch Alarm を設定する場合は、ELB も Terraform で管理しておいた方がやりやすい。
- k8s の LoadBalancer 管理に一抹の不安がある。
ぐらいだろうか。
移行手順
AWS 上の Kubernetes クラスタが kops で構築されていることを前提に、ELB を Terraform で 管理する運用に移行してみる。
- クラスタの名前を仮に
k8s.example.com
とする。
1. Terraform ELB から k8sノードへアクセス出来るように追加のセキュリティグループを作る
k8sノード用とELB用の二つのセキュリティグループを作る。
# For nodes resource "aws_security_group" "nodes" { vpc_id = "${module.environment.vpc_id}" name = "additional.nodes.k8s.example.com" description = "Additional security group for nodes" tags { Name = "additional.nodes.k8s.example.com" } # ELB から NodePort経由のアクセスを受け付ける ingress { from_port = 0 to_port = 0 protocol = "-1" security_groups = ["${aws_security_group.service_elb.id}"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } # For ELB resource "aws_security_group" "service_elb" { vpc_id = "${module.environment.vpc_id}" name = "k8s-service-elb" description = "Security group for k8s service ELBs" tags { Name = "k8s-service-elb" } ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
kops 1.5.x 以降では、マスターあるいはノードに対して、デフォルトのセキュリティグループの他に、独自のセキュリティグループを追加することができる。
- Attach additional security group rules to node security group? · Issue #1628 · kubernetes/kops
上で作ったセキュリティグループ(additional.nodes.k8s.example.com
)が、ノード起動時に適用されるようにクラスタの設定を更新する。
$ kops edit ig nodes ... spec: additionalSecurityGroups: - sg-xxxxxxxx # additional.nodes.k8s.example.com ... $ kops update cluster k8s.example.com --yes
Sticky session を必要としない場合
2. Service を NodePort
型で立ち上げる
通常 NodePort の番号は自動で決定されるが、Terraform ELB から接続できるように固定の番号を設定しておく。NodePort に設定出来る番号の範囲は 30000-32767
。
apiVersion: v1 kind: Service metadata: name: example-app spec: selector: app: example-app type: NodePort ports: - protocol: TCP port: 80 targetPort: 80 nodePort: 30000
3. Terraform で ELB を立ち上げる
instance_port
とhealth_check
のtarget
に NodePort を指定する。instances
は設定しない(後ほど Auto Scaling group と紐付けるため)。- k8sノードへアクセス出来るように、先ほど作ったセキュリティグループ(
k8s-service-elb
)を設定する。
resource "aws_elb" "example_app" { name = "k8s-example-app" cross_zone_load_balancing = true subnets = ["${split(",", module.environment.subnets)}"] security_groups = ["sg-xxxxxxxx"] # k8s-service-elb listener { instance_port = 30000 instance_protocol = "http" lb_port = 80 lb_protocol = "http" } listener { instance_port = 30000 instance_protocol = "http" lb_port = 443 lb_protocol = "https" ssl_certificate_id = "${module.environment.ssl_certificate_id}" } health_check { healthy_threshold = 3 unhealthy_threshold = 2 timeout = 5 target = "HTTP:30000/healthcheck" interval = 30 } }
4. ELB にドメイン名を設定する
サービスのドメイン名が、たった今作った ELB を参照するように Route53 を設定する。
resource "aws_route53_record" "example_app" { zone_id = "${module.environment.zone_id}" name = "app.example.com" type = "CNAME" ttl = "300" records = ["<elb-dns-name>"] }
5. ELB をノードの Auto Scaling Group に紐付ける
3 で立ち上げた ELB を、k8sノードの Auto Scaling Group (nodes.k8s.example.com
) の Load Balancers
に追加する。これによりk8sノードがELBに追加される。
6. 全ての k8sノードが ELB に追加されて、InService
になることを確認する
サービスのURLにアクセスして、サービスが問題なく稼動していることを確認する。
Sticky session を必要とする場合
アプリケーションが sticky session を必要とする場合は、予め ingress controller をクラスタにインストールしておき、そこ経由でサービスにアクセスさせる。なので、このケースではアプリケーション用の ELB は立てない。
参考: Kubernetes on AWS で sticky session を実現する | ゆびてく
今回の移行では、ingress controller の入り口となる Service を NodePort
で作っておいて、そこにアクセスする ELB を Terraform で作った。
2. Service を ClusterIP
型で立ち上げる
apiVersion: v1 kind: Service metadata: name: example-app spec: selector: app: example-app type: ClusterIP ports: - protocol: TCP port: 80 targetPort: 80
3. Ingress controller にドメイン名を追加する
サービスのドメイン名が ingress controllerの ELB を参照するように Route53 を設定する。
resource "aws_route53_record" "example_app" { zone_id = "${module.environment.zone_id}" name = "app.example.com" type = "CNAME" ttl = "300" records = ["<ingress-controller-elb-dns-name>"] }
4. Ingress を追加する
Ingress controller から対象のサービスにアクセス出来るように ingress rule を追加する。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: sticky-session-ingress spec: rules: - host: app.example.com http: paths: - path: / backend: serviceName: example-app servicePort: 80
5. サービスが問題なく稼動していることを確認する
サービスのURLにアクセスして、サービスが問題なく稼動していることを確認する。
“Kubernetes on AWS: LoadBalancer型 Service との決別” への 1 件のフィードバック