TerraformでNATインスタンスを管理する
個人のAWS環境でプライベートサブネット構成を検証したいけどNATゲートウェイに毎月3,500円も払えない*1ので,NATインスタンスのTerraformモジュール int128/nat-instance/aws を作りました.主な特徴はこちらです.
- Auto Scaling GroupによるAuto Healingに対応.インスタンスが落ちても自動復旧します.
- スポットインスタンスで低コストを実現.t3a.nanoなら月100円程度で運用できます.
- ENIの付け替えによる固定ソースIPを実現.
使い方
VPCとサブネットを作成するには terraform-aws-modules/vpc/aws モジュールが便利です.VPCとサブネットに加えてNATインスタンスを作成するには下記のように定義します.
module "vpc" { source = "terraform-aws-modules/vpc/aws" name = "main" cidr = "172.18.0.0/16" azs = ["us-west-2a", "us-west-2b", "us-west-2c"] private_subnets = ["172.18.64.0/20", "172.18.80.0/20", "172.18.96.0/20"] public_subnets = ["172.18.128.0/20", "172.18.144.0/20", "172.18.160.0/20"] enable_dns_hostnames = true } module "nat" { source = "int128/nat-instance/aws" name = "main" vpc_id = module.vpc.vpc_id public_subnet = module.vpc.public_subnets[0] private_subnets_cidr_blocks = module.vpc.private_subnets_cidr_blocks private_route_table_ids = module.vpc.private_route_table_ids }
Terraformを実行すると,下図のようなリソースが作成されます.(プライベートサブネットの各EC2インスタンスは除く)
動作確認のため,AWSコンソールでプライベートサブネットにEC2インスタンスを作成しましょう.作成したEC2インスタンスにSSMセッションマネージャでログインして,外部にアクセスできるか確認してみましょう.
仕組み
Terraformを実行すると,Auto Scaling Groupとそれに必要なリソース群(セキュリティグループ,ENI,EIPなど)が作成されます.また,プライベートサブネットのデフォルトルートがENIに設定されます.これにより,プライベートサブネットから外部への通信はENIを通るようになります.
Auto Scaling GroupがNATインスタンスを起動すると,User Dataにある以下のスクリプトが実行されます.
- あらかじめ確保しておいたENIをeth1にアタッチする.
- IP forwardingのカーネルパラメータを有効にする.
- iptablesでIP masqueradeを有効にする.
- デフォルトゲートウェイをeth1に変更する.
これにより,プライベートサブネット内のEC2インスタンスはENIとNATインスタンスを経由して通信できるようになります.
もしNATインスタンスが停止した場合は,Auto Scaling Groupによって新しいNATインスタンスが作成されます.既存のENIとEIPをアタッチするため,外部通信のソースIPを固定できます.
User DataでENIのアタッチとデフォルトゲートウェイを変更する部分は試行錯誤が必要でかなり苦労しました.
おまけ
マネージドサービスとは逆行する使い方になりますが,iptablesでDNATを有効にするとNATインスタンスのポートをプライベートサブネットに転送できます.例えば,以下のようなスクリプトを追加すれば,NATインスタンスの443ポートをプライベートサブネットにあるEC2インスタンスに転送できます.NLBに課金する代わりにどうぞ.
# Look up the target instance tag_name="TARGET_TAG" target_private_ip="$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$tag_name" | jq -r .Reservations[0].Instances[0].PrivateIpAddress)" # Expose the port of the NAT instance. iptables -t nat -A PREROUTING -m tcp -p tcp --dst "${eni_private_ip}" --dport 8080 -j DNAT --to-destination "$target_private_ip:8080"
Terraformモジュールの詳細については下記を参照してください.
Argo CDでGitLab SSOを利用する
Argo CDは自前でユーザ管理の仕組みを持たず*1,外部のIdentity Providerに認証を移譲するという設計思想になっています.Argo CDのHelm chartにはDexがバンドルされており,様々なIdentity Providerと連携させることが可能です.本稿では,Argo CDでGitLab SSOを利用する方法を紹介します.
Argo CDはOpenID Connectに対応しています.また,GitLabは自身がOpenID Connect Identity Providerになることが可能です.しかし,Argo CDが必要とする groups
claimをGitLabがサポートしていないため,直接連携させることはできません.Argo CDのoidc configでGitLabを指定すると,GitLabで以下のエラーが表示されます(argo-cd#1195).
The requested scope is invalid, unknown, or malformed
代わりに,DexのGitLab connectorを利用することで,Argo CD→Dex→GitLabの流れで連携が可能です.
GitLabの設定
admin area→Applicationsもしくはユーザの設定→Applicationsから,新しいアプリケーションを作成します.以下のように設定します.
- Name: 任意
- Redirect URI:
https://argocd.example.com/api/dex/callback
- Scopes:
read_user
,openid
Dex GitLab connectorではOAuth2を利用するとの記述がありますが,実際はOpenID Connectが利用されるようです.openid
スコープを許可しないとエラーになります.
Argo CDの設定
Helmfileを利用する場合は以下のように設定します.
repositories: - name: argo url: https://argoproj.github.io/argo-helm releases: - name: argocd namespace: argocd chart: argo/argo-cd values: - config: url: https://argocd.example.com dexConfig: connectors: # GitLab SSO - type: gitlab id: gitlab name: GitLab config: baseURL: https://gitlab.com clientID: xxxxxxxx clientSecret: xxxxxxxx rbac: # assign the admin role to everyone policyDefault: role:admin
上記ではすべてのユーザにadminロールを割り当てていますが,GitLabグループに応じてアクセス制御を行うとよいでしょう.
動作確認
下図のように LOGIN VIA GITLAB ボタンが表示されるはずです.ボタンをクリックし,GitLabの同意画面が表示された後,ログインできれば成功です.
See Also
- Argo CD and Dex
- GitLab SSO
*1:ローカルでは組み込みのadminユーザのみ利用できます.
AWS Cluster AutoscalerをTerraformとHelmfileでデプロイする
Cluster Autoscalerを利用すると,CPUやメモリの要求量に応じてノード数を自動的に増減させることが可能です.
本稿では,Amazon EKSで以下を利用する方法を紹介します.
Cluster AutoscalerのPodにIAMロールを割り当てるため,あらかじめkube2iamもしくはkiamをデプロイしておく必要があります.詳しくは下記を参照してください.
(2020/9/23 追記)Cluster AutoscalerのHelm chartはgithub.com/kubernetes/autoscalerに移動しました。また、権限の割り当てはIAM Roles for Service Accountsが推奨されています。
Worker Auto Scaling Groupの修正
Auto Scaling GroupにAuto Discovery用のタグを追加します.
terraform-aws-eks module を利用している場合は以下のように autoscaling_enabled = true
を追加すればよいです.詳しくはモジュールの Autoscaling を参照してください.
worker_groups_launch_template_mixed = [ { # 中略 autoscaling_enabled = true asg_min_size = 1 asg_max_size = 8 asg_desired_capacity = 4 }, ]
kopsを利用している場合は kops edit ig nodes
で以下のようにノード用のタグを追加します.
cloudLabels: k8s.io/cluster-autoscaler/enabled: 1 k8s.io/cluster-autoscaler/CLUSTER_NAME: 1
IAM Roleの作成
Cluster AutoscalerがAuto Scaling Groupを取得したり変更したりするためのIAM Roleを作成します.必要なIAMポリシーは cluster-autoscaler/cloudprovider/aws に記載されています.
# IAM role for Cluster Autoscaler resource "aws_iam_role" "cluster_autoscaler" { name = "${var.cluster_name}-cluster-autoscaler" assume_role_policy = data.aws_iam_policy_document.kube2iam_assume_role.json } resource "aws_iam_role_policy" "cluster_autoscaler" { role = aws_iam_role.cluster_autoscaler.name policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeAutoScalingInstances", "autoscaling:DescribeLaunchConfigurations", "autoscaling:DescribeTags", "autoscaling:SetDesiredCapacity", "autoscaling:TerminateInstanceInAutoScalingGroup" ], "Resource": "*" } ] } EOF } data "aws_iam_policy_document" "kube2iam_assume_role" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } principals { type = "AWS" identifiers = [module.eks.worker_iam_role_arn] } } }
Helm chartのデプロイ
stable/cluster-autoscaler chart をデプロイします.
releases: # https://github.com/helm/charts/tree/master/stable/cluster-autoscaler - name: cluster-autoscaler namespace: kube-system chart: stable/cluster-autoscaler values: - cloudProvider: aws awsRegion: ap-northeast-1 autoDiscovery: clusterName: {{ .Environment.Values.clusterName }} rbac: create: true podAnnotations: # IAM role for Cluster Autoscaler iam.amazonaws.com/role: {{ .Environment.Values.clusterName }}-cluster-autoscaler # https://github.com/helm/charts/tree/master/stable/metrics-server - name: metrics-server namespace: kube-system chart: stable/metrics-server environments: default: values: - clusterName: CLUSTER_NAME
Cluster Autoscalerのログにエラーが出ていないことを確認します.IAMロールやIAMポリシーが不適切な場合はエラーが出ます.
スケールアップの動作確認
ここでは以下のように echoserver をデプロイします.
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: echoserver namespace: echoserver spec: replicas: 1 template: metadata: labels: app: echoserver spec: containers: - image: gcr.io/google_containers/echoserver:1.10 name: echoserver ports: - containerPort: 8080
ここで,以下のようにCPU requestを設定します.1ノード2コアの場合は1500mにするとよいでしょう.要求値が大きすぎると,Cluster Autoscalerはノードを追加してもスケジュール不可能と判断してしまいます.
spec: template: spec: containers: - image: gcr.io/google_containers/echoserver:1.10 name: echoserver ports: - containerPort: 8080 resources: requests: cpu: 1500m
Cluster Autoscalerのログを確認します.以下のように Upcoming 1 nodes
と表示されると成功です.同時に,Auto Scaling GroupのDesired Capacityが書き換わっていることを確認します.しばらく経つと,ノードが増えてPodがスケジュールされるはずです.
I0927 11:50:35.158353 1 scale_up.go:263] Pod echoserver/echoserver-74fd7d865f-vkzqb is unschedulable I0927 11:50:35.158391 1 scale_up.go:300] Upcoming 0 nodes I0927 11:50:35.158521 1 scale_up.go:423] Best option to resize: ASG_NAME I0927 11:50:35.158540 1 scale_up.go:427] Estimated 1 nodes needed in ASG_NAME I0927 11:50:35.158556 1 scale_up.go:529] Final scale-up plan: [{ASG_NAME 4->5 (max: 8)}] I0927 11:50:35.158572 1 scale_up.go:694] Scale-up: setting group ASG_NAME size to 5 I0927 11:50:45.523906 1 scale_up.go:263] Pod echoserver/echoserver-74fd7d865f-vkzqb is unschedulable I0927 11:50:45.524041 1 scale_up.go:300] Upcoming 1 nodes I0927 11:52:36.144782 1 clusterstate.go:194] Scale up in group ASG_NAME finished successfully in 2m0.794268739s
スケールダウンの動作確認
前のセクションで設定したCPU requestを削除します.
デフォルトでは,Cluster Autoscalerがノードが必要ないと判断してから10分後に実際にノードを削除します(詳しくは How does scale-down work? を参照してください).Cluster Autoscalerのログを確認し,以下のように Terminating EC2 instance
が表示されると成功です.
I0927 11:57:07.790306 1 scale_down.go:407] Node ip-172-19-67-52.ap-northeast-1.compute.internal - utilization 0.055000 I0927 11:57:07.790634 1 static_autoscaler.go:359] ip-172-19-67-52.ap-northeast-1.compute.internal is unneeded since 2019-09-27 11:57:07.773690521 +0000 UTC m=+2997.491422805 duration 0s I0927 12:07:12.161679 1 static_autoscaler.go:359] ip-172-19-67-52.ap-northeast-1.compute.internal is unneeded since 2019-09-27 11:57:07.773690521 +0000 UTC m=+2997.491422805 duration 10m4.367847963s I0927 12:07:12.161767 1 scale_down.go:600] ip-172-19-67-52.ap-northeast-1.compute.internal was unneeded for 10m4.367847963s I0927 12:07:12.161868 1 scale_down.go:819] Scale-down: removing empty node ip-172-19-67-52.ap-northeast-1.compute.internal I0927 12:07:12.176857 1 delete.go:64] Successfully added toBeDeletedTaint on node ip-172-19-67-52.ap-northeast-1.compute.internal I0927 12:07:12.391908 1 auto_scaling_groups.go:269] Terminating EC2 instance: i-066bc60549f083e38
最後に,Auto Scaling Groupのdesired capacityが元に戻っていることを確認します.