Kubernetesを使わないGitOpsライクな運用
以前の記事でマイクロサービスのCI/CD戦略として、マルチステージCIとGitOpsの組み合わせを紹介しました。この戦略は、実行環境がKubernetesでなくても適用することができます。
GitOpsは、システムの「あるべき状態」をGitで宣言的に管理する強力な運用モデルですが、その議論はしばしばKubernetesが中心となります。「Kubernetesは導入できないが、手動デプロイから脱却し、デプロイプロセスをモダン化したい」――そんな課題を抱える開発者やインフラエンジニアは少なくないでしょう。
この記事では、そのギャップを埋めるべく、Kubernetesを使わない多様な環境(オンプレミスVM、AWS、GCP、Azure)でGitOpsの原則を適用する実践的な方法を解説します。CI/CDパイプラインを通じてデプロイを自動化する具体的なワークフローと、すぐに使える実装パターンを提示し、信頼性と開発生産性の高い運用を実現する一助となれば幸いです。
GitOpsの基本原則
GitOpsは特定のツールではなく、4つの基本原則に基づいた運用フレームワークです。
1. 唯一の信頼できる情報源としてのGit
システムのあるべき状態は、すべてGitリポジトリで宣言的に記述し、一元管理します。アプリケーションコードだけでなく、インフラ設定、ネットワークポリシーなども含みます。
これにより、すべての変更は追跡可能となり、Pull Request (PR) を通じたレビューと承認プロセスが、変更の透明性と監査性を保証します。
2. 宣言的な状態記述
システムの管理には、最終的な「あるべき状態(What)」を定義する宣言的アプローチを採用します。これは、手順を記述する「命令的(How)」アプローチとは対照的です。
例えば、AWS CloudFormationやServerless Frameworkのserverless.yml
は、この宣言的アプローチの代表例です。
3. 状態を保証する自動化
Gitリポジトリのmainブランチへのマージなどをトリガーとして、CI/CDパイプラインが自動的に動き出します。このパイプラインが、Gitで定義されたあるべき状態を実際の環境へ適用します。
-
CI (継続的インテグレーション)
- アプリケーションコードのテスト、ビルド、アーティファクト(コンテナイメージなど)の生成
-
CD (継続的デリバリー/デプロイ)
- アーティファクトをデプロイするための設定変更を環境に反映
4. 継続的リコンシリエーション
「継続的リコンシリエーション(状態調整)」は、GitOpsの中核をなす原則です。
要素名 | 説明 |
---|---|
Gitリポジトリ | システムのあるべき状態が宣言的に定義された、唯一の信頼できる情報源 |
実行環境 | アプリケーションが実際に稼働しているインフラ |
状態の比較 | あるべき状態と実際の状態を常に比較するプロセス |
差分を修正 | 状態間に差異(ドリフト)が検出された場合に、あるべき状態に一致するように自動修正するプロセス |
Kubernetes環境外ではネイティブな機構が存在しないため、定期的なパイプライン実行やカスタムエージェントでこのループを模倣する必要があります。
アーキテクチャの選択:Push型 vs Pull型
GitOpsの実装には、Push型とPull型の2つの主要なアーキテクチャがあります。非Kubernetes環境では、厳格な運用ルール(手動デプロイの禁止)を前提としたPushモデルが最も現実的です。
Pushモデル:CI/CD分離型のデプロイ
CDパイプラインが、Configリポジトリの変更をトリガーとして、ターゲット環境に設定変更を直接「プッシュ」します。このモデルでは、CIとCDの役割が明確に分離されます。
-
CIパイプライン (Appリポジトリ):
- アプリケーションをビルド・テストし、アーティファクトを作成します。
- アーティファクトの情報(イメージタグ等)でConfigリポジトリの設定ファイルを更新するPull Requestを自動作成します。
- デプロイの準備までを担当します。
-
CDパイプライン (Configリポジトリ):
- 作成されたPRがレビューされ、mainブランチにマージされることをトリガーとして起動します。
- このパイプラインが、Gitで定義された「あるべき状態」を実際の環境へプッシュします。
要素名 | 説明 |
---|---|
Appリポジトリ | アプリケーションのソースコードを管理。CIパイプラインをトリガーする。 |
CIパイプライン | コードのビルド、テスト、アーティファクト生成、ConfigリポジトリへのPR作成までを担当。 |
Configリポジトリ | 環境のあるべき状態を宣言的に管理。CDパイプラインをトリガーする。 |
CDパイプライン | Configリポジトリへのマージを検知し、ターゲット環境へ変更を適用(プッシュ)する。 |
ターゲット環境 | アプリケーションがデプロイされる実行環境。 |
Pullモデル:エージェント主導のリコンシリエーション
ターゲット環境内で動作するエージェントが、Configリポジトリを定期的に監視し、あるべき状態を「プル」して自己の状態を更新します。
このモデルでもCI/CDの役割は分離されますが、デプロイの実行主体が異なります。
-
CIパイプライン (Appリポジトリ):
- Pushモデルと同様に、アプリケーションをビルド・テストし、Configリポジトリへ設定変更のPRを自動作成します。
-
デプロイのトリガー:
- PRがレビューされmainブランチにマージされると、Git上の「あるべき状態」が更新されます。
- これはCDパイプラインを直接起動しません。
-
エージェント (ターゲット環境):
- 環境内で稼働するエージェントが、Configリポジトリの変更を定期的に検知します。
- 現在の環境の状態とGit上の「あるべき状態」との間に差分があれば、エージェントが自ら変更内容をプルし、環境をあるべき状態に同期させます。
要素名 | 説明 |
---|---|
Appリポジトリ | アプリケーションのソースコードを管理。CIパイプラインをトリガーする。 |
CIパイプライン | コードのビルド、テスト、アーティファクト生成、ConfigリポジトリへのPR作成までを担当。 |
Configリポジトリ | 環境のあるべき状態を宣言的に管理。エージェントの監視対象となる。 |
エージェント | ターゲット環境内で動作し、Configリポジトリを監視して状態を同期するプロセス。 |
モデルの比較と選択
評価基準 | Pushモデル | Pullモデル |
---|---|---|
セキュリティ | CIシステムが本番環境のクレデンシャルを要するため、比較的低い | クレデンシャルが環境内に留まるため、高い |
実装の複雑さ (非K8s) | 既存のCI/CDツールを活用でき、低い | カスタムエージェントの開発・管理が必要で、高い |
ドリフト検出 | 別途、定期的な監査が必要 | エージェントが継続的に監視・自動修正するため、強い |
デプロイ速度 | Gitイベントをトリガーに即時実行 | エージェントのポーリング間隔に依存し、遅延の可能性あり |
推奨アプローチ | Pushモデル + IAMロールや短期トークンによるリスク緩和 | - |
CI/CDワークフロー
アプリケーションコードの変更からデプロイまでの流れを、CIとCDの2つのワークフローに分けて解説します。
CIワークフロー:変更からデプロイの意図へ
アプリケーションの変更を、環境に対する「宣言的な変更意図」へと変換するプロセスです。
要素名 | 説明 |
---|---|
Appリポジトリ | アプリケーションのソースコードを管理するリポジトリ |
CIパイプライン | GitHub Actionsなどで構築された自動化プロセス |
アーティファクト | ビルドによって生成されたコンテナイメージやJARファイルなど |
Configリポジトリ | 環境設定(マニフェスト)を管理するリポジトリ。Appリポジトリとは分離を推奨 |
PR (Pull Request) | 変更内容をレビューし、承認するための仕組み |
レビューと承認 | チームによる変更内容の確認と、本番環境へのマージ承認 |
1. リポジトリの分離
アプリケーションのソースコードリポジトリと、環境設定リポジトリは分離します。
- CIループの回避: 意図しないパイプラインの連鎖実行を防止
- 権限の分離: 開発者と運用者の権限を明確に分離
- 関心の分離: アプリケーション開発とインフラ構成のライフサイクルを分離
2. アーティファクトのビルドと登録
GitHub ActionsなどのCIツールで、アプリケーションをビルドし、コンテナレジストリやストレージに登録します。アーティファクトには追跡可能性のため、GitのコミットSHAなどをタグとして付与します。
3. 設定の更新とPRの自動作成
CIジョブが、登録した新しいアーティファクトのタグ(バージョン)で設定リポジトリ内のマニフェストファイルを更新し、mainブランチへのPull Requestを自動で作成します。このPRが、環境への変更意図を記録する監査ログとなります。
CDワークフロー:あるべき状態の適用
承認されたPRをトリガーとして、変更を実際の環境に適用するプロセスです。
要素名 | 説明 |
---|---|
Configリポジトリ | PRがマージされ、あるべき状態が更新される |
CDパイプライン | 設定リポジトリのmainブランチへのマージをトリガーに起動 |
ターゲット環境 | 実際のアプリケーションが稼働するインフラ |
1. デプロイのトリガー
設定リポジトリのmainブランチへのPRマージをトリガーとして、CDパイプライン(GitHub Actionsなど)を起動します。
2. デプロイの実行
CDパイプラインは、最新の設定リポジトリの内容をチェックアウトし、ターゲット環境へ認証後、各プラットフォームに応じたコマンドを実行して変更を適用します。
3. ドリフトの検出と修正 (Pushモデル)
Pushモデルでは状態の自動修復機能がないため、意図しない変更(ドリフト)の検出が重要です。
定期実行(cronなど)のドリフト監査パイプラインを構築し、Git上の「あるべき状態」(例: AnsibleのPlaybook, serverless.yml
)と環境の「実際の状態」(例: VMのパッケージバージョン, Lambdaの環境変数)を比較します。例えば、Ansibleであれば--check
フラグ(ドライランモード)を定期実行して差分を検知したり、AWSであれば設定情報を取得するスクリプト(AWS CLIやSDKを使用)を実行し、Git上の定義と比較したりする方法が考えられます。
差異を検知した場合は、単に通知するだけでなく、自動でIssueを起票したり、修正を促すPRを自動作成したりすることで、GitOpsのワークフローに則った修正を促す仕組みを構築することが理想です。
4. ロールバック戦略
デプロイに問題が発生した場合、設定リポジトリで原因となったコミットをgit revert
します。これにより、以前の安定した状態にシステムを戻すCDパイプラインが自動的にトリガーされます。
実装パターン
各環境への具体的なデプロイ方法を解説します。
オンプレミスVMへのデプロイ (Javaアプリケーション)
GitHub ActionsとAnsibleを利用したPush型ワークフローです。
- 構成管理: Ansible Playbookでサーバーの状態を宣言的に記述します。
-
CDパイプライン:
- CIでビルドされたJARファイルをダウンロードします。
- GitHub Actionsの
webfactory/ssh-agent
などを用いてVMへSSH接続します。 -
ansible-playbook
コマンドを実行し、アプリケーションをデプロイします。
CDワークフローのサンプル (.github/workflows/cd.yml
)
name: Deploy Java App to On-Prem VM
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download JAR artifact
run: echo "Downloading artifact..." # S3などからアーティファクトをダウンロード
- name: Setup SSH connection
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add SSH known hosts
run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Run Ansible Playbook
run: |
ansible-playbook \
-i "${{ secrets.SSH_HOST }}," \ # インベントリを動的に指定
--user ${{ secrets.SSH_USER }} \ # SSH接続ユーザー
deploy.yml # 実行するPlaybookファイル
AWS
VM: Amazon EC2
AWS CodeDeployを利用します。
-
デプロイ定義:
appspec.yml
でファイルのコピー先やライフサイクルフックで実行するスクリプトを定義します。 -
CDパイプライン:
aws deploy create-deployment
コマンドを実行し、指定したGitHubリポジトリのコミットをリビジョンとしてデプロイを開始します。
CDパイプラインのサンプル
- name: Deploy to Amazon EC2
run: |
aws deploy create-deployment \
--application-name ${{ secrets.CODEDEPLOY_APP_NAME }} \ # CodeDeployのアプリケーション名
--deployment-group-name ${{ secrets.CODEDEPLOY_DG_NAME }} \ # デプロイグループ名
--github-location repository=${{ github.repository }},commitId=${{ github.sha }} # デプロイ対象のコミット
CaaS: Amazon ECS
AWS公式のGitHub Actionsを利用します。
-
CDパイプライン:
-
aws-actions/amazon-ecs-render-task-definition
で、タスク定義ファイル内のコンテナイメージを新しいバージョンに更新します。 -
aws-actions/amazon-ecs-deploy-task-definition
で、更新したタスク定義を適用し、サービスを更新します。
-
CDパイプラインのサンプル
- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
# CIステップで生成されたタスク定義ファイルを指定
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ secrets.ECS_SERVICE }} # 更新対象のECSサービス名
cluster: ${{ secrets.ECS_CLUSTER }} # 対象のECSクラスタ名
wait-for-service-stability: true # デプロイの完了を待つ
FaaS: AWS Lambda
Serverless Frameworkを利用します。
-
CDパイプライン:
serverless deploy
コマンドを実行します。CIプロセスでserverless.yml
内のアーティファクトパスは更新済みとします。
CDパイプラインのサンプル
- name: Serverless Deploy
run: serverless deploy --stage production # 'production'ステージにデプロイ
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GCP
VM: Compute Engine
Cloud Buildとgcloud
CLIを利用します。
-
CDパイプライン (
cloudbuild.yaml
):-
gcr.io/cloud-builders/gsutil
でアーティファクトをCloud Storageから取得します。 -
gcloud compute scp
でVMにファイルを転送します。 -
gcloud compute ssh
でリモートコマンドを実行し、サービスを再起動します。
-
CDパイプラインのサンプル
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: 'gcloud'
args:
- 'compute'
- 'ssh'
- 'instance-1' # ターゲットのVMインスタンス名
- '--zone=us-central1-a' # VMのゾーン
- '--command=sudo systemctl restart my-app' # VM上で実行するコマンド
CaaS: Cloud Run
Cloud Buildを利用します。
-
CDパイプライン (
cloudbuild.yaml
):gcloud run deploy
コマンドで、新しいコンテナイメージを指定してサービスを更新します。
CDパイプラインのサンプル
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'my-app-service' # デプロイ対象のCloud Runサービス名
- '--image'
- 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA' # 新しいコンテナイメージ
- '--region'
- 'us-central1' # サービスが稼働するリージョン
FaaS: Cloud Functions
Cloud Buildを利用します。
-
CDパイプライン (
cloudbuild.yaml
):gcloud functions deploy
コマンドで、ソースコードから直接関数をデプロイします。
CDパイプラインのサンプル
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- 'functions'
- 'deploy'
- 'my-java-function' # デプロイする関数名
- '--region=us-central1' # リージョン
- '--source=.' # ソースコードの場所
- '--trigger-http' # トリガーの種類 (ここではHTTP)
- '--runtime=java17' # ランタイム
Azure
VM: Azure Virtual Machines
Azure PipelinesとAnsibleを利用します。
-
CDパイプライン (
azure-pipelines.yml
):-
DownloadPipelineArtifact
タスクでアーティファクトをダウンロードします。 -
Ansible@0
タスクで、セキュアファイルとして保存されたSSH秘密鍵を使い、Ansible Playbookを実行します。
-
CDパイプラインのサンプル
- task: Ansible@0
inputs:
ansibleFile: '$(Pipeline.Workspace)/drop/ansible/deploy.yml' # Playbookファイルのパス
inventories: 'inline'
inline: | # インベントリを直接記述
[webservers]
${{ secrets.AZURE_VM_IP }} ansible_user=${{ secrets.AZURE_VM_USER }}
secureFiles:
- name: privateKey # セキュアファイルに登録したSSH秘密鍵名
CaaS: Azure Container Apps
Azure PipelinesとAzure CLIを利用します。
-
CDパイプライン (
azure-pipelines.yml
):AzureCLI@2
タスク内でaz containerapp update
コマンドを実行し、新しいコンテナイメージでContainer Appを更新します。
CDパイプラインのサンプル
- task: AzureCLI@2
inputs:
azureSubscription: 'YourServiceConnection' # Azureへのサービス接続名
scriptType: 'bash'
inlineScript: |
az containerapp update \
--name my-container-app \ # Container App名
--resource-group my-resource-group \ # リソースグループ名
--image myregistry.azurecr.io/my-app:${{ variables.buildId }} # 新しいコンテナイメージ
FaaS: Azure Functions
Azure Pipelinesの専用タスクを利用します。
-
CDパイプライン (
azure-pipelines.yml
):AzureFunctionApp@1
タスクを使用し、CIで作成されたZIPアーティファクトを指定のFunction Appにデプロイします。
CDパイプラインのサンプル
- task: AzureFunctionApp@1
inputs:
azureSubscription: '$(azureSubscription)' # Azureへのサービス接続
appType: 'functionApp' # アプリケーションのタイプ
appName: '$(functionAppName)' # Function App名
package: '$(Pipeline.Workspace)/**/*.zip' # デプロイするパッケージのパス
runtimeStack: 'JAVA|17' # ランタイムスタック
高度な考慮事項
シークレット管理
データベースのパスワードなどの機密情報は、以下のいずれかの方法で安全に管理します。
戦略 | 説明 |
---|---|
外部シークレットマネージャ | AWS Secrets ManagerやHashiCorp Vaultなどの専用サービスを利用。CI/CD実行時に動的に参照。(推奨) |
Gitリポジトリでの暗号化 | Mozilla SOPSやgit-cryptなどを用いて、リポジトリ内のシークレットファイル自体を暗号化 |
オブザーバビリティ
CI/CDパイプラインとデプロイされたアプリケーションの両方を監視します。
- パイプラインの監視: 実行時間、成功率などを監視し、デプロイプロセスのボトルネックを特定
-
アプリケーションの監視: Gitの「あるべき状態」と監視ツールによる「実際の状態」が一致していることを検証。デプロイによる影響(パフォーマンス、エラーレート)を評価し、問題があれば
git revert
によるロールバックに繋げます
プラットフォームエンジニアリングへの拡張
この記事で示したGitOpsパターンは、組織全体の開発プロセスを標準化する「内部開発者プラットフォーム(IDP)」のエンジンとして機能します。
- 標準化されたワークフロー: プラットフォームチームがCI/CDのテンプレートを提供し、ガバナンスを確保
- 開発者体験の向上: 開発者は使い慣れたGitのフローでインフラを意識せずデプロイを完結
- ガバナンスと自律性の両立: 統制を効かせつつ、開発チームの俊敏性を向上
まとめ
「Kubernetesは導入できないが、手動デプロイからは脱却したい」という課題に対し、Kubernetes以外の環境でGitOpsの原則を実現するための具体的なアプローチを解説しました。
- Gitを信頼できる唯一の情報源とし、宣言的な設定を管理します。
- 非K8s環境では、厳格なルールを前提としたPush型アーキテクチャが現実的な選択肢です。
- CI/CDワークフローを分離し、CIで「変更の意図」をPRとして生成し、CDで承認された変更を適用します。
- オンプレミスVMから主要クラウドの各種サービスまで、環境に応じたツールとパイプラインを構築することで、GitOpsライクな運用は実現可能です。
GitOpsはツールではなく文化です。手動での変更をなくし、すべての変更をGitの追跡可能な履歴として残すことで、システムの安定性と開発チームの生産性は飛躍的に向上します。この記事が、あなたの環境にGitOpsのベストプラクティスを導入するための一歩となれば幸いです。
この記事が少しでも参考になった、あるいは改善点などがあれば、ぜひリアクションやコメント、SNSでのシェアをいただけると励みになります!
参考リンク
GitOpsの基本概念と原則
- GitOpsとは
-
アーキテクチャ (Push vs Pull)
- Push vs. Pull-Based Architecture in GitOps | Akamai - Linode
- GitOps Approach: Pull Vs Push - DevOpsSchool.com
- What Is GitOps? Deployment Strategies & Advantages Explained - KodeKloud
- Why should you use a pull architecture in gitops? : r/devops - Reddit
- GitOps: Push vs Pull? Choosing the Right Approach for Production Deployments - NetEye
- Kubernetes環境でのGitOps
- 非Kubernetes環境への応用
プラットフォーム/ツール別実装ガイド
-
AWS
- Getting Started with GitOps on AWS: Concepts, Tools, and Best Practices - Microtica
- Tutorial: Lambda function deployments with CodePipeline - AWS Documentation
- CodeDeploy AppSpec file reference - AWS Documentation
- Integrating with GitHub Actions – CI/CD pipeline to deploy a Web ...
- Automated Java Application Deployment to EC2 with GitHub Actions | by Fayas Akram
- Google Cloud (GCP)
- Azure
- Serverless
- Terraform / Ansible
CI/CDパイプラインとワークフロー
- GitHub Actions
- Kustomize連携
- Cloud Build
Discussion