AzurePipelinesのSelfhosted Agent構築してみた

目次

はじめに

インフラGの宮本です。  
AzurePipelinesのSelfhosted Agentを用いてPipelineの実行環境を作成したので、そのノウハウを共有したいと思います。  
これからAzurePipelinesで自動化Pipelineを作る人に向け、実行環境作成の一助になれば幸いです。  
  
今回作った構成のポイントは以下の点です。  

  • Azure PipelinesのSelfhosted Agentを使用  
  • プロキシ配下での動作  
  • Agentサーバー構築の自動化

Agentサーバーの構築方法はMicrosoftの公式ドキュメントに記載されています。  
基礎的な知識と、ドキュメントからだと理解しづらいと感じた部分が中心の記事となりますので、あらかじめご了承ください。  

参考:[Self-hosted Linux agents]
参考:[Run a self-hosted agent behind a web proxy]

この記事ではこんなことを知れます

  • なぜSelfhosted Agentを使用するか
  • プロキシ配下のSelfhosted Agentの動き
  • Agentサーバー構築のノウハウ

AzurePipelinesについて  

AzurePipelinesとは  

いわゆるCI/CD Pipelineを作るAzureDevOpsの機能です。  
類似の機能・サービスにGitHubのGitHub Actionsや、AWSのCodeシリーズがあります。  

Pipelineはyamlで記述された定義ファイルを元に、AzurePipelinesのAgentが処理を実行します。  
リポジトリはAzureReposやGitHubなどがサポートされています。  
※統合可能なリポジトリの詳細については、公式ドキュメントの[サポートされているソース リポジトリ]をご確認ください。

なぜSelfhosted Agentを使用するか  

MS公式ドキュメントによるとAgentの実行環境は以下の3種類があります。  

  1. Microsoft-hosted agents:Azure上に作られる、MSマネージドVMの上で動く
  2. Azure Virtual Machine Scale Set agents:1.と同じくMSマネージドで、スケーラブルな環境で動く
  3. Self-hosted agents:自分が作った実行環境で動く

1.や2.のエージェントは、自分で実行環境を作ったりメンテナンスを行ったりする手間がない一方で、動作する場所がAzure上に限定される点に注意が必要です。  
Microsoftの公式ドキュメントには以下のように注記されています。  

When a pipeline needs to access your corporate resources behind a firewall, 
you have to allow the IP address range for the Azure geography. This may increase 
your exposure as the range of IP addresses is rather large and since machines 
in this range can belong to other customers as well. The best way to prevent 
this is to avoid the need to access internal resources. For information on deploying 
artifacts to a set of servers, see Communication to deploy to target servers.

https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#security


1.や2.のエージェントを用いた場合、エージェントの動作環境に割り当てられるIPアドレスは、他顧客も使用する広範囲なIP帯域となる、ということです。

弊社の場合、自社AWS環境へのアクセス制御において、他社が使用するIPアドレスを含む帯域を許可できないため、Selfhosted Agentサーバーを選択しました。

Selfhost Agentの動作するサーバーは自社AWS環境上に構築し、インスタンスプロファイルからAssumeRoleしてAWSのAPIをコールします。

これによりAWS環境に対する実行元の制御は、そもそもソースIPではなく、IAMに完結させています。

参考:[Azure Pipelines agents]

AzurePipelines Agentの動き方  

Selfhosted Agentの動作は以下のようになります。  

  1. オペレーターがPipelineを実行。
  2. Pipelineの定義に従い、AzurePipelinsがジョブを指定の実行環境(エージェント)のキューに入れる。
  3. エージェントは定期的にAzurePipelinesに対し待機中のジョブを問い合わせ、自分のキューに入っているジョブを入手する。
  4. エージェントが取得したジョブを実行し、AzurePipelinesに結果を返す。

エージェントはジョブのポーリングやソースコードの入手を全てアウトバウンド通信で実施します。  

このため、エージェントの動作する環境のセキュリティグループに、インバウンドのルール追加は必要ありません。  

一方AzurePipelinesに対するアウトバウンド通信については、プロキシのホワイトリストに使用するドメインを追加する必要があります。  

公式ドキュメントを参考にドメインを追加してください。  

参考:[AzurePipelinesとのコミュニケーション]
参考:[許可される ドメイン]

Selfhosted Agentサーバー構築の自動化  

今回Selfhosted Agentを構築するにあたり、EC2インスタンス起動時にユーザーデータを流すことで自動化しました。  

以下にそのユーザーデータを記載します。想定するOSはAmazonLinux系です。  

AmazonLinux2023の場合はdnfを使用するので、yumをdnfに置き換えてください。  
(※プロキシを記載するdnf.confのパスは/etc/dnf/dnf.cofに置き換えてください)  

ユーザーデータ  

#!/bin/bash

############################
# ローカル設定
############################
# 変数設定
proxyset=http://${PROXY_NAME}:${PROXY_PORT}
pat_secret_name=${PAT_SECRET_NAME}
azure_dev_ops_url=${AZURE_DEV_OPS_URL}
agent_pool="${AGENT_POOL}"
required_agents_number=${REQUIRED_AGENTS_NUMBER}

# 環境変数設定
AWS_REGION=ap-northeast-1

# プロキシ設定
cat <<EOF >>/etc/environment
http_proxy=$proxyset
https_proxy=$proxyset
HTTP_PROXY=$proxyset
HTTPS_PROXY=$proxyset
no_proxy=localhost,127.0.0.1,169.254.169.254,10.*.*.*
NO_PROXY=localhost,127.0.0.1,169.254.169.254,10.*.*.*
EOF

export http_proxy=$proxyset
export https_proxy=$proxyset
export HTTP_PROXY=$proxyset
export HTTPS_PROXY=$proxyset
export no_proxy=localhost,127.0.0.1,169.254.169.254,10.*.*.*
export NO_PROXY=localhost,127.0.0.1,169.254.169.254,10.*.*.*

# yumのプロキシ設定
cat <<EOF >>/etc/yum.conf
proxy=$proxyset
EOF

# libicu インストール
sudo yum install libicu -y

# AzureDevOpsエージェント用のローカルユーザーを作成
useradd -m {USERNAME}
# パスワード設定
echo `aws secretsmanager get-secret-value --secret-id {PASSWORD_KEY} --query "SecretString" --region $AWS_REGION --output text` | passwd --stdin {USERNAME}
# パスワードなしのsudo権限付与
cat <<EOF >> /etc/sudoers
%{USERNAME} ALL=(ALL:ALL) NOPASSWD: ALL
EOF

# IMDSv2トークン取得、割り当てIPアドレス確認。
METADATA_TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
PRIVATE_IP=`curl -H "X-aws-ec2-metadata-token: $METADATA_TOKEN" -v http://169.254.169.254/latest/meta-data/local-ipv4`

# PAT取得
pat=`aws secretsmanager get-secret-value --secret-id $pat_secret_name --query "SecretString" --region $AWS_REGION --output text`

############################
# SelfhostedAgent登録
############################

if [ $required_agents_number > 0 ]; then

    # {USERNAME}で作業するため、ディレクトリ移動。
    cd /home/{USERNAME}/

    # Self-hosted-agent最新版パッケージ入手
    AGENT_VERSION=$(curl -s https://api.github.com/repos/microsoft/azure-pipelines-agent/releases/latest | grep tag_name | cut -d '"' -f 4 | tr -d 'v')
    AGENT_DOWNLOAD_URL="https://vstsagentpackage.azureedge.net/agent/"$AGENT_VERSION"/vsts-agent-linux-x64-"$AGENT_VERSION".tar.gz"
    curl -O $AGENT_DOWNLOAD_URL

    # 必要なエージェント数のコンフィグ用ディレクトリを作成し、それぞれにファイルを解凍。繰り返し処理。
    for i in `seq $required_agents_number`
    do

        # 作業ディレクトリ移動。
        cd /home/{USERNAME}/

        # Agent名設定
        agentname=$PRIVATE_IP\_agent$i

        # ディレクトリ作成
        mkdir /home/{USERNAME}/agent$i\_config
        dir=/home/{USERNAME}/agent$i\_config

        # パッケージ解凍
        packagename="vsts-agent-linux-x64-"$AGENT_VERSION".tar.gz"
        tar zxvf ./$packagename -C $dir

        # workディレクトリ作成
        mkdir $dir\/_work

        # 権限変更
        chown -R {USERNAME}:{USERNAME} $dir

        # config.shがあるディレクトリに移動
        cd $dir

        # AzureDevOpsエージェント設定
        sudo -u {USERNAME} bash ./config.sh --unattended --proxyurl $proxyset --url $azure_dev_ops_url --auth pat --token $pat --pool "$agent_pool" --agent "$agentname" --replace --work ./_work

        # プロキシ設定ファイル設置
        echo "$proxyset" > ./.proxy

        # エージェント起動
        sudo ./svc.sh install
        sudo ./svc.sh start
    done

    余計なファイルを削除
    cd /home/{USERNAME}
    rm -f vsts-agent-linux-x64-*.tar.gz

fi

############################
# AzurePipelines 実行用パッケージ取得
############################

# git
sudo yum install git -y
# jq
sudo yum install jq -y

前提事項

  • プロキシホワイトリストに前述のドメインが記載されていること。
  • secret managerに{PAT_SECRET_NAME}に置き換える文字列をキーとして、以下要件を満たすPATがvalueに登録されていること。
  • secret managerに{PASSWORD_KEY}に置き換える文字列をキーとして、任意の文字列がvalueに登録されていること。
  • 起動するインスタンスに`"secretsmanager":"get-secret-value"`の権限が付与されていること。
  • 起動するインスタンスに付与するSGにて、プロキシへのアウトバウンドを許可していること。
  • AzurePipelinesのAgentPoolに{AGENT_POOL}に置き換わる文字列でPoolが作られていること。
  • ユーザーデータ中の{ }の文字列について、環境に合わせ、適当な値に置き換えていること。

 

文字列

置き換える値

{PROXY_NAME}

プロキシサーバーのDNS名。

{PROXY_PORT}

プロキシの受付ポート。

{PAT_SECRET_NAME}

シークレットマネージャーの、PATを登録してあるシークレットのキー(シークレットの名前)。

{AZURE_DEV_OPS_URL}

Agentを登録したいAzureDevOpsのOrganization URL。(※Organization SettingsのOverviewから確認できます。)

{AGENT_POOL}

AzurePipelinsのAgentPool名。

{REQUIRED_AGENTS_NUMBER}

一つのインスタンス上で動かしたいAgent数。1サーバー1エージェント推奨です。

{USERNAME}

Selfhosted Agentが使用するために新規作成するOSローカルユーザーの名前。

{PASSWORD_KEY}

シークレットマネージャーの、OSローカルユーザーのパスワードを登録しているシークレットのキー(シークレットの名前)。

ポイントの説明  

  • libicu

# libicu インストール
sudo yum install libicu -y

  Agentをインストールする際に叩いているconfig.shの実行に必要です。  
  AmazonLinux上でエージェントのセットアップを自動化する場合は必要です。  

  • proxy設定

# プロキシ設定ファイル設置
echo "$proxyset" > ./.proxy

Agentのconfig.shと同じディレクトリに.proxyという名前のファイルを作成し、中にプロキシの記載をする必要があります。  
config.shを叩く際に`--proxyurl $proxyset`を記載していますが、これはconfig.shによる処理(初回エージェント登録処理)でのみ使用されます。.proxyファイルが無いとエージェントは登録されているものの、AgentPool上でオフラインの状態になるため必要です。  公式ドキュメントでは既にファイルが作られているようにも読めるのですが、手動での作成が必要でした。

  • その他  

シークレットマネージャーに登録しているPATは、エージェントの初期登録でのみ使用されます。  
構築が完了した段階で無効化しても、エージェントの動作自体には影響ありません。

おわりに

今回はAmazonLinuxでのSelfhosted Agent構築でしたが、ユーザーデータをpowershellに置き換えることでWindowsでも同様の自動化が可能です。  
また、エージェントはDockerコンテナ上で動かすことも可能です。自身の環境に合わせカスタマイズしてみてください。  

インフラGではこの要領で作成したAzurePipelinesの環境を使用し、terraformによるAWSインフラのCI/CDを行っています。  
本記事をご覧になった方、AzurePipelinesやterraformでのインフラ管理にご興味があれば、是非一緒に働きましょう。  

宮本 太一 Taichi Miyamoto

ITマネジメント&インフラ・セキュリティ統括部 インフラ部 インフラグループ リードエンジニア

2017年 パーソルホールディングス グループIT本部入社。2019年に発足したグループ全社向け次世代クラウド基盤のNW設計PJTのPMを経て、次世代クラウド基盤企画と、運用チームの管理に従事。2022年4月よりパーソルキャリアへ出向。インフラエンジニアとして、基幹システムのクラウドリフトを担当。

※2024年05月現在の情報です。