はじめに
CI/CD環境を触っていると、Concourse、Harbor、Argo CD、Red Hat OpenShiftという名前が同じ構成図に登場することがあります。
最初は「すべてデプロイに関係するツールなのでは」と混乱しやすいですが、担当する対象は異なります。
Concourse = 作る
Harbor = 置く
Argo CD = 反映する
OpenShift = 動かす
この覚え方は入口として便利です。ただし、HarborからArgo CDを経由してOpenShiftへコンテナイメージが運ばれるわけではありません。
この記事では、4つの役割を「制御の流れ」と「成果物の流れ」に分け、ソースコードの変更からOpenShift上でPodが起動するまでを整理します。
先に全体像
4つの関係を正確に捉えるには、Gitの変更を反映する制御の流れと、コンテナイメージを受け渡す成果物の流れを分けます。
ここで重要なのは、次の2経路です。
| 経路 | 流れるもの | 主な担当 |
|---|---|---|
| 制御の流れ | Gitのコミット、Kubernetesマニフェスト、同期命令 | Concourse、Argo CD |
| 成果物の流れ | ビルド済みコンテナイメージ | Concourse、Harbor、OpenShift |
Argo CDが扱う中心はGitにあるデプロイ設定です。OpenShift上のワークロードが、設定に書かれたイメージをHarborから取得します。
4つの役割
Concourseはパイプラインを実行する
Concourseは、JobとResourceを組み合わせて処理を自動化するパイプライン基盤です。CIで利用する場合は、次のような処理を担当します。
ソースコード取得
↓
テスト
↓
ビルド
↓
コンテナイメージ作成
↓
Harborへpush
例えばGitへのpushをきっかけにテストを実行し、成功した場合だけコンテナイメージを作成できます。その後、デプロイ設定用のGitリポジトリにあるイメージ参照も更新できます。
Concourse自体はCI専用ではなく、外部状態をResourceとして扱う汎用的なパイプライン基盤です。OpenShiftへ直接デプロイする構成も作れます。この記事ではCIとCDの責務を分けるため、Concourseはビルドとデプロイ設定の更新までを担当するものとします。
Harborはビルド済み成果物を保管する
Harborは、コンテナイメージなどのOCI Artifactを保存・管理するレジストリです。Docker HubやAmazon ECRと同じ領域を担当します。
例えばConcourseが次のイメージを作成したとします。
harbor.example.com/team/my-app:git-a1b2c3d
ConcourseはイメージをHarborへpushします。OpenShiftでPodを起動するときは、ノード上のコンテナランタイムがHarborから対象イメージをpullします。
Harborは単なるファイル置き場ではありません。プロジェクト単位のアクセス制御、脆弱性スキャン、タグの不変化、署名など、組織で成果物を管理するための機能も持ちます。
Argo CDはGitの状態をクラスタへ同期する
Argo CDはKubernetes向けのGitOps CDツールです。Gitに保存したKubernetesマニフェスト、Kustomize、Helmなどの定義から目標状態を取得し、クラスタ上の実状態と比較します。
例えばGitに次のDeploymentがあるとします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: my-app
image: harbor.example.com/team/my-app:git-a1b2c3d
Git上のイメージ参照が変わると、Argo CDは目標状態と実状態の差分を検知します。自動同期を有効にしていれば変更をOpenShiftへ反映し、無効なら利用者による同期操作を待ちます。
Argo CDがアプリケーションをビルドしたり、Harborからイメージを取得してOpenShiftへ転送したりするわけではありません。役割は、Gitに書かれた「あるべき状態」とOpenShift上のリソースを一致させることです。
OpenShiftはアプリケーションを実行する
OpenShiftはRed Hatが提供するKubernetesプラットフォームです。Kubernetesを基盤に、認証・権限管理、Webコンソール、ルーティング、監視、Operatorなど、企業利用を支える機能を提供します。
今回の構成では、Argo CDからDeploymentなどの設定が反映され、OpenShift上でPodが起動します。Podの起動に必要なイメージは、設定された参照先に従ってHarborからpullされます。
OpenShiftには統合イメージレジストリもあるため、Harborは必須ではありません。組織共通の外部レジストリとしてHarborを採用している場合に、OpenShiftからHarborを利用する関係になります。
実際のCI/CDの流れ
アプリケーション用Gitとデプロイ設定用Gitを分ける構成では、次のように進みます。2つのGitは論理的な役割であり、必ず別リポジトリにする必要はありません。
- 開発者がアプリケーション用Gitへコードをpushする
- Concourseが新しいコミットを検知する
- Concourseがテストとビルドを実行する
- Concourseがコンテナイメージを作成する
- Concourseが一意なビルドタグを付けてHarborへpushする
- リリース用タグを運用する場合は、承認したArtifactへバージョンタグを追加する
- Concourseまたは別の更新処理が、デプロイ設定用Gitのイメージ参照を更新する
- Argo CDがGitの変更とOpenShift上の状態との差分を検知する
- Argo CDがDeploymentなどをOpenShiftへ同期する
- OpenShiftがHarborから指定されたイメージをpullする
- 新しいPodが起動し、readiness probeに成功するとトラフィックを受ける
この構成では、ConcourseがOpenShiftのAPIへ直接アクセスする必要はありません。CIはGitへ変更を残し、CDはその変更をArgo CDが反映します。誰が何を変更したかをGitの履歴とレビューで追いやすくなることが、GitOps構成の利点です。
リリース時のタグ付けはどこで行うのか
リリースという単位がある運用では、Harbor上のArtifactへリリース用タグを付ける工程も整理しておくと分かりやすくなります。
ここでは、ビルドを識別するタグと、リリースを表すタグを分けます。
| タグの種類 | 例 | 目的 |
|---|---|---|
| ビルドタグ | git-a1b2c3d |
どのソースコードから作ったかを追跡する |
| リリースタグ | v1.2.3 |
利用者や運用者がリリース単位を識別する |
| 環境名タグ |
staging、production
|
現在の昇格先を示す別名として使う |
例えば、Concourseはビルド時にgit-a1b2c3dを付けてHarborへpushします。テスト、脆弱性スキャン、承認を通過した後、同じdigestのArtifactへv1.2.3を追加します。
my-app
└── sha256:0123...
├── git-a1b2c3d
└── v1.2.3
Harborでは、同じArtifactへ複数のタグを追加できます。タグを追加してもArtifactのdigestは変わらず、同じビルド成果物をリリースへ昇格できます。リリースのたびにイメージを再ビルドしないため、テストした内容と本番で動かす内容がずれるリスクを減らせます。
タグ付けはHarborの画面でもできますが、通常はConcourseのリリースJobなどから自動化した方が履歴と権限を管理しやすくなります。処理の流れは次のようになります。
ただし、リリース用タグは必須ではありません。ビルドタグをそのままリリース識別子として使う場合や、デプロイ設定をdigestで固定する場合は、OpenShiftへデプロイするためだけにv1.2.3を作る必要はありません。リリース用タグは、人がリリースを識別しやすくすることや、承認済みArtifactを示すことに価値があります。
リリースタグは上書きできないようにする
v1.2.3のようなリリースタグを後から別のArtifactへ付け替えると、同じマニフェストから異なるイメージが起動し得ます。監査やロールバックの基準として使えなくなるため、HarborのTag Immutability Ruleでリリースタグを上書き・削除できないようにします。
一方、stagingやproductionのような環境名タグは、昇格に合わせて参照先を変える運用があります。この種の可変タグだけをDeploymentへ書くと、Gitの文字列が変わらずArgo CDが変更を検知できません。環境名タグは補助的な別名として扱い、デプロイ設定では一意なバージョンタグまたはdigestを使う方が追跡しやすくなります。
人が読みやすい一意なリリースタグを使う場合は、次のように指定します。
image: harbor.example.com/team/my-app:v1.2.3
実体を厳密に固定する場合は、digestを指定します。
image: harbor.example.com/team/my-app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Harborでリリースタグを追加しただけでは、Argo CDのデプロイは始まりません。最後にデプロイ設定用Gitのイメージ参照を更新し、その変更をArgo CDに同期させる必要があります。
イメージをHarborへpushしただけではデプロイされない
この構成で特に間違えやすい点です。新しいイメージをHarborへpushしても、Argo CDが監視するGitの内容に差分がなければ、通常は新しいデプロイは始まりません。
例えばDeploymentが常に次を参照しているとします。
image: harbor.example.com/team/my-app:latest
同じlatestタグへ別イメージをpushしても、Git上の文字列は変わりません。Argo CDから見ると目標状態に変更がなく、既存Podも自動では作り直されません。さらに、イメージのpull条件やノードのキャッシュによっては、再起動しても期待するイメージを使わない可能性があります。
変更を追跡しやすくするには、コミットSHAなどから一意なタグを作り、デプロイ設定のイメージ参照を毎回更新します。
image: harbor.example.com/team/my-app:git-a1b2c3d
再現性をさらに高める場合は、タグではなくdigestでイメージを固定します。
image: harbor.example.com/team/my-app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
digestを使うと、タグの参照先が後から変更されても同じ内容のイメージを指定できます。どの方法を選んでも、ビルドした成果物とGit上のデプロイ設定を同じ識別子で追えることが重要です。
ConcourseとArgo CDの違い
ConcourseとArgo CDはどちらもCI/CD周辺に登場しますが、判断の基準が異なります。
| 観点 | Concourse | Argo CD |
|---|---|---|
| 主な入力 | ソースコード、外部Resource | Git上のデプロイ設定 |
| 主な処理 | テスト、ビルド、成果物の公開 | 目標状態と実状態の比較・同期 |
| 主な出力 | コンテナイメージ、Gitコミット | Kubernetes/OpenShiftリソース |
| クラスタへの権限 | GitOps構成では不要 | 同期対象への権限が必要 |
| 失敗時に最初に見る場所 | Job、Task、Resourceのログ | ApplicationのSync・Health状態 |
Concourseから直接oc applyやhelm upgradeを実行する構成も可能です。ただし、その場合はCIパイプラインがクラスタの認証情報を持ち、実際の状態変更まで担当します。
GitOps構成では、Concourseはデプロイ設定のGit更新まで、Argo CDはクラスタへの同期を担当します。この境界によって、ビルド失敗とデプロイ失敗を切り分けやすくなります。
HarborとOpenShiftを接続するときの注意点
Harborがprivate projectの場合、OpenShiftは認証なしではイメージをpullできません。対象NamespaceのServiceAccountへpull secretを関連付けるか、WorkloadのimagePullSecretsで指定します。
spec:
template:
spec:
imagePullSecrets:
- name: harbor-pull-secret
containers:
- name: my-app
image: harbor.example.com/team/my-app:git-a1b2c3d
接続時には、少なくとも次を確認します。
- OpenShiftからHarborのホスト名を名前解決できるか
- OpenShiftのノードからHarborへ通信できるか
- pull secretの接続先、ユーザー名、権限が正しいか
- HarborのTLS証明書をOpenShift側が信頼できるか
- 指定したrepository、tag、digestが実際に存在するか
Argo CDの同期が成功しても、イメージ取得まで成功したとは限りません。Deploymentの作成は成功しても、PodがImagePullBackOffになることがあります。同期結果とPodの起動結果は分けて確認します。
反映されないときの確認順序
問題が起きたら、ツール名から推測するのではなく、処理の境界を上流から確認します。
1. Concourseで成果物が作られたか
- 手順: 対象ビルドのJobとTaskの結果、出力したタグまたはdigestを確認します。
- 理由: テスト成功とイメージpush成功は別の処理だからです。
- 確認: Harborに同じタグまたはdigestのArtifactが存在します。
- 異常時: build処理、registry-image Resource、Harborへのpush権限を確認します。
2. デプロイ設定用Gitが更新されたか
- 手順: 対象環境のマニフェスト、Helm values、Kustomize定義の差分を確認します。
- 理由: Harborへのpushだけでは、Argo CDの目標状態は変わらないからです。
- 確認: Gitに新しいイメージ参照のコミットがあり、対象branchへ反映されています。
- 異常時: 更新Jobのログ、push権限、branch protection、Pull Requestの状態を確認します。
3. Argo CDが正しいGitを見ているか
- 手順: Applicationのrepository URL、path、target revision、Sync状態を確認します。
- 理由: 別branchや別環境のpathを監視していると、正しいコミットでも反映されないからです。
- 確認: Argo CDが期待するrevisionを読み、Syncedになっています。
- 異常時: refresh、差分、同期エラー、対象Namespaceへの権限を確認します。
4. OpenShiftがイメージを取得してPodを起動できたか
- 手順: Deploymentのイメージ参照、Podの状態、Eventを確認します。
- 理由: マニフェストの同期成功とコンテナ起動成功は別だからです。
- 確認: 新しいReplicaSetのPodがRunningかつReadyになっています。
- 異常時:
ImagePullBackOffならpull secret、TLS証明書、ネットワーク、tagやdigestの存在を確認します。CrashLoopBackOffならアプリケーションのログと起動設定を確認します。
4製品は固定のセットではない
この4製品は一緒に使えますが、互いを必須とする固定セットではありません。
- Concourseの代わりにGitHub Actions、GitLab CI/CD、Jenkinsなどを使えます。
- Harborの代わりにOpenShiftの統合レジストリ、Amazon ECR、Google Artifact Registryなどを使えます。
- Argo CDを使わず、Concourseから直接OpenShiftへデプロイする構成も作れます。
- OpenShiftではなく、ほかのKubernetes環境をArgo CDの同期先にできます。
そのため、製品名を覚えるよりも「誰がビルドするか」「成果物をどこに置くか」「誰が目標状態を反映するか」「どこで実行するか」という責務で構成を読む方が応用できます。
まとめ
Concourse、Harbor、Argo CD、OpenShiftは、同じCI/CDの流れに登場しても担当する対象が異なります。
Concourse = 作る
Harbor = 置く
Argo CD = 反映する
OpenShift = 動かす
より正確には、Concourseが作ったイメージをHarborへpushし、OpenShiftがHarborからpullします。Argo CDは、その間でイメージを運ぶのではなく、Git上のデプロイ設定をOpenShiftへ同期します。
構成を読むときは、次の4つを順番に確認します。
- 誰がテストとビルドを実行するのか
- ビルド済み成果物をどこへ保存するのか
- 誰がデプロイ設定をクラスタへ反映するのか
- アプリケーションはどこで実行されるのか
この分け方ができると、反映されないときもConcourse、Git、Argo CD、OpenShift、Harborのどこで流れが止まったかを追いやすくなります。