オンプレミスでCircleCI Serverを動かすシリーズの第5回、今回で最終回となります。前回はNomadクライアントを構築し、ジョブの実行を可能にしました。今回はさらにVMサービスを構築し、Machine ExecutorやRemote Dockerの実行を可能にしていきます。
セキュリティグループの作成
作成されるEC2ノードに紐づける、セキュリティグループを以下のコマンドで作成します。vpc-id
は、前回取得したクラスターのVPC IDに書き換えてください。コマンドが成功すると、作成されたセキュリティグループのIDが返されます。この値を控えておいてください。
$ aws ec2 create-security-group --vpc-id (VPC ID) --description "CircleCI VM Service security group" --group-name "circleci-vm-service-sg" { "GroupId": "sg-xxxxxxxx" }
セキュリティグループにルールを追加します。まず以下のコマンドで、クラスターが所属するVPCのCIDRブロックを調べます。前回Terraformに設定したものと同じですので、本ブログ通りの手順で構築していれば、10.21.0.0/16
となっているはずです。
$ aws ec2 describe-vpcs --filters Name=vpc-id,Values=(VPC ID) --query 'Vpcs[0].CidrBlock' "10.21.0.0/16"
上記で得られたCIDRブロックから、TCPの22番ポートと2376番ポートへのアクセスを許可します。またCircleCIでは、SSHを利用したデバッグ機能が用意されています。そこでVMにSSH接続を可能とするため、TCPの54782番ポートをPublicに対して開放する必要があります*1。
$ aws ec2 authorize-security-group-ingress --group-id (セキュリティグループID) --protocol tcp --port 22 --cidr 10.21.0.0/16 $ aws ec2 authorize-security-group-ingress --group-id (セキュリティグループID) --protocol tcp --port 2376 --cidr 10.21.0.0/16 $ aws ec2 authorize-security-group-ingress --group-id (セキュリティグループID) --protocol tcp --port 54782 --cidr 0.0.0.0/0
VMサービス用IAMユーザーの作成
S3バケットアクセス用のユーザーを作成した時と同様に、EC2インスタンス起動用のIAMユーザーを作成します。まずは以下のコマンドでユーザーを作成します。
$ aws iam create-user --user-name circleci-vm-service
以下の内容でvmpolicy.json
というファイルを作成します。セキュリティグループのIDは上で作成したものに、VPC IDはクラスターのVPCのIDに書き換えてください。
{ "Version": "2012-10-17", "Statement": [ { "Action": "ec2:RunInstances", "Effect": "Allow", "Resource": [ "arn:aws:ec2:*::image/*", "arn:aws:ec2:*::snapshot/*", "arn:aws:ec2:*:*:key-pair/*", "arn:aws:ec2:*:*:launch-template/*", "arn:aws:ec2:*:*:network-interface/*", "arn:aws:ec2:*:*:placement-group/*", "arn:aws:ec2:*:*:volume/*", "arn:aws:ec2:*:*:subnet/*", "arn:aws:ec2:*:*:security-group/セキュリティグループID" ] }, { "Action": "ec2:RunInstances", "Effect": "Allow", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringEquals": { "aws:RequestTag/ManagedBy": "circleci-vm-service" } } }, { "Action": [ "ec2:CreateVolume" ], "Effect": "Allow", "Resource": [ "arn:aws:ec2:*:*:volume/*" ], "Condition": { "StringEquals": { "aws:RequestTag/ManagedBy": "circleci-vm-service" } } }, { "Action": [ "ec2:Describe*" ], "Effect": "Allow", "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": "arn:aws:ec2:*:*:*/*", "Condition": { "StringEquals": { "ec2:CreateAction" : "CreateVolume" } } }, { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": "arn:aws:ec2:*:*:*/*", "Condition": { "StringEquals": { "ec2:CreateAction" : "RunInstances" } } }, { "Action": [ "ec2:CreateTags", "ec2:StartInstances", "ec2:StopInstances", "ec2:TerminateInstances", "ec2:AttachVolume", "ec2:DetachVolume", "ec2:DeleteVolume" ], "Effect": "Allow", "Resource": "arn:aws:ec2:*:*:*/*", "Condition": { "StringEquals": { "ec2:ResourceTag/ManagedBy": "circleci-vm-service" } } }, { "Action": [ "ec2:RunInstances", "ec2:StartInstances", "ec2:StopInstances", "ec2:TerminateInstances" ], "Effect": "Allow", "Resource": "arn:aws:ec2:*:*:subnet/*", "Condition": { "StringEquals": { "ec2:Vpc": "VPC ID" } } } ] }
以下のコマンドでユーザーにポリシーをアタッチします。
$ aws iam put-user-policy --user-name circleci-vm-service --policy-name circleci-vm-service --policy-document file://vmpolicy.json
以下のコマンドで、ユーザーのアクセスキーとシークレットキーを作成します。この値を控えておいてください。
$ aws iam create-access-key --user-name circleci-vm-service { "AccessKey": { "UserName": "circleci-vm-service", "AccessKeyId": "アクセスキー", "Status": "Active", "SecretAccessKey": "シークレットキー", "CreateDate": "2022-04-20T02:05:34+00:00" } }
VMサービスの設定
まず以下のコマンドを実行して、vm-serviceのエンドポイントを調べます。Nomadサーバーなどと同様に、xxxxxxxx.elb.ap-northeast-1.amazonaws.com
といった文字列が得られるはずです。これを控えておいてください。
$ kubectl get svc vm-service -n circleci-server -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
次に以下のコマンドを実行して、VMを展開するサブネットのサブネットIDを調べます。サブネットは複数設定することができるため、3つ存在するパブリックサブネットをすべて指定するのがよいと思うのですが、ドキュメントにMust be in the same availability zone.
という記述があるため、ここではサブネットのリストから先頭のひとつだけを抽出しています*2。
$ aws ec2 describe-subnets --filters "Name=tag-key,Values=Name" "Name=tag-value,Values=eksctl-circleci-server-cluster/SubnetPublic*" --query 'Subnets[0].SubnetId' "subnet-xxxxxxxx"
ブラウザーからCircleCI Serverの管理コンソールを開いてください。「VM Service」のセクションに移動し、必要な項目を入力していきます。
「Assign Public IPs」のチェックを外してください。VMにパブリックIPをアサインしてしまうと、NomadクライアントからVMへの通信がパブリックネットワークを経由してしまい、先ほどのセキュリティグループでは通信できなくなってしまいます。
「VM Service Load Balancer Hostname」には、上で調べたエンドポイントを入力します。「AWS Region」はap-northeast-1
とします。
「Subnets」には、上で調べたサブネットIDを入力します。
「Security Group ID」には、上で作成したセキュリティグループのIDを入力します。
「Access Key」と「Secret Key」には、上で作成したIAMユーザーのキーをそれぞれ入力します。
ここまでできたら、コンフィグの保存とデプロイを行ってください。
動作を確認する
CircleCIでは、CircleCI Serverの動作確認用にrealitycheckというリポジトリを用意しています。このリポジトリを自身のGitHubアカウントにforkし、CircleCI ServerにセットアップしてCIを起動させてください。これまでに解説したサービスが正しく構築されていれば、すべてのジョブが成功するはずです。特にremote_docker
やmachine
ジョブの結果に注目してください。またRerun Job with SSH
を実行して、SSHデバッグが可能かも確認しておくとよいでしょう。なおいくつかのジョブは、プロジェクト環境変数とコンテキストが設定されていないと失敗します。ドキュメントを参考に、あらかじめ設定しておきましょう。
まとめ
全5回にわたってCircleCI Serverのセットアップ方法を解説しました。おそらくほぼ最適化できているとは思いますが、見ての通り非常に多くの手順が必要となっています。また構築したら終わりではなく、継続して運用メンテを行っていかなくてはなりません。特別な理由がない限り、クラウド版のCircleCIを利用する方が、ずっと低コストでCI/CDを運用することができるのではないか、というのが率直な感想です。
とはいえ機密保持上の理由などで、どうしてもオンプレミスにCI環境が必要ということもあるでしょう。そのような場合に本エントリーが参考になれば幸いです。