とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど

開催予定の勉強会

読者登録と各種SNSのフォローもよろしくお願いいたします。

オンプレミスでCircleCI Serverを動かそう(その5) VMサービスの構築編

オンプレミスで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ユーザーのキーをそれぞれ入力します。

VMサービスを設定する

ここまでできたら、コンフィグの保存とデプロイを行ってください。

動作を確認する

CircleCIでは、CircleCI Serverの動作確認用にrealitycheckというリポジトリを用意しています。このリポジトリを自身のGitHubアカウントにforkし、CircleCI ServerにセットアップしてCIを起動させてください。これまでに解説したサービスが正しく構築されていれば、すべてのジョブが成功するはずです。特にremote_dockermachineジョブの結果に注目してください。またRerun Job with SSHを実行して、SSHデバッグが可能かも確認しておくとよいでしょう。なおいくつかのジョブは、プロジェクト環境変数とコンテキストが設定されていないと失敗します。ドキュメントを参考に、あらかじめ設定しておきましょう。

realitycheckでCircleCI Serverの動作を確認する

まとめ

全5回にわたってCircleCI Serverのセットアップ方法を解説しました。おそらくほぼ最適化できているとは思いますが、見ての通り非常に多くの手順が必要となっています。また構築したら終わりではなく、継続して運用メンテを行っていかなくてはなりません。特別な理由がない限り、クラウド版のCircleCIを利用する方が、ずっと低コストでCI/CDを運用することができるのではないか、というのが率直な感想です。

とはいえ機密保持上の理由などで、どうしてもオンプレミスにCI環境が必要ということもあるでしょう。そのような場合に本エントリーが参考になれば幸いです。

*1:デバッグ時にSSHは標準の22番ではなく、このポートで待ち受けているcircleci-agentを経由します。

*2:当然AZ障害に対して弱くなってしまうのですが、ここでは考慮しないものとします。