TT Lab
ブログ

한국어 English 日本語

はじめる
ブログ

ブログ

Argo CD·Argo Workflows 読み比べ:比較のループとPod内のワーカー、そしてCronJobひとつのステータス判定

ステータス:ドラフト(2026-10-17)、一部実測。 基準版 argoproj/argo-cd v3.5.1(コミット 109ca7ca7113)、argoproj/argo-workflows v4.1.5(コミット 6c80c7e56ae6)。 Argo CD は小さなクラスターが同じ版を実行しているため、2026-10-17 に 読み取りのみ で確認しました(Pod・Applicationステータス・設定キー・CronJobステータスフィールド)。 Argo Workflows はそのクラスターに存在しないためコードのみを読みました(未実測)。 作成ルール:実測データが基本であり、測定できなかったものは「未実測」、確認できなかったものは「未確認」と表示します。

目次

1. 一行まとめ:「比較するループ」と「Podで働くワーカー」

同じArgoという名前を使っていますが、両プロジェクトの性質は異なります。

コンポーネント 役割 どこで動くか
argocd-repo-server git·helm 저장소를 받아 helm/kustomize/plain 으로 매니페스트 렌더 Deployment
argocd-application-controller アプリごとの比較・ステータス判定・sync。クラスターキャッシュを保持 StatefulSet(シャーディング可能)
argocd-server API・UI・CLIの入口、認証 Deployment
redis レンダリング結果・アプリリソースツリーキャッシュ Deployment
applicationset-controller ジェネレーターでApplicationを生成 Deployment
workflow-controller Workflowのノードステータスマシン、Pod作成 Deployment
argoexec ステップPod内のinit・wait(またはsupervisor)コンテナ、mainをラップするemissary ステップPodごと

2. 構成図①:Argo CD の比較ループ

Argo CD: git 의 원하는 상태와 클러스터를 계속 비교한다 Git 저장소 원하는 상태 repo-server helm·kustomize·plain 렌더 → 매니페스트 결과는 redis 에 캐시 application-controller 큐 두 개: 새로 고침 · 작업 ① 새로 고침 큐 다시 볼 때인가 ② 비교 CompareAppState ③ 상태 판정 자원별 Lua·Go Synced/OutOfSync · Healthy/Degraded 를 Application 에 기록 ④ operation 큐 자동·수동 sync 요청 ⑤ SyncAppState 웨이브·훅 순서로 적용 비교·적용의 핵심은 저장소 안 gitops-engine 이 맡는다 클러스터 캐시 모든 자원을 watch 로 유지 쿠버네티스 API 대상 클러스터(여럿 가능) ③ 의 순서: 먼저 찾은 것 하나만 쓴다 1 argocd-cm 의 resource.customizations.health.{그룹_종류} 2 argocd-cm 의 와일드카드 항목 3 내장 Lua: resource_customizations/{그룹}/{종류}/health.lua 4 내장 Lua 와일드카드 → 5 Go 로 짠 내장 검사(Deployment 등)
図:Argo CD のコンポーネントと比較・ステータス判定・syncループ、ステータス判定スクリプトを選択する順序

図の読み方:

3. コードマップ

v3.5のargo-cdリポジトリには、かつて別個にあった gitops-engine が gitops-engine/ フォルダに取り込まれて います(go.mod の replace … => ./gitops-engine)。 比較・sync・クラスターキャッシュの核心はここにあります。

パス 内容 最初に読むファイル
argo-cd controller/ アプリコントローラー:キュー、比較、ステータス、sync appcontroller.go, state.go, health.go, sync.go
argo-cd controller/cache/ クラスターキャッシュをアプリの観点でラップしたレイヤー cache.go
argo-cd gitops-engine/pkg/ cache(watchキャッシュ)、 diff, sync(ウェーブ・フック)、 health(Go内蔵チェック) sync/sync_context.go, health/health.go
argo-cd reposerver/repository/ マニフェスト生成、レンダリングキャッシュ repository.go
argo-cd resource_customizations/ リソース種別ごとのLua(ステータス・アクション)、テストデータ batch/CronJob/health.lua
argo-cd util/lua/ Luaランナー、スクリプト選択 lua.go
argo-workflows workflow/controller/ Workflowコントローラー、operator(ステータスマシン)、Pod作成 controller.go, operator.go, workflowpod.go
argo-workflows workflow/executor/ Pod内で動作するランナー(アーティファクトの保存・ロード) executor.go
argo-workflows workflow/artifacts/ ストレージドライバー(s3, gcs, azure, oss, hdfs, http, git …) artifacts.go
argo-workflows cmd/argoexec/commands/ argoexec init·wait·emissary コマンド emissary.go

4. コアパスをたどる — Argo CD

4.1 リフレッシュ:いつ再確認するか

コントローラーにはキューが2つあります(controller/appcontroller.go:118 appRefreshQueue、 121行目 appOperationQueue). appcontroller.go:955 Run これらの2つのキューにワーカーゴルーチンを別々にアタッチします。

  1. リフレッシュワーカーは appcontroller.go:1775 processAppRefreshQueueItem です。 まず appcontroller.go:2105 needRefreshAppStatus で「本当に再比較するタイミングか」を問います。 ユーザーがリフレッシュを押した、管理するリソースが変わった、あるいは指定した周期(デフォルト120秒にジッター最大60秒、く argocd_application_controller.go:45)が経過していれば比較します。
  2. 比較は controller/state.go:633 CompareAppState です。この関数が望ましい状態を state.go:206 GetRepoObjs で repo-server から gRPC で取得し、実際の状態を controller/cache/cache.go:729 GetManagedLiveObjs でキャッシュから取り出した後、 gitops-engine の pkg/diff/diff.go:76 Diff でリソースごとに比較します。
  3. 同じ関数の最後で状態を判定します(state.go:1051 で setApplicationHealth 呼び出し)。4.3節で詳しく見ていきます。
  4. 自動 sync が有効な場合、 appcontroller.go:2322 autoSync が Application に operation を書き込み、 すると2つ目のキューがそのアプリを取り上げます。

4.2 レンダリングと適用:repo-server と sync

4.3 状態判定:どのスクリプトが選択するのか

1つのリソースの状態は gitops-engine の pkg/health/health.go:70 GetResourceHealth が決定します。 削除中の場合は Progressing、そうでなければ オーバーライド(override)を先 に確認し、回答がない場合のみ Go で書かれた内蔵チェック (health.go:104 GetHealthCheckFunc、Deployment・Pod・Job など)へと進みます。

Argo CD 側の「オーバーライド」が Lua です。 util/lua/lua.go:169 GetHealthScript の順序を簡略化すると次のようになります。

key := GetConfigMapKey(obj.GroupVersionKind())
if script, ok := vm.ResourceOverrides[key]; ok && script.HealthLua != "" {
    return script.HealthLua, script.UseOpenLibs, nil   // argocd-cm 에 사용자가 적은 것
}
// 와일드카드 항목 → 내장 Lua 파일 → 내장 와일드카드 순으로 찾는다
builtInScript, err := vm.getPredefinedLuaScripts(key, healthScriptFile)

出所: argoproj/argo-cd v3.5.1 util/lua/lua.go(GetHealthScript 要約)、Apache-2.0。

内蔵 Lua は resource_customizations/<그룹>/<종류>/health.lua ファイルであり、 resource_customizations/embed.go の go:embed によってバイナリにまるごと取り込まれます。同じフォルダの health_test.yaml と testdata/ がそのスクリプトのテストです。 新しい CRD の状態判定に貢献する最も簡単な方法は、 このフォルダに1つの Lua とテストデータを追加することです。Go コードを知らなくてもよく、テストは util/lua/health_test.go がフォルダ内の health_test.yaml をすべて見つけて実行します。

このように判定されたリソースの状態は controller/health.go:20 setApplicationHealth が集約してアプリ全体のステータスにします。 最も悪い状態がアプリの状態となり、リソースに argocd.argoproj.io/ignore-healthcheck: "true" アノテーションがある場合は集約から除外されます(41行目)。 v3 ではリソースごとの状態を Application オブジェクトではなく redis のリソースツリーに 格納します。 --persist-resource-health のデフォルトが false だからです (argocd_application_controller.go:293)。Application が巨大化して etcd への書き込みが重くなるのを避けるための選択です。

4.4 CronJob の事例:ドキュメントと Lua が異なるところ

CronJob の内蔵 Lua は resource_customizations/batch/CronJob/health.lua の56行のコードです。判定は5つの分岐に分かれています。

条件(上から順に最初に一致するもの) 判定 行
spec.suspend == true Healthy(「CronJob is Suspended」) — 課題 #24428 が解決されるまでの暫定対応 3
一度も成功したことがない(lastSuccessfulTime がない) Healthy(「never completed successfully」) 15
現在実行中の Job がある(status.active) Healthy(「job is running」) — 課題 #24429 27
最後の成功 < 最後のスケジュール Degraded 35
その他(スケジュールされたことがない場合を含む) Healthy 41·50

v3.5.1 版のドキュメント(docs/operator-manual/health.md)では、CronJob のセクションで「最後の Job が失敗した場合は Degraded」、「最後の Job が実行中の場合は Progressing」と記載されており、 Suspended ステータスの例として「停止した CronJob」が挙げられています。コードと比較すると、この3点はすべて食い違っています。失敗した後に次の Job が実行されている場合は Healthy に戻り、実行中の間は Progressing ではなく Healthy であり、停止した CronJob は Suspended ではなく Healthy です。

5. コアパスをたどる — Argo Workflows

Argo Workflows: 컨트롤러는 파드를 만들고, 파드가 일한다 Workflow CR(템플릿 묶음) workflow-controller · operate() 노드 상태 머신: steps·DAG·컨테이너 createWorkflowPod 단계 하나 = 파드 하나 init argoexec 입력 아티팩트 불러오기 main 사용자 이미지 emissary 가 감싸 실행 wait argoexec 출력 아티팩트 저장·결과 보고 아티팩트 저장소 — NewDriver 가 종류로 고른다 S3(MinIO 호환) · GCS · Azure · OSS · HDFS HTTP · Artifactory · Git · Raw · Plugin 어느 저장소를 쓰나(artifactRepositoryRef 해석) 지정함: 워크플로 네임스페이스의 ConfigMap → 컨트롤러 쪽 지정 안 함: 네임스페이스 기본 → 컨트롤러 설정의 기본
図:Argo Workflows のコントローラー・ステップポッド・アーティファクトリポジトリ
  1. workflow/controller/controller.go:337 Run が informer とワーカーを起動し、 controller.go:922 processNextItem が Workflow を1つ取り出して operator を作成します。
  2. workflow/controller/operator.go:212 operate が1回のリコンサイル(reconcile)となります。 まず operator.go:1216 podReconciliation によってすでに起動しているポッドの状態をノード状態に反映し、 operator.go:2235 executeTemplate によってエントリーテンプレートから再度たどりながら次にすべきことを決定します。 テンプレートの種類に応じて steps.go:42 executeSteps, dag.go:278 executeDAG, operator.go:3435 executeContainer に分岐します。毎回最初から辿り直すがすでに完了したノードはスキップする方式であるため、 コントローラーが再起動しても Workflow の status.nodes さえあれば処理を継続できます。
  3. コンテナステップは workflowpod.go:341 createWorkflowPod がポッドとして作成します。ユーザーコンテナ main の隣に init(workflowpod.go:1092 newInitContainers)と wait をアタッチし、 main のコマンドは argoexec emissary でラップします (cmd/argoexec/commands/emissary.go:46)。v4ではinitなしで supervisor コンテナ1つがinitとwaitの仕事を一緒に行う方式も導入されました (workflowpod.go:1127 newSupervisorContainer, initlessPod 設定)。
  4. Pod内で wait はmainが終了するのを待ってから(workflow/executor/executor.go:1355 Wait), executor.go:449 SaveArtifacts で出力ファイルをストレージにアップロードします。
  5. ストレージドライバーは workflow/artifacts/artifacts.go:29 NewDriver がアーティファクトに記述された種類に応じて選択します。 S3が一番最初(38行目)にあり、アクセスキーはその場でSecretを読み込んで設定します。MinIOはS3ドライバーとして接続します。
  6. アーティファクトにストレージが記述されていない場合、 workflow/artifactrepositories/artifactrepositories.go:38 Resolve が ワークフロー名前空間のデフォルトConfigMap、その次にコントローラー設定のデフォルトストレージの順に探します。

なぜこのように分けたのか。 コントローラーはクラスターに1つですが、ステップは数千個に及びます。重いファイル転送をコントローラーではなくステップPod内の argoexec に任せれば、転送量が増えてもステップPodのみが増加します。その代わりPodごとにサイドカーコンテナが追加されるコストが発生し、v4のsupervisor方式はそのコストを削減しようとする試みと読み取れます(設計意度はドキュメントで確認できず、未確認)。

6. 執筆者の貢献と関連箇所

7. 実測:小規模クラスターにおけるArgo CD

2026-10-17、読み取りコマンドのみを使用しました。アプリ名やリポジトリのアドレスは記載していません。

項目 取得値
コンポーネント application-controller(StatefulSet 1)、repo-server、server、applicationset-controller、notifications-controller、redis、dex — すべて v3.5.1(dex・redisを除く)
repo-serverの再起動 4回(残りは0)
Application 5件、すべて Synced·Healthy
status.resourceHealthSource 5件すべて appTree — 4.3の「リソース状態はredisのリソースツリーに」の通り
argocd-cm の resource.customizations.* キー ignoreResourceUpdates.* が8個のみ存在し、 health.* はなし → CronJobは組み込みLuaで判定

Applicationの status.resources においてCronJobは44件であり、その health の欄はすべて空でした(上記の appTree のため)。そのため、CronJob 44件の実ステータスフィールド (spec.suspend, status.lastScheduleTime, lastSuccessfulTime, active)を読み込み、 4.4の表のルールをPythonに移植して 分類しました。入力は実測値であり、判定はLuaを移植した計算によるものです。

該当する分岐 件数 判定
停止中(suspend: true) 27 Healthy
現在Jobが稼働中 3 Healthy
前回の実行が成功 14 Healthy
前回の実行が失敗(稼働中のJobなし) 0 Degraded

44件のうち27件が停止中のCronJobでした。ドキュメント通りであればUIでSuspendedと表示されるべきものがすべてHealthyに混ざっているということであり、 「Suspendedでフィルタリングして確認する」ことでは1件も見つけることができません。この分類はissue #30243の3番目を実際の数値で裏付けています。

8. どこから読むべきか

  1. resource_customizations/batch/CronJob/ — Luaの56行目とテストデータ。最も小さな入口です。
  2. util/lua/lua.go:169 GetHealthScript → gitops-engine/pkg/health/health.go:70.
  3. controller/appcontroller.go:1775 processAppRefreshQueueItem — 1回のリフレッシュ全体。
  4. controller/state.go:633 CompareAppState — 長文ですが比較のためのすべての材料が揃います。
  5. Workflowsは workflow/controller/operator.go:212 operate と workflowpod.go:341 createWorkflowPod の2箇所を押さえれば全体像がつかめます。
  6. 最後に workflow/artifacts/artifacts.go:29 — ドライバー1つ(s3)を選んで最後まで追いかけてみる。

9. 最初のエントリポイントにおすすめの箇所

10. 制限事項

ティーティーの一言。 休憩中の選手に「元気です!」とだけ書いておくと、監督が「誰が休んでいるんだ?」と探したときに誰も出てきません。27人も休んでいたのに!

参考

閲覧日はすべて2026-10-17です。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます