こんにちは、SREチームの小川です。
「夏への扉」を読みました。過去・現在・未来の時間軸が最後にかち合った時には感動を覚えました。月並みですが、さすがSFの金字塔ですね。
今回は「外資就活への扉」である、ALB・Route53をSREがどのように管理しているか紹介したいと思います!
はじめに
以前よりSREチームが「外資就活ドットコム」のインフラをEC2中心のものから、コンテナ中心のEKSへと移行したお話をしてきました。
今回はKubernetesを導入した際に、トラフィックの流入元であるALBをAWS Load Balancer Controller
とExternalDNS
を利用して管理を行うようにしたお話しをします。
EKSのサービスをALBを利用して公開する際に、アプリケーションコンテナをNodePortで公開し、ALBがNodeGroupの特定のNodePortにリバースプロキシするなどがあります。
一方AWS Load Balancer Controller
を利用すると、Ingressリソースを元に作成されたALBは、アプリケーションコンテナのClusterIPを解決してリバースプロキシすることができるようになります。
このように不用意にノードのポートをクラスタ外に公開する必要がなくなるため、AWS Load Balancer Controller
を採用しました。
またExternalDNS
を併せて利用することで、特定のIngressリソースから作成されたALBのルールを元に、自動でRoute53にレコードを登録することができます。
これにより、Ingressのrules
パラメータを設定するだけで、公開したサービスを名前解決することができるようになります。
AWS Load Balancer Controllerのデプロイ
AWS Load Balancer Controller
を利用してALBを作成するためには
- SecurityGroup
- IAMロール
- aws-load-balancer-controller
- ingress
の定義をする必要があります。
このうち、1・2はTerraformで構成管理し、3・4はK8sのマニフェストで管理しています。
3・4を作成する際には1・2のARNなどを埋め込んで読み込む形になっています。
SG, IAMロール
SGは適宜インターネットなどに公開したいポートをオープンにしたものを定義します。公式ドキュメントに酷似した定義を利用しているため割愛します。
IAMロールは以下の通り定義します。
Controllerの利用するServiceAccountとIAMロールをIRSAで関連づけることを目的としたリソースとなっています。
AssumeRoleのポリシーをFederated
にすることで、EKSのOIDC ID プロバイダ
とIAMロールを関連づけています。
resource "aws_iam_openid_connect_provider" "eks_oidc" { url = var.eks_oidc client_id_list = [ "sts.amazonaws.com" ] thumbprint_list = [data.tls_certificate.eks_oidc.certificates[0].sha1_fingerprint] } data "http" "alb_management" { url = format( "https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/%s/docs/install/iam_policy.json", var.alb_version ) } data "aws_iam_policy_document" "alb_management" { statement { effect = "Allow" principals { identifiers = [aws_iam_openid_connect_provider.eks_oidc.arn] type = "Federated" } actions = ["sts:AssumeRoleWithWebIdentity"] condition { test = "StringEquals" variable = format("%s:sub", replace(aws_iam_openid_connect_provider.eks_oidc.url, "https://", "")) values = [ "system:serviceaccount:kube-system:aws-load-balancer-controller" ] } } } resource "aws_iam_role" "alb_management" { name = format("%s-%s-alb-management", var.env, var.project) assume_role_policy = data.aws_iam_policy_document.alb_management.json } resource "aws_iam_role_policy_attachment" "alb_management" { policy_arn = aws_iam_policy.alb_management.arn role = aws_iam_role.alb_management.name } resource "aws_iam_policy" "alb_management" { name = format("%s-%s-alb-management", var.env, var.project) policy = data.http.alb_management.body }
aws_iam_openid_connect_provider
のurlに渡す値は下記のように取得可能です。
output "eks_oidc" { value = aws_eks_cluster.cluster.identity[0].oidc[0].issuer }
aws-load-balancer-controller
外資就活ドットコムではK8sリソースのデプロイ管理にArgo CDを利用しています。
aws-load-balancer-controllerについてもArgoCDを利用してデプロイしています。
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: aws-load-balancer-controller spec: project: gaishishukatsu source: repoURL: マニフェストのGitHubリポジトリURL targetRevision: main path: alb/overlays/x destination: server: https://kubernetes.default.svc namespace: kube-system syncPolicy: automated: prune: true allowEmpty: true retry: limit: 0 backoff: duration: 10s factor: 1 --- apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: cert-manager spec: project: gaishishukatsu source: repoURL: マニフェストのGitHubリポジトリURL targetRevision: main path: cert_manager/overlays/x destination: server: https://kubernetes.default.svc namespace: cert-manager syncPolicy: automated: prune: true allowEmpty: true retry: limit: 0 backoff: duration: 10s factor: 1
ArgoCDがrepoURLからポーリングするaws-load-balancer-controller
のマニフェストは、Kustomizeを利用して下記のように定義しています。
Terraformで作成したIAMロールのARNと、EKSの所属するVPCのIDをそれぞれ埋め込みます。
ここまでで作成したリソースがデプロイされると、EKSのIngressリソースを作成するだけでALB経由でサービスを公開する準備が整います。
# base/kustomization.yaml bases: - https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/${controllerのversion}/docs/install/${controllerのversion}full.yaml # overlays/x/deployment-patch.yaml apiVersion: apps/v1 kind: Deployment metadata: name: aws-load-balancer-controller namespace: kube-system spec: template: spec: containers: - name: controller args: - --cluster-name=cluster - --aws-region=ap-northeast-1 - --aws-vpc-id=EKSの所属するVPCのID env: - name: AWS_DEFAULT_REGION value: ap-northeast-1 - name: AWS_ROLE_ARN value: Terraformで作成したIAMロールのARN - name: AWS_WEB_IDENTITY_TOKEN_FILE value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token # overlays/x/service-account-patch.yaml apiVersion: v1 kind: ServiceAccount metadata: name: aws-load-balancer-controller namespace: kube-system annotations: eks.amazonaws.com/role-arn: Terraformで作成したIAMロールのARN # overlays/x/kustomization.yaml resources: - ../../base patchesStrategicMerge: - service-account-patch.yaml - deployment-patch.yaml
ingress
下記のようなIngressを作成することで、ALBが80,443番で公開され、K8sのapp-svc
というClusterIPへとリバースプロキシすることができます。
他にもalb.ingress.kubernetes.io/load-balancer-attributes
というアノテーションを設定することで、別途Terraformで作成したS3へALBのログを保存することや、alb.ingress.kubernetes.io/wafv2-acl-arn
でWAFを設定することもできます。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: app annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/group.name: "public" alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/security-groups: Terraformで作成したSGのID alb.ingress.kubernetes.io/certificate-arn: HTTPSの場合にはTerraformで作成したACMのARN alb.ingress.kubernetes.io/actions.app: "{\"type\":\"forward\",\"forwardConfig\":{\"targetGroups\":[{\"serviceName\":\"app-svc\",\"servicePort\":80}]}}" spec: rules: - host: app.gaishishukatsu.com http: paths: - backend: service: name: app port: name: use-annotation pathType: ImplementationSpecific --- apiVersion: v1 kind: Service metadata: name: app-svc annotations: alb.ingress.kubernetes.io/healthcheck-protocol: HTTP alb.ingress.kubernetes.io/healthcheck-port: traffic-port alb.ingress.kubernetes.io/healthcheck-path: /healthz alb.ingress.kubernetes.io/healthy-threshold-count: '5' alb.ingress.kubernetes.io/unhealthy-threshold-count: '2' alb.ingress.kubernetes.io/healthcheck-interval-seconds: '300' alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5' alb.ingress.kubernetes.io/success-codes: '200' spec: type: ClusterIP ports: - name: "http" protocol: "TCP" port: 80 targetPort: 80 selector: app: app
ExternalDNS
ここまででALBを作成することができました。
ホストベース・パスベースのルールを増やしたい場合には、Ingressを修正することでALBのルーティングを操作することができます。
ここからはExternalDNS
を利用して、作成したIngressからRoute53のレコードを登録するためのマニフェストの管理を紹介していきます。
IAMロール
本来はIRSAを利用したいですが、EKSノードにRoute53を操作可能なIAMロールをアタッチしています。
data "aws_iam_policy_document" "eks_node_route53_policy" { statement { effect = "Allow" actions = [ "route53:ChangeResourceRecordSets", "route53:ListHostedZones", "route53:ListResourceRecordSets" ] resources = ["*"] } }
external-dns
ArgoCDとKustomizeを利用してデプロイしています。
--annotation-filter
で先ほど作成したIngressのアノテーションalb.ingress.kubernetes.io/group.name
を指定することで、特定のALBのルールを管理することができます。
policyは3段階あり、upsert-only
ではレコードの新規作成と上書きができ、レコードの削除は行わないようになっています。開発環境などではsync
にすることで削除も行うことが可能となるため、検証がスムーズに進みます。Kustomizeを利用することでこの辺の環境差分を管理することが可能です。
このマニフェストのデプロイに成功することで、gaishishukatsu.com
というホストゾーンに
レコード | Type | 値 |
---|---|---|
app.gaishishukatsu.com | A (エイリアス) | ALBのDNS名 |
というレコードを作成することができます。これで本エントリーの目的である、ALBの公開とDNSでの名前解決が達成されました。
# base/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: external-dns spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:tag # overlays/x/deployment-patch.yaml apiVersion: apps/v1 kind: Deployment metadata: name: external-dns spec: template: spec: containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:tag args: - --domain-filter=gaishishukatsu.com - --annotation-filter=alb.ingress.kubernetes.io/group.name in (public) - --provider=aws - --policy=upsert-only - --source=service - --source=ingress
また、外資就活ドットコムはインターネットに公開していますが、バックオフィス向けのサービスなどについてはVPNからのアクセスしか許可しない体制をとっています。
aws-load-balancer-controller
とexternal-dns
を利用している場合には、Ingressを追加しalb.ingress.kubernetes.io/group.name: "vpn"
など、別のグループを指定することで複数のALBを管理することができます。
そしてexternal-dns
側ではannotation-filter
を別途作成したIngressのアノテーションにし、argsに--aws-zone-type=private
を追加することで、プライベートなDNSレコードを利用した名前解決により、閉ざされたネットワークでサービスを公開することができます。
さいごに
ここまでが外資就活ドットコムのトラフィックの入り口を、SREチームでどのように管理しているか紹介でした。
この構成を採用したことで、マニフェストの管理のみでL7のルーティングを管理することが実現できたため、運用の負荷を非常に低く抑えることができています。
また紹介した通り、ALBを複数管理したり、ルーティングのルールを細かく設定することもできるため、新規にサービスを公開するための実装コストも低いです。
他にもexternal-secrets + Secrets Manger
であったり、ArgoCD + ArgoCD Notifications
など、K8sのエコシステムを活用した取り組みを行なっています。
こういった挑戦できる機会が外資就活にはあります。「これを使ってもっと良くしたい」とか「あそこはこうしなきゃ...」など改善していきたいと考えています。一緒に取り組んでくれるエンジニアの方、募集しております!