Spinnaker
Spinnaker 完全ガイド: クラウドネイティブ時代の継続的デリバリープラットフォーム
1. はじめに
1.1 Spinnaker とは
Spinnaker は、Netflix が開発しマルチクラウド環境向けに設計されたオープンソースの継続的デリバリー(CD)プラットフォームである。2015年にオープンソース化され、現在は Continuous Delivery Foundation(CDF)のもとで開発が進められている。Google、Microsoft、Amazon、Netflix など主要なクラウドプロバイダーやテクノロジー企業がコントリビューターとして参加しており、エンタープライズレベルの本番環境での運用実績を持つ。
Spinnaker の最大の特徴は、アプリケーションのデプロイメントを「パイプライン」という概念で抽象化し、複数のクラウドプロバイダーに対して統一的なデプロイメントワークフローを提供する点にある。AWS、Google Cloud Platform(GCP)、Microsoft Azure、Kubernetes、Cloud Foundry など、主要なクラウドインフラストラクチャに対応しており、マルチクラウド戦略を採用する組織にとって極めて有用なツールである。
1.2 Spinnaker が解決する課題
現代のソフトウェア開発において、継続的デリバリーは不可欠な実践となっている。しかし、以下のような課題が存在する。
- マルチクラウド環境の複雑性: 複数のクラウドプロバイダーを利用する場合、各プロバイダー固有のデプロイメントツールやAPIを個別に管理する必要がある
- デプロイメント戦略の標準化: Blue/Green デプロイメント、カナリアリリース、ローリングアップデートなどの高度なデプロイメント戦略を一貫して実装することが困難
- ガバナンスとコンプライアンス: デプロイメントプロセスにおける承認フロー、監査ログ、ポリシー適用の統一的な管理
- 可視性と透明性: デプロイメントの状態、履歴、依存関係を一元的に把握することの難しさ
- ロールバックの信頼性: 問題発生時の迅速かつ確実なロールバック機構の確保
Spinnaker はこれらの課題に対して、統一されたプラットフォームとして包括的なソリューションを提供する。
1.3 Spinnaker の歴史と背景
Spinnaker の起源は、Netflix 内部で使用されていた「Asgard」というデプロイメントツールに遡る。Asgard は AWS に特化したウェブベースのデプロイメントツールであったが、Netflix のインフラストラクチャが複雑化するにつれて、より柔軟でマルチクラウド対応のプラットフォームが求められるようになった。
2014年、Netflix は Google と共同で Spinnaker の開発を開始した。Google は自社の内部ツールである「Google Deployment Manager」の知見を活かし、クラウドネイティブなデプロイメントプラットフォームの設計に貢献した。2015年11月にオープンソースとして公開され、以降急速にコミュニティが拡大した。
主要なマイルストーンは以下の通りである。
| 年 | イベント |
|---|---|
| 2014 | Netflix と Google が共同開発を開始 |
| 2015 | オープンソースとして公開 |
| 2017 | Kubernetes プロバイダーのサポート追加 |
| 2018 | Managed Pipeline Templates (MPT) v2 導入 |
| 2019 | Continuous Delivery Foundation (CDF) に参加 |
| 2020 | Managed Delivery 機能の強化 |
| 2021 | AWS ECS、Lambda のネイティブサポート強化 |
| 2022-2024 | Kubernetes ネイティブ統合の深化、Terraform 連携強化 |
2. アーキテクチャ概要
2.1 マイクロサービスアーキテクチャ
Spinnaker は、疎結合なマイクロサービス群で構成されている。各サービスは独立してデプロイ・スケール可能であり、REST API を介して相互に通信する。この設計により、高い拡張性と耐障害性を実現している。
Spinnaker を構成する主要なマイクロサービスは以下の通りである。
┌─────────────────────────────────────────────────────────────────┐
│ Spinnaker Platform │
│ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────────┐ │
│ │ Deck │ │ Gate │ │ Orca │ │ Clouddriver │ │
│ │ (UI) │→│ (API GW) │→│ (Orch.) │→│ (Cloud Provider) │ │
│ └─────────┘ └──────────┘ └─────────┘ └──────────────────┘ │
│ │ │ │
│ ┌─────┴─────┐ ┌────┴────┐ │
│ │ Igor │ │ Echo │ │
│ │ (CI Integ.)│ │(Notif.) │ │
│ └───────────┘ └─────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌────────────────┐ │
│ │ Rosco │ │ Front50 │ │ Fiat │ │ Kayenta │ │
│ │ (Bakery) │ │(Persist.)│ │ (AuthZ) │ │(Canary Anal.) │ │
│ └──────────┘ └──────────┘ └─────────┘ └────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Keel │ │ Halyard │ │
│ │(Managed) │ │ (Config) │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 各マイクロサービスの詳細
2.2.1 Deck(UI)
Deck は Spinnaker のフロントエンド Web UI を提供する Single Page Application(SPA)である。React.js で構築されており、ユーザーはブラウザを通じてパイプラインの作成・管理、アプリケーションの状態監視、デプロイメントの実行を行うことができる。
主な機能:
- パイプラインの視覚的な作成・編集(パイプラインエディタ)
- アプリケーション・クラスター・サーバーグループの一覧表示と詳細確認
- インフラストラクチャの状態をリアルタイムで可視化
- デプロイメント履歴の閲覧と差分比較
- 手動承認(Manual Judgment)のインターフェース
設定例(settings.js):
window.spinnakerSettings = {
defaultProviders: ['kubernetes', 'aws'],
gateUrl: 'https://gate.spinnaker.example.com',
bakeryDetailUrl: 'https://rosco.spinnaker.example.com',
authEnabled: true,
feature: {
canary: true,
pipelineTemplates: true,
managedDelivery: true,
notifications: true,
artifacts: true,
managedServiceAccounts: true,
},
notifications: {
slack: {
enabled: true,
botName: 'spinnaker-bot',
},
email: {
enabled: true,
},
},
defaultTimeZone: 'Asia/Tokyo',
};
2.2.2 Gate(API Gateway)
Gate は Spinnaker の API ゲートウェイであり、すべての外部リクエストのエントリーポイントとなる。Spring Boot ベースで実装されており、認証・認可の処理、リクエストのルーティング、レート制限などを担当する。
主な責務:
- OAuth 2.0 / SAML 2.0 / LDAP による認証処理
- API リクエストの各マイクロサービスへのルーティング
- CORS(Cross-Origin Resource Sharing)の処理
- セッション管理
- API ドキュメント(Swagger/OpenAPI)の提供
設定例(gate.yml):
server:
port: 8084
ssl:
enabled: true
keyStore: /opt/spinnaker/certs/keystore.jks
keyStorePassword: ${KEYSTORE_PASSWORD}
keyAlias: gate
security:
oauth2:
enabled: true
client:
clientId: ${OAUTH_CLIENT_ID}
clientSecret: ${OAUTH_CLIENT_SECRET}
accessTokenUri: https://oauth2.googleapis.com/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
scope: openid email profile
resource:
userInfoUri: https://openidconnect.googleapis.com/v1/userinfo
userInfoMapping:
email: email
firstName: given_name
lastName: family_name
cors:
allowedOriginsPattern: ^https?://(?:localhost|spinnaker\.example\.com)
rate-limit:
enabled: true
rateSeconds: 60
capacity: 120
learning: false
2.2.3 Orca(Orchestration Engine)
Orca は Spinnaker の中核となるオーケストレーションエンジンである。パイプラインとタスクの実行を管理し、各ステージの処理を適切な順序で実行する責務を担う。Orca は内部的に有向非巡回グラフ(DAG)を用いてタスクの依存関係を管理し、並列実行が可能なタスクを自動的に識別して並行処理を行う。
主な機能:
- パイプラインの実行管理(開始、一時停止、再開、キャンセル)
- ステージ間の依存関係の解決と実行順序の決定
- タスクの並列実行とフォーク/ジョインの制御
- 実行コンテキスト(SpEL: Spring Expression Language)の管理
- リトライロジックとエラーハンドリング
- 実行履歴の永続化
Orca の内部アーキテクチャ:
┌──────────────────────────────────────────────┐
│ Orca │
│ │
│ ┌────────────┐ ┌──────────────────────┐ │
│ │ Pipeline │────→│ Stage Executor │ │
│ │ Launcher │ │ ┌────────────────┐ │ │
│ └────────────┘ │ │ Task Resolver │ │ │
│ │ └────────────────┘ │ │
│ ┌────────────┐ │ ┌────────────────┐ │ │
│ │ Queue │────→│ │ Task Runner │ │ │
│ │ (Keiko) │ │ └────────────────┘ │ │
│ └────────────┘ └──────────────────────┘ │
│ │
│ ┌────────────┐ ┌──────────────────────┐ │
│ │ Execution │ │ Context Manager │ │
│ │ Repository │ │ (SpEL Evaluation) │ │
│ └────────────┘ └──────────────────────┘ │
└──────────────────────────────────────────────┘
Orca は Keiko と呼ばれるキューシステムを使用してタスクをスケジューリングする。Keiko は Redis ベースのキューイングフレームワークであり、タスクの優先順位付け、遅延実行、デッドレター処理などの機能を提供する。
SpEL(Spring Expression Language)の使用例:
{
"type": "deploy",
"name": "Deploy to Production",
"clusters": [
{
"application": "myapp",
"account": "prod-account",
"capacity": {
"min": "${#stage('Evaluate Capacity').outputs.minInstances}",
"max": "${#stage('Evaluate Capacity').outputs.maxInstances}",
"desired": "${#stage('Evaluate Capacity').outputs.desiredInstances}"
},
"imageId": "${trigger.artifacts.find { it.type == 'docker/image' }.reference}"
}
]
}
2.2.4 Clouddriver(Cloud Provider Interface)
Clouddriver は Spinnaker とクラウドプロバイダーの間のインターフェースを提供する。各クラウドプロバイダー固有の API を抽象化し、統一的なインターフェースでクラウドリソースの操作・照会を可能にする。
サポートするクラウドプロバイダー:
- Amazon Web Services(EC2, ECS, Lambda)
- Google Cloud Platform(Compute Engine, Kubernetes Engine, App Engine)
- Microsoft Azure
- Kubernetes(マニフェストベースのデプロイメント)
- Cloud Foundry
- Oracle Cloud Infrastructure
- Huawei Cloud
- Alibaba Cloud
Clouddriver の内部構造:
Clouddriver
├── clouddriver-core # 共通インターフェースと抽象化レイヤー
├── clouddriver-aws # AWS プロバイダー実装
├── clouddriver-google # GCP プロバイダー実装
├── clouddriver-azure # Azure プロバイダー実装
├── clouddriver-kubernetes # Kubernetes プロバイダー実装
├── clouddriver-cloudfoundry # Cloud Foundry プロバイダー実装
├── clouddriver-ecs # AWS ECS プロバイダー実装
├── clouddriver-oracle # Oracle Cloud プロバイダー実装
└── clouddriver-sql # SQL ベースのキャッシュストレージ
Kubernetes プロバイダーの設定例(clouddriver.yml):
kubernetes:
enabled: true
accounts:
- name: production-k8s
providerVersion: V2
context: production-context
kubeconfigFile: /opt/spinnaker/kubeconfig/production
namespaces:
- default
- production
- monitoring
omitNamespaces:
- kube-system
- kube-public
kinds:
- deployment
- replicaSet
- service
- ingress
- configMap
- secret
- horizontalPodAutoscaler
omitKinds: []
cacheThreads: 2
cachingPolicies:
- kubernetesKind: deployment
maxEntriesPerAgent: 500
customResources:
- kubernetesKind: VirtualService
spinnakerKind: serverGroupManagers
deployPriority: "100"
versioned: false
namespaced: true
dockerRegistries:
- accountName: docker-registry
namespaces:
- production
oAuthScopes: []
onlySpinnakerManaged: true
checkPermissionsOnStartup: true
liveManifestCalls: false
rawResourcesEndpointConfig:
kindExpressions: []
omitKindExpressions: []
- name: staging-k8s
providerVersion: V2
context: staging-context
kubeconfigFile: /opt/spinnaker/kubeconfig/staging
namespaces:
- default
- staging
dockerRegistries:
- accountName: docker-registry
namespaces:
- staging
primaryAccount: production-k8s
AWS プロバイダーの設定例:
aws:
enabled: true
accounts:
- name: production-aws
accountId: "123456789012"
assumeRole: role/SpinnakerManaged
regions:
- name: us-east-1
- name: us-west-2
- name: ap-northeast-1
lifecycleHooks:
- defaultResult: CONTINUE
heartbeatTimeout: 7200
lifecycleTransition: autoscaling:EC2_INSTANCE_TERMINATING
notificationTargetARN: arn:aws:sns:us-east-1:123456789012:spinnaker-lifecycle
roleARN: arn:aws:iam::123456789012:role/SpinnakerLifecycle
- name: staging-aws
accountId: "987654321098"
assumeRole: role/SpinnakerManaged
regions:
- name: us-east-1
defaultKeyPairTemplate: '{{name}}-keypair'
defaultRegions:
- name: us-east-1
primaryAccount: production-aws
bakeryDefaults:
baseImages:
- baseImage:
id: ubuntu-22
shortDescription: Ubuntu 22.04 LTS
detailedDescription: Ubuntu 22.04 LTS (Jammy Jellyfish)
packageType: deb
virtualizationSettings:
- region: us-east-1
virtualizationType: hvm
instanceType: t3.medium
sourceAmi: ami-0abcdef1234567890
sshUserName: ubuntu
templateFile: aws-ebs.json
awsAssociatePublicIpAddress: true
defaultVirtualizationType: hvm
2.2.5 Front50(Persistence Service)
Front50 は Spinnaker のメタデータ永続化サービスである。パイプライン定義、アプリケーション設定、プロジェクト情報、通知設定などのメタデータを永続化ストレージに保存・管理する。
サポートするストレージバックエンド:
- Amazon S3
- Google Cloud Storage(GCS)
- Azure Blob Storage
- Oracle Object Storage
- SQL(MySQL, PostgreSQL)
- Redis(非推奨)
設定例(front50.yml):
spinnaker:
s3:
enabled: true
bucket: spinnaker-front50-data
rootFolder: front50
region: us-east-1
pathStyleAccess: true
serverSideEncryption: AES256
gcs:
enabled: false
bucket: spinnaker-front50-data
project: my-gcp-project
jsonPath: /opt/spinnaker/credentials/gcs-account.json
sql:
enabled: true
connectionPools:
default:
jdbcUrl: jdbc:mysql://mysql-host:3306/front50?useSSL=true
user: front50_service
password: ${MYSQL_PASSWORD}
connectionTimeout: 5000
maxLifetime: 30000
maxPoolSize: 50
migration:
jdbcUrl: jdbc:mysql://mysql-host:3306/front50?useSSL=true
user: front50_migrate
password: ${MYSQL_MIGRATE_PASSWORD}
2.2.6 Igor(CI Integration)
Igor は CI/CD ツールとの統合を担当するサービスである。Jenkins、Travis CI、GitHub Actions、GitLab CI、Google Cloud Build、AWS CodeBuild など、様々な CI ツールのビルド結果をポーリングまたはウェブフック経由で取得し、Spinnaker パイプラインのトリガーとして利用する。
設定例(igor.yml):
jenkins:
enabled: true
masters:
- name: main-jenkins
address: https://jenkins.example.com
username: spinnaker-service
password: ${JENKINS_API_TOKEN}
csrf: true
permissions: {}
travis:
enabled: false
masters:
- name: main-travis
address: https://api.travis-ci.com
baseUrl: https://travis-ci.com
githubToken: ${TRAVIS_GITHUB_TOKEN}
numberOfRepositories: 25
gcb:
enabled: true
accounts:
- name: gcb-account
project: my-gcp-project
jsonKey: /opt/spinnaker/credentials/gcb-account.json
subscriptionName: projects/my-gcp-project/subscriptions/spinnaker-gcb
github:
enabled: true
accounts:
- name: github-account
token: ${GITHUB_TOKEN}
dockerRegistry:
enabled: true
accounts:
- name: docker-registry
address: https://index.docker.io
username: ${DOCKER_USERNAME}
password: ${DOCKER_PASSWORD}
repositories:
- myorg/myapp
- myorg/myapp-worker
cacheIntervalSeconds: 60
trackDigests: true
2.2.7 Echo(Event & Notification Service)
Echo は Spinnaker のイベントバスおよび通知サービスである。パイプラインの実行イベント、ステージの状態変化、デプロイメント完了などのイベントを処理し、各種通知チャネルへの配信を行う。また、外部からのウェブフックやCRONスケジュールによるパイプライントリガーの処理も担当する。
設定例(echo.yml):
slack:
enabled: true
token: ${SLACK_BOT_TOKEN}
botName: spinnaker-notifier
email:
enabled: true
from: spinnaker@example.com
host: smtp.example.com
port: 587
username: ${SMTP_USERNAME}
password: ${SMTP_PASSWORD}
github-status:
enabled: true
token: ${GITHUB_STATUS_TOKEN}
webhooks:
enabled: true
preconfigured:
- label: PagerDuty Incident
description: Create a PagerDuty incident
type: pagerduty
enabled: true
url: https://events.pagerduty.com/v2/enqueue
customHeaders:
Content-Type: application/json
method: POST
payload: |-
{
"routing_key": "${parameterValues['routingKey']}",
"event_action": "trigger",
"payload": {
"summary": "${parameterValues['summary']}",
"severity": "${parameterValues['severity']}",
"source": "spinnaker"
}
}
parameters:
- label: Routing Key
name: routingKey
type: string
- label: Summary
name: summary
type: string
- label: Severity
name: severity
type: string
defaultValue: critical
scheduler:
enabled: true
cron:
timezone: Asia/Tokyo
pubsub:
enabled: true
google:
enabled: true
subscriptions:
- name: gcs-artifacts
project: my-gcp-project
subscriptionName: spinnaker-gcs-artifacts
jsonPath: /opt/spinnaker/credentials/pubsub-account.json
ackDeadlineSeconds: 30
messageFormat: GCS
2.2.8 Rosco(Bakery Service)
Rosco は VM イメージのベイキング(作成)を担当するサービスである。HashiCorp Packer を内部的に使用し、AMI(Amazon Machine Image)、GCE イメージ、Azure VM イメージなどの不変インフラストラクチャイメージを作成する。
設定例(rosco.yml):
rosco:
configDir: /opt/rosco/config/packer
jobs:
local:
enabled: true
debianRepository: https://apt.example.com/repo main
yumRepository: https://yum.example.com/repo
aws:
enabled: true
bakeryDefaults:
templateFile: aws-ebs.json
baseImages:
- baseImage:
id: ubuntu-22-base
shortDescription: Ubuntu 22.04 Base
packageType: deb
virtualizationSettings:
- region: us-east-1
virtualizationType: hvm
instanceType: t3.medium
sourceAmi: ami-0abcdef1234567890
sshUserName: ubuntu
spotPrice: "0"
spotPriceAutoProduct: Linux/UNIX
google:
enabled: true
bakeryDefaults:
templateFile: gce.json
baseImages:
- baseImage:
id: ubuntu-2204-lts
shortDescription: Ubuntu 22.04 LTS
packageType: deb
virtualizationSettings:
sourceImage: ubuntu-2204-jammy-v20230616
sourceImageFamily: ubuntu-2204-lts
zone: us-central1-f
network: default
useInternalIp: false
machineType: n1-standard-2
2.2.9 Fiat(Authorization Service)
Fiat は Spinnaker の認可(Authorization)サービスである。ロールベースのアクセス制御(RBAC)を提供し、アプリケーション、パイプライン、アカウント、サービスアカウントへのアクセスを制御する。
設定例(fiat.yml):
auth:
enabled: true
groupMembership:
service: LDAP
ldap:
url: ldaps://ldap.example.com
managerDn: cn=spinnaker,ou=services,dc=example,dc=com
managerPassword: ${LDAP_PASSWORD}
groupSearchBase: ou=groups,dc=example,dc=com
groupSearchFilter: (member={0})
groupRoleAttributes: cn
userDnPattern: uid={0},ou=users,dc=example,dc=com
userSearchBase: ou=users,dc=example,dc=com
userSearchFilter: (uid={0})
permissions:
fallback: DENY
allowAccessToUnknownApplications: false
applications:
- name: myapp-production
permissions:
READ:
- engineering-team
- devops-team
- management
WRITE:
- devops-team
EXECUTE:
- devops-team
- senior-engineers
CREATE:
- devops-team
accounts:
- name: production-k8s
permissions:
READ:
- engineering-team
- devops-team
WRITE:
- devops-team
- name: staging-k8s
permissions:
READ:
- engineering-team
- devops-team
- qa-team
WRITE:
- engineering-team
- devops-team
admin:
roles:
- spinnaker-admins
2.2.10 Kayenta(Canary Analysis Service)
Kayenta は自動カナリア分析(Automated Canary Analysis: ACA)を提供するサービスである。Netflix が開発した統計的手法を用いて、カナリアデプロイメントとベースラインの間のメトリクスを自動的に比較・評価する。
設定例(kayenta.yml):
kayenta:
enabled: true
google:
enabled: true
accounts:
- name: canary-stackdriver
project: my-gcp-project
jsonPath: /opt/spinnaker/credentials/kayenta-account.json
supportedTypes:
- METRICS_STORE
- OBJECT_STORE
bucket: kayenta-canary-data
bucketLocation: us-central1
rootFolder: kayenta
prometheus:
enabled: true
accounts:
- name: canary-prometheus
endpoint:
baseUrl: http://prometheus.monitoring:9090
supportedTypes:
- METRICS_STORE
datadog:
enabled: true
accounts:
- name: canary-datadog
endpoint:
baseUrl: https://api.datadoghq.com
apiKey: ${DATADOG_API_KEY}
applicationKey: ${DATADOG_APP_KEY}
supportedTypes:
- METRICS_STORE
aws:
enabled: true
accounts:
- name: canary-s3
bucket: kayenta-canary-data
region: us-east-1
rootFolder: kayenta
supportedTypes:
- OBJECT_STORE
newrelic:
enabled: true
accounts:
- name: canary-newrelic
endpoint:
baseUrl: https://insights-api.newrelic.com
apiKey: ${NEWRELIC_API_KEY}
applicationKey: ${NEWRELIC_APP_KEY}
supportedTypes:
- METRICS_STORE
serviceIntegrations:
- name: google
enabled: true
- name: prometheus
enabled: true
- name: datadog
enabled: true
- name: aws
enabled: true
judge:
- name: NetflixACAJudge-v1.0
enabled: true
2.2.11 Keel(Managed Delivery)
Keel は宣言的デリバリー(Managed Delivery)を提供するサービスである。ユーザーが望ましい状態(Desired State)を宣言すると、Keel が現在の状態との差分を検出し、自動的にデリバリーパイプラインを実行して望ましい状態に収束させる。
Delivery Config の例:
name: myapp-delivery-config
application: myapp
serviceAccount: myapp-service-account@spinnaker
artifacts:
- name: myapp-docker
type: docker
reference: myorg/myapp
tagVersionStrategy: semver
from:
branch:
name: main
regex: false
environments:
- name: testing
constraints: []
notifications:
- type: slack
address: "#myapp-deploys"
frequency: verbose
locations:
account: staging-k8s
regions:
- name: us-east-1
resources:
- kind: titus/cluster@v1
spec:
moniker:
app: myapp
stack: testing
container:
organization: myorg
image: myapp
capacity:
min: 1
max: 3
desired: 2
- name: production
constraints:
- type: depends-on
environment: testing
- type: allowed-times
windows:
- days: Monday-Friday
hours: 09-17
tz: Asia/Tokyo
- type: manual-judgement
timeout: 4h
notifications:
- type: slack
address: "#myapp-production"
frequency: verbose
locations:
account: production-k8s
regions:
- name: us-east-1
- name: us-west-2
resources:
- kind: titus/cluster@v1
spec:
moniker:
app: myapp
stack: production
container:
organization: myorg
image: myapp
capacity:
min: 3
max: 10
desired: 5
2.2.12 Halyard(Configuration Management)
Halyard は Spinnaker 自体のデプロイメントと設定管理を行うツールである。CLI インターフェースを提供し、Spinnaker の各マイクロサービスの設定生成、バージョン管理、デプロイメントを統合的に管理する。
Halyard CLI の主要コマンド:
# Spinnaker のバージョン設定
hal config version edit --version 1.32.0
# Kubernetes プロバイダーの追加
hal config provider kubernetes enable
hal config provider kubernetes account add production-k8s \
--context production-context \
--kubeconfig-file /opt/spinnaker/kubeconfig/production \
--provider-version v2 \
--namespaces default,production \
--only-spinnaker-managed true
# Docker レジストリの追加
hal config provider docker-registry enable
hal config provider docker-registry account add docker-hub \
--address index.docker.io \
--repositories myorg/myapp \
--username ${DOCKER_USERNAME} \
--password
# 永続化ストレージの設定
hal config storage s3 edit \
--bucket spinnaker-data \
--region us-east-1 \
--root-folder spinnaker
hal config storage edit --type s3
# カナリア分析の有効化
hal config canary enable
hal config canary prometheus enable
hal config canary prometheus account add canary-prometheus \
--base-url http://prometheus:9090
# 通知の設定
hal config notification slack enable
hal config notification slack edit \
--bot-name spinnaker-bot \
--token ${SLACK_TOKEN}
# Spinnaker のデプロイ
hal deploy apply
2.3 データフローとサービス間通信
Spinnaker のマイクロサービス間の通信は、主に HTTP/REST API を介して行われる。また、非同期イベント処理には Redis Pub/Sub または Google Cloud Pub/Sub が使用される。
典型的なデプロイメントリクエストのフロー:
1. ユーザーが Deck (UI) でパイプライン実行をトリガー
│
▼
2. Gate (API GW) がリクエストを受信し認証・認可を処理
├─ Fiat に認可チェックを委譲
│
▼
3. Orca (Orchestrator) がパイプライン実行を開始
├─ Front50 からパイプライン定義を取得
├─ 各ステージを順次/並列で実行
│
▼
4. 各ステージの処理
├─ Bake ステージ → Rosco にイメージ作成を依頼
├─ Find Image ステージ → Clouddriver にイメージ検索
├─ Deploy ステージ → Clouddriver にデプロイ操作を依頼
├─ Canary ステージ → Kayenta にカナリア分析を依頼
├─ Webhook ステージ → 外部サービスへHTTPリクエスト
├─ Manual Judgment → Echo 経由で通知、ユーザー入力待ち
│
▼
5. Echo がイベントを処理
├─ Slack/Email/PagerDuty への通知送信
├─ パイプライントリガーの評価(連鎖パイプライン)
│
▼
6. 完了 → 結果が Deck に表示
2.4 キャッシュアーキテクチャ
Clouddriver は、クラウドプロバイダーから取得したインフラストラクチャ情報をキャッシュに保持する。これにより、API 呼び出しの回数を削減し、UI のレスポンス性能を向上させる。
キャッシュの仕組み:
# Clouddriver のキャッシュ設定
redis:
enabled: true
connection: redis://redis-host:6379
sql:
enabled: true
taskRepository:
enabled: true
cache:
enabled: true
readBatchSize: 500
writeBatchSize: 300
connectionPools:
default:
default: true
jdbcUrl: jdbc:mysql://mysql-host:3306/clouddriver?useSSL=true
user: clouddriver_service
password: ${MYSQL_PASSWORD}
connectionTimeout: 5000
maxLifetime: 30000
maxPoolSize: 50
tasks:
jdbcUrl: jdbc:mysql://mysql-host:3306/clouddriver_tasks?useSSL=true
user: clouddriver_service
password: ${MYSQL_PASSWORD}
migration:
jdbcUrl: jdbc:mysql://mysql-host:3306/clouddriver?useSSL=true
user: clouddriver_migrate
password: ${MYSQL_MIGRATE_PASSWORD}
caching:
writeEnabled: true
agentScheduler:
parallelism: 4
kubernetes:
v2:
cacheThreads: 4
キャッシングエージェント:
Clouddriver は「キャッシングエージェント」と呼ばれるコンポーネントを使用して、定期的にクラウドプロバイダーの状態をポーリングし、ローカルキャッシュを更新する。各クラウドプロバイダーに対して専用のエージェントが存在し、以下のようなリソースタイプごとに独立して動作する。
- Server Groups(サーバーグループ)
- Instances(インスタンス)
- Load Balancers(ロードバランサー)
- Security Groups(セキュリティグループ)
- Networks(ネットワーク)
- Subnets(サブネット)
- Images(イメージ)
3. パイプラインの詳細
3.1 パイプラインの基本概念
Spinnaker のパイプラインは、アプリケーションのデリバリープロセスを定義するワークフローである。パイプラインは一連の「ステージ」で構成され、各ステージは特定のアクション(デプロイ、テスト、承認など)を実行する。
パイプラインの主要な構成要素:
- Configuration(設定): パイプライン全体の設定(トリガー、パラメータ、通知、期待されるアーティファクトなど)
- Stages(ステージ): 個々のアクションを定義する処理単位
- Triggers(トリガー): パイプラインの自動起動条件
- Parameters(パラメータ): パイプライン実行時に指定する変数
- Notifications(通知): パイプラインの状態変化時の通知設定
- Artifacts(アーティファクト): パイプラインで使用される成果物の定義
3.2 パイプライントリガー
Spinnaker は多様なトリガーメカニズムをサポートしている。
Docker レジストリトリガー
{
"type": "docker",
"enabled": true,
"account": "docker-registry",
"organization": "myorg",
"registry": "index.docker.io",
"repository": "myorg/myapp",
"tag": "v.*",
"expectedArtifactIds": ["docker-image-artifact"]
}
Git トリガー(GitHub Webhook)
{
"type": "git",
"enabled": true,
"source": "github",
"project": "myorg",
"slug": "myapp",
"branch": "main",
"expectedArtifactIds": ["github-file-artifact"]
}
Jenkins トリガー
{
"type": "jenkins",
"enabled": true,
"master": "main-jenkins",
"job": "myapp-build",
"propertyFile": "spinnaker.properties",
"expectedArtifactIds": ["build-artifact"]
}
CRON スケジュールトリガー
{
"type": "cron",
"enabled": true,
"cronExpression": "0 0 9 ? * MON-FRI *",
"runAsUser": "myapp-service-account@spinnaker"
}
Pub/Sub トリガー
{
"type": "pubsub",
"enabled": true,
"pubsubSystem": "google",
"subscriptionName": "spinnaker-gcr-triggers",
"expectedArtifactIds": ["gcr-image-artifact"],
"attributeConstraints": {
"action": "INSERT",
"tag": "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
}
}
Webhook トリガー
{
"type": "webhook",
"enabled": true,
"source": "custom-webhook",
"payloadConstraints": {
"environment": "production",
"status": "approved"
}
}
3.3 ステージタイプの詳細
Spinnaker は豊富なステージタイプを提供している。
3.3.1 Deploy(デプロイ)ステージ
最も基本的なステージであり、アプリケーションをクラウドインフラストラクチャにデプロイする。
Kubernetes Manifest Deploy の例:
{
"type": "deployManifest",
"name": "Deploy to Kubernetes",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"moniker": {
"app": "myapp"
},
"source": "artifact",
"manifestArtifactId": "k8s-manifest-artifact",
"manifestArtifactAccount": "github-artifact-account",
"namespaceOverride": "production",
"skipExpressionEvaluation": false,
"trafficManagement": {
"enabled": true,
"options": {
"enableTraffic": true,
"services": ["service myapp-service"],
"strategy": "redblack"
}
},
"requiredArtifactIds": ["docker-image-artifact"],
"stageTimeoutMs": 600000
}
使用する Kubernetes マニフェスト例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
labels:
app: myapp
version: "${trigger.artifacts[0].version}"
annotations:
moniker.spinnaker.io/application: myapp
moniker.spinnaker.io/cluster: myapp-production
traffic.spinnaker.io/load-balancers: '["service myapp-service"]'
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: myapp
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app: myapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
containers:
- name: myapp
image: myorg/myapp # Spinnaker がアーティファクトバインディングで置換
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
env:
- name: ENVIRONMENT
value: "production"
- name: LOG_LEVEL
value: "INFO"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname
3.3.2 Bake(ベイク)ステージ
VMイメージを作成するステージ。Rosco を介して Packer を実行する。
{
"type": "bake",
"name": "Bake AMI",
"cloudProviderType": "aws",
"region": "us-east-1",
"baseOs": "ubuntu",
"baseLabel": "release",
"vmType": "hvm",
"storeType": "ebs",
"package": "myapp",
"rebake": false,
"enhancedNetworking": true,
"amiName": "myapp-${trigger.buildInfo.number}",
"templateFileName": "aws-ebs.json",
"extendedAttributes": {
"aws_instance_type": "t3.medium",
"aws_region": "us-east-1",
"aws_source_ami_filter_name": "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*",
"aws_source_ami_filter_owners": "099720109477"
}
}
3.3.3 Manual Judgment(手動承認)ステージ
人間の判断・承認を必要とするステージ。本番デプロイ前のゲートとして使用される。
{
"type": "manualJudgment",
"name": "Production Deployment Approval",
"instructions": "## Production Deployment Review\n\n### Changes:\n- ${trigger.buildInfo.url}\n- Commit: ${trigger.buildInfo.scm[0].sha1}\n\n### Pre-deployment Checklist:\n- [ ] Staging environment tests passed\n- [ ] Performance regression tests completed\n- [ ] Database migration reviewed\n- [ ] Rollback plan confirmed\n\n### Approve or reject this deployment.",
"judgmentInputs": [
{ "value": "approve-full" },
{ "value": "approve-canary-first" },
{ "value": "reject" }
],
"propagateAuthenticationContext": true,
"failPipeline": true,
"notifications": [
{
"type": "slack",
"address": "#release-approvals",
"level": "stage",
"when": ["manualJudgment"]
},
{
"type": "email",
"address": "release-managers@example.com",
"level": "stage",
"when": ["manualJudgment"]
}
],
"stageTimeoutMs": 259200000,
"sendNotifications": true
}
3.3.4 Wait(待機)ステージ
指定した時間だけパイプラインの実行を一時停止するステージ。
{
"type": "wait",
"name": "Wait for DNS Propagation",
"waitTime": 300,
"skipWaitText": "DNS propagation confirmed manually"
}
3.3.5 Webhook ステージ
外部の HTTP エンドポイントを呼び出すステージ。
{
"type": "webhook",
"name": "Run Integration Tests",
"url": "https://ci.example.com/api/v1/jobs/integration-tests/trigger",
"method": "POST",
"customHeaders": {
"Authorization": "Bearer ${#stage('Get CI Token').outputs.token}",
"Content-Type": "application/json"
},
"payload": {
"environment": "staging",
"image": "${deployedImageTag}",
"testSuite": "smoke",
"callbackUrl": "${execution.trigger.parameters.callbackUrl}"
},
"waitForCompletion": true,
"statusUrlResolution": "getMethod",
"statusUrlJsonPath": "$.status_url",
"statusJsonPath": "$.status",
"progressJsonPath": "$.progress",
"successStatuses": "PASSED,SUCCESS",
"retryStatuses": "RUNNING,PENDING",
"terminalStatuses": "FAILED,ERROR,TIMEOUT",
"canceledStatuses": "CANCELLED",
"retryCount": 3,
"retryBackoffPeriod": 5000
}
3.3.6 Run Job(ジョブ実行)ステージ
Kubernetes Job を実行するステージ。データベースマイグレーション、バッチ処理などに使用される。
{
"type": "runJobManifest",
"name": "Database Migration",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"source": "text",
"manifest": {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "db-migration-${execution.id}",
"namespace": "production",
"labels": {
"app": "myapp",
"type": "migration"
}
},
"spec": {
"backoffLimit": 0,
"activeDeadlineSeconds": 600,
"template": {
"spec": {
"restartPolicy": "Never",
"containers": [
{
"name": "migration",
"image": "myorg/myapp-migrations:${trigger.artifacts[0].version}",
"command": ["./migrate.sh"],
"args": ["--direction", "up", "--target", "latest"],
"env": [
{
"name": "DATABASE_URL",
"valueFrom": {
"secretKeyRef": {
"name": "db-credentials",
"key": "url"
}
}
}
]
}
]
}
}
}
},
"consumeArtifactSource": "none",
"propertyFile": "",
"alias": "runJobManifest"
}
3.3.7 Evaluate Variables(変数評価)ステージ
SpEL 式を評価して変数を設定するステージ。
{
"type": "evaluateVariables",
"name": "Set Deployment Parameters",
"variables": [
{
"key": "targetCluster",
"value": "${trigger.parameters.environment == 'production' ? 'prod-cluster' : 'staging-cluster'}"
},
{
"key": "replicaCount",
"value": "${trigger.parameters.environment == 'production' ? 5 : 2}"
},
{
"key": "deploymentTimestamp",
"value": "${new java.text.SimpleDateFormat('yyyy-MM-dd_HH-mm-ss').format(new java.util.Date())}"
},
{
"key": "imageTag",
"value": "${trigger.artifacts.?[type == 'docker/image'][0].version ?: 'latest'}"
}
]
}
3.3.8 Check Preconditions(前提条件チェック)ステージ
デプロイメント前の条件チェックを行うステージ。
{
"type": "checkPreconditions",
"name": "Verify Deployment Conditions",
"preconditions": [
{
"type": "expression",
"context": {
"expression": "${trigger.buildInfo.result == 'SUCCESS'}",
"failureMessage": "Build must be successful before deployment"
},
"failPipeline": true
},
{
"type": "clusterSize",
"context": {
"credentials": "production-k8s",
"cluster": "myapp-production",
"comparison": ">=",
"expected": 2,
"regions": ["production"],
"moniker": {
"app": "myapp",
"cluster": "myapp-production"
}
},
"failPipeline": true
},
{
"type": "stageStatus",
"context": {
"stageName": "Integration Tests",
"stageStatus": "SUCCEEDED"
},
"failPipeline": true
}
]
}
3.3.9 Pipeline(パイプライン呼び出し)ステージ
他のパイプラインを呼び出すステージ。パイプラインの再利用とモジュール化を促進する。
{
"type": "pipeline",
"name": "Run Smoke Tests Pipeline",
"application": "myapp",
"pipeline": "smoke-tests-pipeline-id",
"pipelineParameters": {
"environment": "${trigger.parameters.environment}",
"imageTag": "${deployedImageTag}",
"testTimeout": "300"
},
"waitForCompletion": true,
"failPipeline": true
}
3.4 パイプラインの完全な構成例
以下は、本番環境へのカナリアデプロイメントを含む完全なパイプライン定義の例である。
{
"name": "Production Canary Deployment",
"application": "myapp",
"description": "Canary deployment pipeline with automated analysis",
"keepWaitingPipelines": false,
"limitConcurrent": true,
"maxConcurrentExecutions": 1,
"locked": {
"ui": true,
"description": "Pipeline locked - changes require review",
"allowUnlockUi": true
},
"parameterConfig": [
{
"name": "imageTag",
"label": "Image Tag",
"required": true,
"description": "Docker image tag to deploy",
"hasOptions": false,
"pinned": true
},
{
"name": "canaryWeight",
"label": "Canary Traffic Weight (%)",
"required": false,
"default": "10",
"description": "Percentage of traffic to route to canary",
"hasOptions": true,
"options": [
{ "value": "5" },
{ "value": "10" },
{ "value": "25" }
]
},
{
"name": "analysisMinutes",
"label": "Analysis Duration (minutes)",
"required": false,
"default": "30",
"description": "Duration of canary analysis"
}
],
"notifications": [
{
"type": "slack",
"address": "#deployments",
"level": "pipeline",
"when": ["pipeline.starting", "pipeline.complete", "pipeline.failed"]
}
],
"triggers": [
{
"type": "docker",
"enabled": true,
"account": "docker-registry",
"organization": "myorg",
"registry": "index.docker.io",
"repository": "myorg/myapp",
"tag": "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
}
],
"expectedArtifacts": [
{
"id": "docker-image-artifact",
"displayName": "Docker Image",
"matchArtifact": {
"type": "docker/image",
"name": "myorg/myapp"
},
"useDefaultArtifact": false,
"usePriorArtifact": false
}
],
"stages": [
{
"refId": "1",
"requisiteStageRefIds": [],
"type": "checkPreconditions",
"name": "Pre-flight Checks",
"preconditions": [
{
"type": "expression",
"context": {
"expression": "${trigger.type == 'docker' || trigger.type == 'manual'}",
"failureMessage": "Invalid trigger type"
},
"failPipeline": true
}
]
},
{
"refId": "2",
"requisiteStageRefIds": ["1"],
"type": "deployManifest",
"name": "Deploy Canary",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"source": "text",
"manifests": [
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "myapp-canary",
"namespace": "production"
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "myapp",
"track": "canary"
}
},
"template": {
"metadata": {
"labels": {
"app": "myapp",
"track": "canary"
}
},
"spec": {
"containers": [
{
"name": "myapp",
"image": "myorg/myapp",
"ports": [{ "containerPort": 8080 }]
}
]
}
}
}
}
],
"requiredArtifactIds": ["docker-image-artifact"]
},
{
"refId": "3",
"requisiteStageRefIds": ["2"],
"type": "kayentaCanary",
"name": "Canary Analysis",
"analysisType": "realTime",
"canaryConfig": {
"canaryAnalysisIntervalMins": 10,
"canaryConfigId": "canary-config-id",
"lifetimeDuration": "PT${trigger.parameters.analysisMinutes ?: 30}M",
"metricsAccountName": "canary-prometheus",
"scoreThresholds": {
"marginal": 50,
"pass": 75
},
"storageAccountName": "canary-s3",
"scopes": [
{
"controlScope": "myapp-production-baseline",
"experimentScope": "myapp-production-canary",
"scopeName": "default",
"extendedScopeParams": {
"namespace": "production"
}
}
]
}
},
{
"refId": "4",
"requisiteStageRefIds": ["3"],
"type": "manualJudgment",
"name": "Approve Full Rollout",
"instructions": "Canary analysis passed. Approve full production rollout?",
"judgmentInputs": [
{ "value": "proceed" },
{ "value": "rollback" }
],
"stageTimeoutMs": 86400000
},
{
"refId": "5",
"requisiteStageRefIds": ["4"],
"type": "deployManifest",
"name": "Full Production Deploy",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"source": "artifact",
"manifestArtifactId": "k8s-production-manifest",
"requiredArtifactIds": ["docker-image-artifact"],
"trafficManagement": {
"enabled": true,
"options": {
"enableTraffic": true,
"strategy": "redblack",
"services": ["service myapp-service"],
"rollback": {
"onFailure": true
}
}
}
},
{
"refId": "6",
"requisiteStageRefIds": ["5"],
"type": "deleteManifest",
"name": "Cleanup Canary",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"location": "production",
"manifestName": "deployment myapp-canary",
"options": {
"cascading": true
}
},
{
"refId": "7",
"requisiteStageRefIds": ["5"],
"type": "wait",
"name": "Monitoring Period",
"waitTime": 900
},
{
"refId": "8",
"requisiteStageRefIds": ["7"],
"type": "webhook",
"name": "Notify Monitoring System",
"url": "https://monitoring.example.com/api/v1/deployments",
"method": "POST",
"payload": {
"application": "myapp",
"version": "${trigger.artifacts[0].version}",
"environment": "production",
"status": "completed",
"timestamp": "${execution.startTime}"
}
}
]
}
3.5 Pipeline Templates(パイプラインテンプレート)
Spinnaker はパイプラインテンプレートを使用して、組織全体で標準化されたパイプラインパターンを共有・再利用できる。
テンプレート定義例:
{
"schema": "v2",
"id": "standard-k8s-deploy-template",
"metadata": {
"name": "Standard Kubernetes Deployment",
"description": "Organization standard deployment pipeline for Kubernetes",
"owner": "platform-team@example.com",
"scopes": ["global"]
},
"protect": true,
"variables": [
{
"name": "applicationName",
"description": "Application name",
"type": "string"
},
{
"name": "kubernetesAccount",
"description": "Kubernetes account to deploy to",
"type": "string",
"defaultValue": "production-k8s"
},
{
"name": "namespace",
"description": "Target namespace",
"type": "string",
"defaultValue": "default"
},
{
"name": "notificationChannel",
"description": "Slack channel for notifications",
"type": "string",
"defaultValue": "#deployments"
},
{
"name": "requireApproval",
"description": "Require manual approval before deploy",
"type": "boolean",
"defaultValue": true
}
],
"pipeline": {
"name": "Deploy {{ applicationName }}",
"application": "{{ applicationName }}",
"keepWaitingPipelines": false,
"limitConcurrent": true,
"stages": [
{
"refId": "1",
"type": "checkPreconditions",
"name": "Validate"
},
{
"refId": "2",
"requisiteStageRefIds": ["1"],
"type": "manualJudgment",
"name": "Approve Deployment",
"when": "{{ requireApproval }}"
},
{
"refId": "3",
"requisiteStageRefIds": ["{{ requireApproval ? '2' : '1' }}"],
"type": "deployManifest",
"name": "Deploy",
"account": "{{ kubernetesAccount }}",
"namespaceOverride": "{{ namespace }}"
}
]
}
}
テンプレートの使用例:
{
"schema": "v2",
"template": {
"artifactAccount": "front50ArtifactCredentials",
"reference": "spinnaker://standard-k8s-deploy-template",
"type": "front50/pipelineTemplate"
},
"type": "templatedPipeline",
"application": "myapp",
"name": "MyApp Production Deploy",
"variables": {
"applicationName": "myapp",
"kubernetesAccount": "production-k8s",
"namespace": "production",
"notificationChannel": "#myapp-deploys",
"requireApproval": true
},
"inherit": [],
"exclude": []
}
4. デプロイメント戦略
4.1 概要
Spinnaker は、アプリケーションのリリースリスクを最小化するための複数のデプロイメント戦略を提供している。各戦略にはそれぞれの利点とトレードオフがあり、アプリケーションの特性やリスク許容度に応じて適切な戦略を選択する必要がある。
4.2 Red/Black(Blue/Green)デプロイメント
Red/Black デプロイメント(一般的には Blue/Green デプロイメントとも呼ばれる)は、新しいバージョンのアプリケーションを既存の環境と並行してデプロイし、トラフィックを一度に切り替える戦略である。
動作フロー:
時刻T1: 既存バージョン (v1) が全トラフィックを処理
┌─────────────┐ ┌──────────────┐
│ Load Balancer│────→│ v1 (Active) │ ← 100% トラフィック
└─────────────┘ └──────────────┘
時刻T2: 新バージョン (v2) をデプロイ(トラフィックなし)
┌─────────────┐ ┌──────────────┐
│ Load Balancer│────→│ v1 (Active) │ ← 100% トラフィック
└─────────────┘ ┌──────────────┐
│ v2 (New) │ ← 0% トラフィック
└──────────────┘
時刻T3: ヘルスチェック通過後、トラフィックを切替
┌─────────────┐ ┌──────────────┐
│ Load Balancer│────→│ v2 (Active) │ ← 100% トラフィック
└─────────────┘ ┌──────────────┐
│ v1 (Previous)│ ← 0%(保持/削除)
└──────────────┘
Kubernetes での Red/Black 設定例:
{
"type": "deployManifest",
"name": "Red/Black Deploy",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"trafficManagement": {
"enabled": true,
"options": {
"enableTraffic": true,
"strategy": "redblack",
"services": ["service myapp-service"],
"rollback": {
"onFailure": true
},
"maxRemainingAsgs": 2
}
}
}
AWS での Red/Black 設定例:
{
"type": "deploy",
"name": "Red/Black Deploy to AWS",
"clusters": [
{
"account": "production-aws",
"application": "myapp",
"availabilityZones": {
"us-east-1": ["us-east-1a", "us-east-1b", "us-east-1c"]
},
"capacity": {
"desired": 3,
"max": 6,
"min": 3
},
"cooldown": 300,
"healthCheckType": "ELB",
"healthCheckGracePeriod": 600,
"strategy": "redblack",
"maxRemainingAsgs": 2,
"rollback": {
"onFailure": true
},
"scaleDown": true,
"delayBeforeScaleDownSec": 600,
"interestingHealthProviderNames": ["Amazon"],
"instanceType": "m5.large",
"keyPair": "myapp-keypair",
"loadBalancers": ["myapp-production-lb"],
"targetGroups": ["myapp-production-tg"],
"securityGroups": ["sg-myapp-production"],
"subnetType": "internal",
"iamRole": "myapp-instance-role",
"tags": {
"Environment": "production",
"Application": "myapp",
"ManagedBy": "spinnaker"
}
}
]
}
4.3 カナリアデプロイメント
カナリアデプロイメントは、新バージョンをトラフィックの一部にのみ公開し、メトリクスを監視して問題がないことを確認してから段階的にロールアウトする戦略である。
カナリア分析の構成:
┌──────────────────────────────────────────────────────────┐
│ Canary Deployment Flow │
│ │
│ Phase 1: Deploy Canary (5% traffic) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────────┐│
│ │Baseline │ │ Canary │ │ Production (v1) ││
│ │(v1 copy) │ │ (v2) │ │ 95% traffic ││
│ │5% traffic│ │5% traffic│ │ ││
│ └──────────┘ └──────────┘ └──────────────────────────┘│
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ │
│ │ Kayenta Analysis │ │
│ │ - Latency metrics │ │
│ │ - Error rates │ │
│ │ - CPU/Memory usage │ │
│ │ - Custom metrics │ │
│ └─────────────────────────┘ │
│ │ │
│ ▼ │
│ Score >= 75? ─── Yes ──→ Phase 2: Expand (25% traffic) │
│ │ │
│ No ──→ Rollback │
│ │
│ Phase 2: Analysis continues... │
│ Score >= 75? ─── Yes ──→ Phase 3: Full rollout (100%) │
└──────────────────────────────────────────────────────────┘
Kayenta カナリア設定の詳細例:
{
"canaryConfig": {
"name": "MyApp Canary Config",
"description": "Canary analysis configuration for MyApp",
"judge": {
"name": "NetflixACAJudge-v1.0",
"judgeConfigurations": {}
},
"metrics": [
{
"name": "Request Latency (p99)",
"query": {
"type": "prometheus",
"customInlineTemplate": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{app=\"myapp\",namespace=\"${scope}\"}[5m])) by (le))",
"serviceType": "prometheus"
},
"groups": ["Latency"],
"analysisConfigurations": {
"canary": {
"direction": "increase",
"nanStrategy": "replace",
"critical": true,
"mustHaveData": true,
"effectSize": {
"allowedIncrease": 0.1,
"criticalIncrease": 0.5
}
}
},
"scopeName": "default"
},
{
"name": "Error Rate (5xx)",
"query": {
"type": "prometheus",
"customInlineTemplate": "sum(rate(http_requests_total{app=\"myapp\",namespace=\"${scope}\",status=~\"5..\"}[5m])) / sum(rate(http_requests_total{app=\"myapp\",namespace=\"${scope}\"}[5m]))",
"serviceType": "prometheus"
},
"groups": ["Errors"],
"analysisConfigurations": {
"canary": {
"direction": "increase",
"nanStrategy": "replace",
"critical": true,
"mustHaveData": true,
"effectSize": {
"allowedIncrease": 0.01,
"criticalIncrease": 0.05
}
}
},
"scopeName": "default"
},
{
"name": "CPU Usage",
"query": {
"type": "prometheus",
"customInlineTemplate": "avg(rate(container_cpu_usage_seconds_total{pod=~\"myapp.*\",namespace=\"${scope}\"}[5m]))",
"serviceType": "prometheus"
},
"groups": ["Resources"],
"analysisConfigurations": {
"canary": {
"direction": "either",
"nanStrategy": "replace",
"critical": false,
"mustHaveData": false
}
},
"scopeName": "default"
},
{
"name": "Memory Usage",
"query": {
"type": "prometheus",
"customInlineTemplate": "avg(container_memory_working_set_bytes{pod=~\"myapp.*\",namespace=\"${scope}\"}) / avg(kube_pod_container_resource_limits{pod=~\"myapp.*\",namespace=\"${scope}\",resource=\"memory\"})",
"serviceType": "prometheus"
},
"groups": ["Resources"],
"analysisConfigurations": {
"canary": {
"direction": "increase",
"nanStrategy": "replace",
"critical": false,
"mustHaveData": false
}
},
"scopeName": "default"
},
{
"name": "Throughput (RPS)",
"query": {
"type": "prometheus",
"customInlineTemplate": "sum(rate(http_requests_total{app=\"myapp\",namespace=\"${scope}\"}[5m]))",
"serviceType": "prometheus"
},
"groups": ["Performance"],
"analysisConfigurations": {
"canary": {
"direction": "decrease",
"nanStrategy": "replace",
"critical": false,
"mustHaveData": true
}
},
"scopeName": "default"
}
],
"classifier": {
"groupWeights": {
"Latency": 40,
"Errors": 40,
"Resources": 10,
"Performance": 10
},
"scoreThresholds": {
"marginal": 50,
"pass": 75
}
},
"configVersion": "1"
}
}
4.4 ローリングアップデート
ローリングアップデートは、インスタンスを段階的に新バージョンに更新していく戦略である。Kubernetes のネイティブなローリングアップデート戦略を活用する。
Kubernetes マニフェストでの設定:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
annotations:
strategy.spinnaker.io/max-version-history: "3"
strategy.spinnaker.io/use-source-capacity: "true"
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
template:
spec:
containers:
- name: myapp
image: myorg/myapp
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
terminationGracePeriodSeconds: 30
4.5 Highlander 戦略
Highlander 戦略は Red/Black の変形で、新バージョンのデプロイ成功後に旧バージョンを即座に削除する。「There can be only one(ただ一つだけ存在できる)」という原則に基づく。
{
"type": "deploy",
"clusters": [
{
"strategy": "highlander",
"application": "myapp",
"account": "production-k8s"
}
]
}
4.6 カスタムデプロイメント戦略
Spinnaker のパイプラインステージを組み合わせて、独自のデプロイメント戦略を構築できる。以下は、段階的カナリアと手動承認を組み合わせた複合戦略の例である。
Stage 1: Deploy Canary (1%)
│
▼
Stage 2: Automated Canary Analysis (15 min)
│
├── Pass → Stage 3
└── Fail → Rollback & Alert
│
▼
Stage 3: Scale Canary to 10%
│
▼
Stage 4: Automated Canary Analysis (30 min)
│
├── Pass → Stage 5
└── Fail → Rollback & Alert
│
▼
Stage 5: Manual Approval
│
├── Approve → Stage 6
└── Reject → Rollback
│
▼
Stage 6: Scale Canary to 50%
│
▼
Stage 7: Automated Canary Analysis (30 min)
│
├── Pass → Stage 8
└── Fail → Rollback & Alert
│
▼
Stage 8: Full Production Rollout (100%)
│
▼
Stage 9: Cleanup Canary Resources
│
▼
Stage 10: Post-deployment Verification
5. アーティファクト管理
5.1 アーティファクトの概念
Spinnaker におけるアーティファクトとは、パイプラインで使用される任意のデプロイ可能なリソースを指す。Docker イメージ、Kubernetes マニフェスト、Helm チャート、AWS AMI、設定ファイルなどが含まれる。
アーティファクトの主要な属性:
- type: アーティファクトの種類(
docker/image、kubernetes/configMap、helm/chartなど) - name: アーティファクトの識別名
- version: バージョン(タグ、ダイジェスト、コミットハッシュなど)
- reference: アーティファクトの完全な参照先
- location: アーティファクトの保存場所
5.2 サポートされるアーティファクトタイプ
# Docker イメージ
- type: docker/image
name: myorg/myapp
version: v1.2.3
reference: myorg/myapp:v1.2.3
# Kubernetes マニフェスト
- type: kubernetes/configMap
name: myapp-config
reference: gs://my-bucket/configs/myapp-config.yml
# Helm チャート
- type: helm/chart
name: myapp-chart
version: 1.0.0
reference: gs://helm-charts/myapp-chart-1.0.0.tgz
# GitHub ファイル
- type: github/file
name: deploy/kubernetes/deployment.yml
reference: https://api.github.com/repos/myorg/myapp/contents/deploy/kubernetes/deployment.yml
version: main
# GCS オブジェクト
- type: gcs/object
name: gs://my-bucket/artifacts/myapp.tar.gz
reference: gs://my-bucket/artifacts/myapp.tar.gz
# S3 オブジェクト
- type: s3/object
name: s3://my-bucket/artifacts/myapp.tar.gz
reference: s3://my-bucket/artifacts/myapp.tar.gz
# HTTP ファイル
- type: http/file
name: config.yml
reference: https://config-server.example.com/myapp/production/config.yml
# Embedded Base64
- type: embedded/base64
name: inline-config
reference: <base64-encoded-content>
# Maven アーティファクト
- type: maven/file
name: com.example:myapp:1.0.0
reference: com.example:myapp:1.0.0
5.3 アーティファクトアカウントの設定
GitHub アーティファクトアカウント:
artifacts:
github:
enabled: true
accounts:
- name: github-artifact-account
token: ${GITHUB_TOKEN}
usernamePasswordFile: /opt/spinnaker/credentials/github-creds
gitrepo:
enabled: true
accounts:
- name: gitrepo-account
token: ${GITHUB_TOKEN}
sshPrivateKeyFilePath: /opt/spinnaker/credentials/git-ssh-key
sshPrivateKeyPassphrase: ${SSH_PASSPHRASE}
sshKnownHostsFilePath: /opt/spinnaker/credentials/known_hosts
sshTrustUnknownHosts: false
gcs:
enabled: true
accounts:
- name: gcs-artifact-account
jsonPath: /opt/spinnaker/credentials/gcs-account.json
s3:
enabled: true
accounts:
- name: s3-artifact-account
region: us-east-1
apiEndpoint: https://s3.amazonaws.com
apiRegion: us-east-1
helm:
enabled: true
accounts:
- name: helm-stable
repository: https://charts.helm.sh/stable
- name: helm-private
repository: https://helm.example.com/charts
usernamePasswordFile: /opt/spinnaker/credentials/helm-creds
http:
enabled: true
accounts:
- name: http-artifact-account
usernamePasswordFile: /opt/spinnaker/credentials/http-creds
oracle:
enabled: false
bitbucket:
enabled: true
accounts:
- name: bitbucket-artifact-account
usernamePasswordFile: /opt/spinnaker/credentials/bitbucket-creds
5.4 Expected Artifacts とバインディング
Expected Artifacts は、パイプラインが期待するアーティファクトの定義であり、トリガーやステージの出力とマッチングさせることでアーティファクトバインディングを実現する。
Expected Artifact の定義例:
{
"expectedArtifacts": [
{
"id": "docker-image",
"displayName": "Application Docker Image",
"matchArtifact": {
"type": "docker/image",
"name": "myorg/myapp"
},
"defaultArtifact": {
"type": "docker/image",
"name": "myorg/myapp",
"version": "latest",
"reference": "myorg/myapp:latest"
},
"useDefaultArtifact": true,
"usePriorArtifact": true,
"usePriorExecution": false
},
{
"id": "k8s-manifest",
"displayName": "Kubernetes Deployment Manifest",
"matchArtifact": {
"type": "github/file",
"name": "deploy/k8s/deployment.yml"
},
"defaultArtifact": {
"type": "github/file",
"name": "deploy/k8s/deployment.yml",
"version": "main",
"reference": "https://api.github.com/repos/myorg/myapp/contents/deploy/k8s/deployment.yml"
},
"useDefaultArtifact": true,
"usePriorArtifact": false
},
{
"id": "helm-values",
"displayName": "Helm Values Override",
"matchArtifact": {
"type": "gcs/object",
"name": "gs://my-configs/myapp/values-production.yml"
},
"useDefaultArtifact": false,
"usePriorArtifact": true
}
]
}
5.5 Helm チャートのデプロイ
Spinnaker は Helm チャートのネイティブサポートを提供している。
Bake (Manifest) ステージでの Helm テンプレートレンダリング:
{
"type": "bakeManifest",
"name": "Bake Helm Chart",
"templateRenderer": "HELM3",
"inputArtifacts": [
{
"account": "helm-private",
"id": "helm-chart-artifact"
},
{
"account": "gcs-artifact-account",
"id": "helm-values-artifact"
}
],
"outputName": "myapp-manifests",
"namespace": "production",
"overrides": {
"image.repository": "myorg/myapp",
"image.tag": "${trigger.artifacts[0].version}",
"replicaCount": "3",
"resources.requests.cpu": "500m",
"resources.requests.memory": "512Mi",
"ingress.enabled": "true",
"ingress.hosts[0].host": "myapp.example.com",
"ingress.hosts[0].paths[0]": "/"
},
"rawOverrides": false,
"kustomize": false,
"helmChartFilePath": "",
"evaluateOverrideExpressions": true
}
Helm の values ファイル例(values-production.yml):
replicaCount: 3
image:
repository: myorg/myapp
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
monitoring:
enabled: true
serviceMonitor:
interval: 30s
path: /metrics
env:
- name: ENVIRONMENT
value: production
- name: LOG_LEVEL
value: INFO
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
6. セキュリティと認証・認可
6.1 認証(Authentication)
Spinnaker は複数の認証メカニズムをサポートしている。
OAuth 2.0 / OpenID Connect
# gate.yml
security:
oauth2:
enabled: true
client:
clientId: ${OAUTH_CLIENT_ID}
clientSecret: ${OAUTH_CLIENT_SECRET}
accessTokenUri: https://oauth2.googleapis.com/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
scope: openid email profile
resource:
userInfoUri: https://openidconnect.googleapis.com/v1/userinfo
userInfoMapping:
email: email
firstName: given_name
lastName: family_name
username: email
SAML 2.0
# gate.yml
saml:
enabled: true
metadataUrl: https://idp.example.com/metadata
keyStore: /opt/spinnaker/certs/saml-keystore.jks
keyStorePassword: ${SAML_KEYSTORE_PASSWORD}
keyStoreAliasName: saml
issuerId: spinnaker.example.com
redirectProtocol: https
redirectHostname: gate.spinnaker.example.com
redirectBasePath: /
serviceAddress: https://gate.spinnaker.example.com
userAttributeMapping:
firstName: firstName
lastName: lastName
email: email
roles: groups
LDAP
# gate.yml
ldap:
enabled: true
url: ldaps://ldap.example.com:636
userDnPattern: uid={0},ou=users,dc=example,dc=com
userSearchBase: ou=users,dc=example,dc=com
userSearchFilter: (uid={0})
groupSearchBase: ou=groups,dc=example,dc=com
groupSearchFilter: (member={0})
groupRoleAttributes: cn
managerDn: cn=spinnaker-bind,ou=services,dc=example,dc=com
managerPassword: ${LDAP_BIND_PASSWORD}
x509 クライアント証明書
# gate.yml
x509:
enabled: true
subjectPrincipalRegex: CN=(.*?)(?:,|$)
roleOid: 1.2.840.10070.8.1
server:
ssl:
enabled: true
clientAuth: want
keyStore: /opt/spinnaker/certs/server-keystore.p12
keyStorePassword: ${SERVER_KEYSTORE_PASSWORD}
trustStore: /opt/spinnaker/certs/truststore.jks
trustStorePassword: ${TRUSTSTORE_PASSWORD}
6.2 認可(Authorization)- RBAC
Fiat による RBAC の詳細設定例を示す。
アプリケーションレベルのパーミッション:
# fiat.yml
auth:
enabled: true
permissions:
source: APPLICATION
fallback: DENY
groupMembership:
service: EXTERNAL
# または LDAP, GITHUB, GOOGLE, FILE
# GitHub Teams ベースの認可
github:
organization: myorg
baseUrl: https://api.github.com
accessToken: ${GITHUB_TOKEN}
teams:
- name: platform-team
roles:
- spinnaker-admin
- name: backend-team
roles:
- engineering
- name: frontend-team
roles:
- engineering
- name: qa-team
roles:
- qa
アプリケーション設定での RBAC:
{
"name": "myapp",
"email": "myapp-team@example.com",
"description": "My Application",
"permissions": {
"READ": ["engineering", "qa", "management"],
"WRITE": ["engineering", "devops"],
"EXECUTE": ["engineering", "devops", "release-manager"]
},
"dataSources": {
"enabled": ["serverGroups", "executions", "loadBalancers", "securityGroups"],
"disabled": []
}
}
サービスアカウントの設定:
# サービスアカウントの作成
curl -X POST \
https://gate.spinnaker.example.com/serviceAccounts \
-H 'Content-Type: application/json' \
-d '{
"name": "myapp-cicd@spinnaker",
"memberOf": ["engineering", "devops"],
"description": "Service account for MyApp CI/CD pipelines"
}'
# 自動トリガー用パイプラインへのサービスアカウント設定
# パイプライン JSON 内で指定
{
"triggers": [
{
"type": "docker",
"enabled": true,
"runAsUser": "myapp-cicd@spinnaker"
}
]
}
6.3 ネットワークセキュリティ
TLS/SSL の設定:
# Spinnaker 全体の SSL 設定
server:
ssl:
enabled: true
keyStore: /opt/spinnaker/certs/server.p12
keyStoreType: PKCS12
keyStorePassword: ${KEYSTORE_PASSWORD}
trustStore: /opt/spinnaker/certs/truststore.jks
trustStoreType: JKS
trustStorePassword: ${TRUSTSTORE_PASSWORD}
ciphers:
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
enabledProtocols:
- TLSv1.2
- TLSv1.3
# サービス間通信の SSL
ok-http-client:
keyStore: /opt/spinnaker/certs/client.p12
keyStoreType: PKCS12
keyStorePassword: ${CLIENT_KEYSTORE_PASSWORD}
trustStore: /opt/spinnaker/certs/truststore.jks
trustStoreType: JKS
trustStorePassword: ${TRUSTSTORE_PASSWORD}
6.4 Secret 管理
Spinnaker は外部のシークレット管理システムとの統合をサポートしている。
HashiCorp Vault との統合:
# spinnaker-local.yml
secrets:
vault:
enabled: true
url: https://vault.example.com
path: secret/spinnaker
role: spinnaker
authMethod: KUBERNETES
namespace: spinnaker
engineVersion: 2
# 使用例
aws:
accounts:
- name: production
accessKeyId: encrypted:vault!e:secret!p:spinnaker/aws!k:access_key_id
secretAccessKey: encrypted:vault!e:secret!p:spinnaker/aws!k:secret_access_key
AWS Secrets Manager との統合:
secrets:
aws:
enabled: true
region: us-east-1
# 使用例
kubernetes:
accounts:
- name: production-k8s
kubeconfigContents: encrypted:aws-sm!r:us-east-1!s:spinnaker/kubeconfig-production
Google Secret Manager との統合:
secrets:
gcp:
enabled: true
project: my-gcp-project
# 使用例
slack:
token: encrypted:gcp-sm!p:my-gcp-project!s:spinnaker-slack-token
7. インストールとインフラストラクチャ
7.1 デプロイメント方式
Spinnaker のデプロイメント方式には主に3つのアプローチがある。
7.1.1 Halyard によるデプロイ(推奨)
Halyard は Spinnaker の公式インストール・管理ツールである。
Halyard のインストール:
# Linux へのインストール
curl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/debian/InstallHalyard.sh
sudo bash InstallHalyard.sh
# macOS へのインストール
curl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/macos/InstallHalyard.sh
sudo bash InstallHalyard.sh
# Docker を使用したインストール
docker run -d \
--name halyard \
-v /home/spinnaker/.hal:/home/spinnaker/.hal \
-v /home/spinnaker/.kube:/home/spinnaker/.kube \
us-docker.pkg.dev/spinnaker-community/docker/halyard:stable
# バージョン確認
hal version list
デプロイ先の設定:
# Kubernetes へのデプロイ(Distributed Installation)
hal config deploy edit \
--type distributed \
--account-name spinnaker-install-account
# ローカルデプロイ(Local Installation - 開発/テスト向け)
hal config deploy edit \
--type localdebian
完全なインストールフロー:
# 1. バージョンの選択
hal config version edit --version 1.32.0
# 2. ストレージバックエンドの設定
hal config storage s3 edit \
--bucket my-spinnaker-bucket \
--root-folder spinnaker \
--region us-east-1 \
--access-key-id ${AWS_ACCESS_KEY_ID} \
--secret-access-key
hal config storage edit --type s3
# 3. クラウドプロバイダーの設定
hal config provider kubernetes enable
hal config provider kubernetes account add my-k8s-account \
--provider-version v2 \
--context $(kubectl config current-context)
# 4. Docker レジストリの設定
hal config provider docker-registry enable
hal config provider docker-registry account add my-docker-reg \
--address index.docker.io \
--repositories myorg/myapp \
--username ${DOCKER_USERNAME} \
--password
# 5. CI 統合の設定
hal config ci jenkins enable
hal config ci jenkins master add my-jenkins \
--address https://jenkins.example.com \
--username admin \
--password
# 6. 認証の設定
hal config security authn oauth2 edit \
--client-id ${CLIENT_ID} \
--client-secret ${CLIENT_SECRET} \
--provider google
hal config security authn oauth2 enable
# 7. 認可の設定
hal config security authz enable
hal config security authz edit \
--type github \
--github-access-token ${GITHUB_TOKEN} \
--github-organization myorg
# 8. 通知の設定
hal config notification slack enable
hal config notification slack edit \
--bot-name spinnaker \
--token ${SLACK_TOKEN}
# 9. カナリアの有効化
hal config canary enable
hal config canary prometheus enable
hal config canary prometheus account add my-prometheus \
--base-url http://prometheus:9090
# 10. デプロイの実行
hal deploy apply
# 11. UI のポート公開(必要に応じて)
hal config security ui edit --override-base-url https://spinnaker.example.com
hal config security api edit --override-base-url https://gate.spinnaker.example.com
hal deploy apply
7.1.2 Spinnaker Operator(Kubernetes ネイティブ)
Spinnaker Operator は Kubernetes のカスタムリソースとして Spinnaker を管理する。
Operator のインストール:
# CRD のインストール
kubectl apply -f https://github.com/armory/spinnaker-operator/releases/latest/download/manifests.tgz
# Operator のデプロイ
kubectl create namespace spinnaker-operator
kubectl -n spinnaker-operator apply -f operator/deploy/
SpinnakerService カスタムリソースの定義:
apiVersion: spinnaker.armory.io/v1alpha2
kind: SpinnakerService
metadata:
name: spinnaker
namespace: spinnaker
spec:
spinnakerConfig:
config:
version: 1.32.0
persistentStorage:
persistentStoreType: s3
s3:
bucket: spinnaker-data
rootFolder: front50
region: us-east-1
deploymentEnvironment:
size: SMALL
type: Distributed
accountName: spinnaker-install
updateVersions: true
consul:
enabled: false
haServices:
clouddriver:
enabled: true
disableClouddriverRoDeck: false
echo:
enabled: true
security:
authn:
oauth2:
enabled: true
client:
clientId: ${OAUTH_CLIENT_ID}
clientSecret: ${OAUTH_CLIENT_SECRET}
accessTokenUri: https://oauth2.googleapis.com/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
scope: openid email profile
resource:
userInfoUri: https://openidconnect.googleapis.com/v1/userinfo
authz:
enabled: true
groupMembership:
service: GITHUB
github:
organization: myorg
accessToken: ${GITHUB_TOKEN}
providers:
kubernetes:
enabled: true
primaryAccount: production-k8s
accounts:
- name: production-k8s
providerVersion: V2
permissions:
READ:
- engineering
WRITE:
- devops
dockerRegistries:
- accountName: dockerhub
context: production
configureImagePullSecrets: true
cacheThreads: 2
namespaces:
- production
- staging
omitNamespaces: []
kinds: []
omitKinds: []
onlySpinnakerManaged: true
artifacts:
github:
enabled: true
accounts:
- name: github-artifact
token: ${GITHUB_TOKEN}
helm:
enabled: true
accounts:
- name: helm-stable
repository: https://charts.helm.sh/stable
notifications:
slack:
enabled: true
token: ${SLACK_TOKEN}
botName: spinnaker
canary:
enabled: true
serviceIntegrations:
- name: prometheus
enabled: true
accounts:
- name: canary-prometheus
endpoint:
baseUrl: http://prometheus.monitoring:9090
supportedTypes:
- METRICS_STORE
service-settings:
gate:
healthEndpoint: /health
kubernetes:
serviceType: ClusterIP
deck:
healthEndpoint: /
kubernetes:
serviceType: ClusterIP
clouddriver:
kubernetes:
serviceType: ClusterIP
orca:
kubernetes:
serviceType: ClusterIP
profiles:
gate:
server:
port: 8084
cors:
allowedOriginsPattern: ".*"
clouddriver:
sql:
enabled: true
connectionPools:
default:
jdbcUrl: jdbc:mysql://mysql:3306/clouddriver
user: clouddriver
password: ${MYSQL_PASSWORD}
orca:
sql:
enabled: true
connectionPools:
default:
jdbcUrl: jdbc:mysql://mysql:3306/orca
user: orca
password: ${MYSQL_PASSWORD}
front50:
sql:
enabled: true
connectionPools:
default:
jdbcUrl: jdbc:mysql://mysql:3306/front50
user: front50
password: ${MYSQL_PASSWORD}
expose:
type: service
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/abc-123
7.1.3 Helm チャートによるインストール
# Helm リポジトリの追加
helm repo add spinnaker https://helmcharts.opsmx.com/
helm repo update
# values ファイルの準備
cat > spinnaker-values.yaml << 'EOF'
halyard:
spinnakerVersion: 1.32.0
image:
tag: 1.45.0
additionalScripts:
enabled: true
configMapName: custom-halyard-scripts
configMapKey: config.sh
additionalSecrets:
create: true
data:
oauth-client-secret: ${OAUTH_CLIENT_SECRET}
additionalConfigMaps:
create: true
data:
kubeconfig-production: |
apiVersion: v1
kind: Config
...
additionalProfileConfigMaps:
data:
gate-local.yml: |
server:
port: 8084
cors:
allowedOriginsPattern: ".*"
clouddriver-local.yml: |
sql:
enabled: true
spinnakerFeatureFlags:
- artifacts
- pipeline-templates
- managed-pipeline-templates-v2-ui
- canary
dockerRegistries:
- name: dockerhub
address: index.docker.io
repositories:
- myorg/myapp
kubeConfig:
enabled: true
secretName: spinnaker-kubeconfig
redis:
enabled: true
cluster:
enabled: false
master:
persistence:
enabled: true
size: 10Gi
minio:
enabled: false
s3:
enabled: true
bucket: spinnaker-data
rootFolder: front50
region: us-east-1
accessKey: ${AWS_ACCESS_KEY_ID}
secretKey: ${AWS_SECRET_ACCESS_KEY}
ingress:
enabled: true
host: spinnaker.example.com
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
- secretName: spinnaker-tls
hosts:
- spinnaker.example.com
ingressGate:
enabled: true
host: gate.spinnaker.example.com
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
- secretName: gate-tls
hosts:
- gate.spinnaker.example.com
EOF
# インストール
helm install spinnaker spinnaker/spinnaker \
-n spinnaker \
--create-namespace \
-f spinnaker-values.yaml \
--timeout 600s
7.2 高可用性(HA)構成
本番環境向けの HA 構成では、各マイクロサービスの冗長化とデータベースの高可用性を確保する。
HA アーキテクチャ図:
┌──────────────────┐
│ Load Balancer │
│ (ALB/NLB) │
└────────┬─────────┘
│
┌────────┴─────────┐
│ Ingress │
│ Controller │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Deck x2 │ │ Gate x3 │ │ Echo x2 │
└───────────┘ └─────┬─────┘ └───────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Orca x3 │ │Clouddriver│ │ Igor x2 │
│ │ │ RW x2 │ │ │
└───────────┘ │ RO x3 │ └───────────┘
└─────┬─────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌─────┴─────┐ ┌────────┴────────┐ ┌──────┴──────┐
│ MySQL │ │ Redis Cluster │ │ Front50 x2 │
│ Primary │ │ (Sentinel) │ │ │
│ + Replica │ │ 3 nodes │ └─────────────┘
└───────────┘ └──────────────────┘
HA 設定例:
# SpinnakerService HA 設定
spec:
spinnakerConfig:
config:
deploymentEnvironment:
haServices:
clouddriver:
enabled: true
disableClouddriverRoDeck: false
echo:
enabled: true
service-settings:
clouddriver:
kubernetes:
replicas: 2
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "7002"
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 8Gi
volumes:
- name: kubeconfig
mountPath: /opt/spinnaker/kubeconfig
secret:
secretName: spinnaker-kubeconfig
tolerations:
- key: "dedicated"
operator: "Equal"
value: "spinnaker"
effect: "NoSchedule"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: cluster
operator: In
values:
- spin-clouddriver
topologyKey: kubernetes.io/hostname
clouddriver-ro:
kubernetes:
replicas: 3
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
clouddriver-ro-deck:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
orca:
kubernetes:
replicas: 3
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 8Gi
gate:
kubernetes:
replicas: 3
resources:
requests:
cpu: "500m"
memory: 1Gi
limits:
cpu: "1"
memory: 2Gi
echo:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
front50:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
igor:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
rosco:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
fiat:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
kayenta:
kubernetes:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
deck:
kubernetes:
replicas: 2
resources:
requests:
cpu: "100m"
memory: 128Mi
7.3 MySQL への移行(Redis からの脱却)
Spinnaker は従来 Redis に大きく依存していたが、最近のバージョンでは MySQL(または PostgreSQL)への移行が推奨されている。
各サービスの SQL 設定:
# orca-local.yml
sql:
enabled: true
connectionPool:
jdbcUrl: jdbc:mysql://mysql-host:3306/orca?useSSL=true&serverTimezone=UTC
user: orca_service
password: ${MYSQL_PASSWORD}
connectionTimeout: 5000
maxLifetime: 30000
maxPoolSize: 50
migration:
jdbcUrl: jdbc:mysql://mysql-host:3306/orca?useSSL=true&serverTimezone=UTC
user: orca_migrate
password: ${MYSQL_MIGRATE_PASSWORD}
executionRepository:
sql:
enabled: true
redis:
enabled: false
monitor:
activeExecutions:
redis: false
# Keiko Queue の SQL 設定
keiko:
queue:
sql:
enabled: true
redis:
enabled: false
# Pending Execution の SQL 設定
queue:
zombieCheck:
enabled: true
pendingExecutionService:
sql:
enabled: true
redis:
enabled: false
# clouddriver-local.yml
sql:
enabled: true
taskRepository:
enabled: true
cache:
enabled: true
readBatchSize: 500
writeBatchSize: 300
scheduler:
enabled: true
connectionPools:
default:
default: true
jdbcUrl: jdbc:mysql://mysql-host:3306/clouddriver?useSSL=true&serverTimezone=UTC
user: clouddriver_service
password: ${MYSQL_PASSWORD}
connectionTimeout: 5000
maxLifetime: 30000
maxPoolSize: 50
tasks:
jdbcUrl: jdbc:mysql://mysql-host:3306/clouddriver?useSSL=true&serverTimezone=UTC
user: clouddriver_service
password: ${MYSQL_PASSWORD}
maxPoolSize: 20
redis:
enabled: false
cache:
enabled: false
scheduler:
enabled: false
taskRepository:
enabled: false
7.4 モニタリングとオブザーバビリティ
Prometheus + Grafana による監視
# 各サービスの monitoring 設定
monitoring:
enabled: true
prometheus:
enabled: true
period: 30
主要な監視メトリクス:
# Prometheus の ServiceMonitor 定義
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: spinnaker
namespace: spinnaker
labels:
release: prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: spinnaker
endpoints:
- port: http
path: /prometheus_metrics
interval: 30s
scrapeTimeout: 10s
namespaceSelector:
matchNames:
- spinnaker
重要な監視メトリクス一覧:
| メトリクス | サービス | 説明 |
|---|---|---|
pipeline.executions | Orca | パイプライン実行数 |
stage.invocations | Orca | ステージ呼び出し数 |
task.completionTime | Orca | タスク完了時間 |
queue.depth | Orca | キューの深さ |
queue.ready.depth | Orca | 処理可能なキュー深さ |
cache.refresh.time | Clouddriver | キャッシュ更新時間 |
operations.count | Clouddriver | クラウド操作数 |
controller.invocations | Gate | API 呼び出し数 |
hystrix.countShortCircuited | Gate | サーキットブレーカー発動数 |
storageServiceSupport.autoRefresh | Front50 | 自動リフレッシュ時間 |
canaryJudge.score | Kayenta | カナリアスコア |
echo.triggers.count | Echo | トリガー処理数 |
echo.pubsub.messagesProcessed | Echo | Pub/Sub メッセージ処理数 |
jvm.memory.used | All | JVM メモリ使用量 |
jvm.gc.pause | All | GC 一時停止時間 |
Grafana ダッシュボードの構成例:
{
"dashboard": {
"title": "Spinnaker Overview",
"panels": [
{
"title": "Pipeline Execution Rate",
"type": "graph",
"targets": [
{
"expr": "rate(pipeline_executions_total[5m])",
"legendFormat": "{{status}}"
}
]
},
{
"title": "Pipeline Duration (p95)",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(pipeline_execution_duration_seconds_bucket[5m]))",
"legendFormat": "p95"
}
]
},
{
"title": "Orca Queue Depth",
"type": "graph",
"targets": [
{
"expr": "queue_depth",
"legendFormat": "Total"
},
{
"expr": "queue_ready_depth",
"legendFormat": "Ready"
}
]
},
{
"title": "Clouddriver Cache Refresh Time",
"type": "graph",
"targets": [
{
"expr": "rate(cache_refresh_time_sum[5m]) / rate(cache_refresh_time_count[5m])",
"legendFormat": "{{agent}}"
}
]
}
]
}
}
8. 高度な機能と活用パターン
8.1 Managed Delivery(宣言的デリバリー)
Managed Delivery は Spinnaker の宣言的デリバリーシステムであり、Keel サービスによって提供される。ユーザーは「望ましい状態」を宣言するだけで、Keel が自動的にデリバリーパイプラインを管理・実行する。
宣言的アプローチと命令的アプローチの比較:
| 観点 | 命令的(パイプライン) | 宣言的(Managed Delivery) |
|---|---|---|
| 定義方法 | 「何をどの順序で行うか」を指定 | 「どうあるべきか」を宣言 |
| 制御 | 手動/トリガーで実行 | 自動的に収束 |
| 複雑性 | パイプラインの設計・保守が必要 | 宣言ファイルのみ |
| 柔軟性 | 高い(任意のワークフロー) | 標準パターンに限定 |
| 適用場面 | 複雑なリリースフロー | 標準的なデプロイメント |
詳細な Delivery Config の例:
name: myapp-delivery
application: myapp
serviceAccount: myapp-delivery@spinnaker
artifacts:
- name: myapp-docker-image
type: docker
reference: myorg/myapp
tagVersionStrategy: increasing-tag
statuses:
- RELEASE
from:
branch:
name: main
environments:
- name: testing
constraints: []
verifyWith:
- type: test-container
application: myapp
image: myorg/myapp-integration-tests
location:
account: staging-k8s
namespace: testing
notifications:
- type: slack
address: "#myapp-testing"
frequency: quiet
locations:
account: staging-k8s
regions:
- name: us-east-1
resources:
- kind: titus/cluster@v1
spec:
moniker:
app: myapp
stack: testing
deployWith:
strategy: red-black
waitForInstancesUp: PT10M
container:
organization: myorg
image: myapp
digest: ${artifact.digest}
capacity:
min: 1
max: 2
desired: 1
dependencies:
loadBalancerNames:
- myapp-testing-lb
securityGroupNames:
- myapp-testing-sg
- name: staging
constraints:
- type: depends-on
environment: testing
- type: pipeline
pipelineId: staging-smoke-test-pipeline
parameters:
environment: staging
notifications:
- type: slack
address: "#myapp-staging"
frequency: normal
locations:
account: staging-k8s
regions:
- name: us-east-1
resources:
- kind: titus/cluster@v1
spec:
moniker:
app: myapp
stack: staging
container:
organization: myorg
image: myapp
capacity:
min: 2
max: 4
desired: 2
- name: production
constraints:
- type: depends-on
environment: staging
- type: allowed-times
windows:
- days: Monday-Thursday
hours: 10-16
tz: Asia/Tokyo
- type: manual-judgement
timeout: 4h
notifications:
- type: slack
address: "#myapp-production"
frequency: verbose
- type: email
address: sre-team@example.com
frequency: quiet
postDeploy:
- type: webhook
name: notify-deployment-tracker
url: https://deployments.example.com/api/notify
method: POST
locations:
account: production-k8s
regions:
- name: us-east-1
- name: us-west-2
- name: ap-northeast-1
resources:
- kind: titus/cluster@v1
spec:
moniker:
app: myapp
stack: production
deployWith:
strategy: red-black
isRollbackOnFailure: true
waitForInstancesUp: PT15M
maxRemainingAsgs: 2
container:
organization: myorg
image: myapp
capacity:
min: 5
max: 20
desired: 10
scaling:
targetTrackingPolicies:
- targetValue: 70.0
customizedMetricSpecification:
metricName: CPUUtilization
namespace: AWS/EC2
statistic: Average
scaleOutCooldown: 300
scaleInCooldown: 600
disableScaleIn: false
8.2 パイプラインの高度なパターン
8.2.1 Fan-out / Fan-in パターン
複数のリージョンやクラスターに同時デプロイする際に使用する並列実行パターン。
{
"stages": [
{
"refId": "1",
"type": "evaluateVariables",
"name": "Prepare Deployment",
"requisiteStageRefIds": []
},
{
"refId": "2a",
"type": "deployManifest",
"name": "Deploy to us-east-1",
"account": "k8s-us-east-1",
"requisiteStageRefIds": ["1"]
},
{
"refId": "2b",
"type": "deployManifest",
"name": "Deploy to us-west-2",
"account": "k8s-us-west-2",
"requisiteStageRefIds": ["1"]
},
{
"refId": "2c",
"type": "deployManifest",
"name": "Deploy to ap-northeast-1",
"account": "k8s-ap-northeast-1",
"requisiteStageRefIds": ["1"]
},
{
"refId": "3",
"type": "checkPreconditions",
"name": "Verify All Regions",
"requisiteStageRefIds": ["2a", "2b", "2c"]
},
{
"refId": "4",
"type": "webhook",
"name": "Update Global Load Balancer",
"requisiteStageRefIds": ["3"]
}
]
}
8.2.2 条件分岐パターン
{
"stages": [
{
"refId": "1",
"type": "evaluateVariables",
"name": "Determine Deployment Type",
"variables": [
{
"key": "isHotfix",
"value": "${trigger.parameters.deploymentType == 'hotfix'}"
}
]
},
{
"refId": "2a",
"type": "pipeline",
"name": "Standard Deployment",
"pipeline": "standard-deploy-pipeline-id",
"requisiteStageRefIds": ["1"],
"stageEnabled": {
"type": "expression",
"expression": "${!isHotfix}"
}
},
{
"refId": "2b",
"type": "pipeline",
"name": "Hotfix Deployment",
"pipeline": "hotfix-deploy-pipeline-id",
"requisiteStageRefIds": ["1"],
"stageEnabled": {
"type": "expression",
"expression": "${isHotfix}"
}
},
{
"refId": "3",
"type": "webhook",
"name": "Post-deployment Notification",
"requisiteStageRefIds": ["2a", "2b"]
}
]
}
8.2.3 ロールバック自動化パターン
{
"stages": [
{
"refId": "1",
"type": "deployManifest",
"name": "Deploy New Version",
"account": "production-k8s",
"failOnFailedExpressions": true,
"onFailure": {
"type": "pipeline",
"pipeline": "rollback-pipeline-id",
"pipelineParameters": {
"application": "myapp",
"cluster": "myapp-production",
"reason": "Deployment failed"
}
}
},
{
"refId": "2",
"type": "wait",
"name": "Stabilization Period",
"waitTime": 300,
"requisiteStageRefIds": ["1"]
},
{
"refId": "3",
"type": "webhook",
"name": "Health Check",
"url": "https://healthcheck.example.com/api/v1/check/myapp",
"method": "GET",
"successStatuses": "HEALTHY",
"terminalStatuses": "UNHEALTHY,DEGRADED",
"requisiteStageRefIds": ["2"],
"failOnFailedExpressions": true,
"completeOtherBranchesThenFail": false
},
{
"refId": "4",
"type": "undoRolloutManifest",
"name": "Rollback on Health Check Failure",
"account": "production-k8s",
"cloudProvider": "kubernetes",
"location": "production",
"manifestName": "deployment myapp",
"numRevisionsBack": 1,
"requisiteStageRefIds": ["3"],
"stageEnabled": {
"type": "expression",
"expression": "${#stage('Health Check').status.toString() == 'TERMINAL'}"
}
}
]
}
8.3 Spinnaker Plugins
Spinnaker 1.23 以降、プラグインアーキテクチャが導入され、カスタム機能を追加できるようになった。
プラグインの設定例:
# orca-local.yml
spinnaker:
extensibility:
plugins:
Armory.PolicyEngine:
id: Armory.PolicyEngine
enabled: true
version: 0.3.0
extensions: {}
Netflix.FeatureFlag:
id: Netflix.FeatureFlag
enabled: true
version: 1.0.0
repositories:
armory-plugins:
id: armory-plugins
url: https://raw.githubusercontent.com/armory-plugins/pluginRepository/master/repositories.json
custom-plugins:
id: custom-plugins
url: https://artifactory.example.com/spinnaker-plugins/plugins.json
カスタムステージプラグインの開発例:
// CustomVerificationPlugin.kt
package com.example.spinnaker.plugin
import com.netflix.spinnaker.orca.api.simplestage.SimpleStage
import com.netflix.spinnaker.orca.api.simplestage.SimpleStageInput
import com.netflix.spinnaker.orca.api.simplestage.SimpleStageOutput
import com.netflix.spinnaker.orca.api.simplestage.SimpleStageStatus
import org.pf4j.Extension
import org.pf4j.Plugin
import org.pf4j.PluginWrapper
class CustomVerificationPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
override fun start() {
log.info("CustomVerificationPlugin started")
}
override fun stop() {
log.info("CustomVerificationPlugin stopped")
}
}
@Extension
class CustomVerificationStage : SimpleStage<CustomVerificationInput> {
override fun execute(input: CustomVerificationInput): SimpleStageOutput {
val output = SimpleStageOutput()
val outputMap = mutableMapOf<String, Any>()
try {
// カスタム検証ロジック
val verificationResult = performVerification(
input.endpoint,
input.expectedStatus,
input.timeout
)
outputMap["verificationResult"] = verificationResult
outputMap["verifiedAt"] = System.currentTimeMillis()
output.status = if (verificationResult.success) {
SimpleStageStatus.SUCCEEDED
} else {
SimpleStageStatus.TERMINAL
}
} catch (e: Exception) {
outputMap["error"] = e.message ?: "Unknown error"
output.status = SimpleStageStatus.TERMINAL
}
output.output = outputMap
return output
}
private fun performVerification(
endpoint: String,
expectedStatus: Int,
timeout: Long
): VerificationResult {
// 検証ロジックの実装
return VerificationResult(success = true, details = "All checks passed")
}
}
data class CustomVerificationInput(
@SimpleStageInput val endpoint: String,
@SimpleStageInput val expectedStatus: Int = 200,
@SimpleStageInput val timeout: Long = 30000
)
data class VerificationResult(
val success: Boolean,
val details: String
)
8.4 Policy Engine との統合
OPA(Open Policy Agent)などのポリシーエンジンと統合して、デプロイメントポリシーを強制する。
OPA ポリシーの例:
# policy/spinnaker/deploy.rego
package spinnaker.deployment
# 本番デプロイメントには承認が必要
deny[msg] {
input.stage.type == "deployManifest"
input.stage.account == "production-k8s"
not has_manual_judgment_before(input)
msg := "Production deployments require manual approval"
}
# 営業時間外のデプロイメントを禁止
deny[msg] {
input.stage.type == "deployManifest"
input.stage.account == "production-k8s"
not is_business_hours
msg := "Production deployments are only allowed during business hours (9:00-17:00 JST)"
}
# 最小レプリカ数の強制
deny[msg] {
input.stage.type == "deployManifest"
input.stage.account == "production-k8s"
manifest := input.stage.manifests[_]
manifest.kind == "Deployment"
manifest.spec.replicas < 3
msg := sprintf("Production deployments must have at least 3 replicas, got %d", [manifest.spec.replicas])
}
# リソース制限の必須化
deny[msg] {
input.stage.type == "deployManifest"
manifest := input.stage.manifests[_]
manifest.kind == "Deployment"
container := manifest.spec.template.spec.containers[_]
not container.resources.limits
msg := sprintf("Container '%s' must have resource limits defined", [container.name])
}
# イメージタグの 'latest' を禁止
deny[msg] {
input.stage.type == "deployManifest"
manifest := input.stage.manifests[_]
container := manifest.spec.template.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Container '%s' must not use 'latest' tag", [container.name])
}
has_manual_judgment_before(input) {
some stage in input.pipeline.stages
stage.type == "manualJudgment"
stage.refId < input.stage.refId
}
is_business_hours {
time.now_ns() > time.parse_rfc3339_ns(sprintf("%sT09:00:00+09:00", [format_date]))
time.now_ns() < time.parse_rfc3339_ns(sprintf("%sT17:00:00+09:00", [format_date]))
}
8.5 Terraform との統合
Spinnaker は Terraform Integration(旧 Terraformer)を通じて Terraform を統合できる。
Terraform ステージの設定例:
{
"type": "terraform",
"name": "Provision Infrastructure",
"terraformVersion": "1.5.0",
"action": "planAndApply",
"dir": "terraform/production",
"workspace": "production",
"artifacts": [
{
"reference": "https://github.com/myorg/infrastructure",
"type": "git/repo",
"version": "main"
}
],
"overrides": {
"region": "us-east-1",
"instance_count": "3",
"environment": "production"
},
"backendArtifact": {
"type": "embedded/base64",
"reference": "dGVycmFmb3JtIHsKICBiYWNrZW5kICJzMyIgewogICAgYnVja2V0ID0gInRlcnJhZm9ybS1zdGF0ZSIKICAgIGtleSAgICA9ICJwcm9kdWN0aW9uL3RlcnJhZm9ybS50ZnN0YXRlIgogICAgcmVnaW9uID0gInVzLWVhc3QtMSIKICB9Cn0="
},
"targets": ["module.vpc", "module.rds", "module.elasticache"],
"planForDestroy": false
}
9. 運用とトラブルシューティング
9.1 よくある問題と対処法
9.1.1 パイプラインが STUCK 状態になる
症状: パイプラインが実行中のまま進行しない。
原因と対処:
# 1. Orca のキュー状態を確認
curl -s http://localhost:8083/queue | jq .
# 2. 実行中のパイプラインを確認
curl -s http://localhost:8083/executions/active | jq .
# 3. 特定のパイプライン実行をキャンセル
curl -X PUT http://localhost:8083/pipelines/{executionId}/cancel \
-H 'Content-Type: application/json' \
-d '{"reason": "Pipeline stuck - manual cancellation"}'
# 4. Zombie 実行のクリーンアップ
curl -X POST http://localhost:8083/admin/queue/zombies
9.1.2 Clouddriver のキャッシュ問題
症状: UI に古い情報が表示される、または新しいリソースが表示されない。
# キャッシュの強制リフレッシュ
curl -X POST http://localhost:7002/cache/kubernetes/account/production-k8s/refresh
# 特定のリソースタイプのキャッシュをリフレッシュ
curl -X POST "http://localhost:7002/cache/kubernetes/manifest?account=production-k8s&location=production&name=deployment+myapp"
# キャッシングエージェントの状態確認
curl -s http://localhost:7002/cache/kubernetes/agents | jq .
9.1.3 メモリ問題
症状: OutOfMemoryError、サービスのクラッシュ。
# JVM メモリチューニング
# service-settings/clouddriver.yml
env:
JAVA_OPTS: >-
-XX:MaxRAMPercentage=70.0
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ParallelRefProcEnabled
-XX:+UnlockExperimentalVMOptions
-XX:+UnlockDiagnosticVMOptions
-XX:+G1SummarizeRSetStats
-XX:G1SummarizeRSetStatsPeriod=1
-Dspring.profiles.active=local
# orca のメモリ設定
# service-settings/orca.yml
env:
JAVA_OPTS: >-
-XX:MaxRAMPercentage=70.0
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-Dorca.executionRepository.redis.enabled=false
-Dorca.executionRepository.sql.enabled=true
9.2 パフォーマンスチューニング
Orca のチューニング
# orca-local.yml
tasks:
executionWindow:
timezone: Asia/Tokyo
queue:
zombieCheck:
enabled: true
pendingExecutionService:
sql:
enabled: true
# スレッドプールの設定
server:
tomcat:
max-threads: 200
min-spare-threads: 25
# 実行履歴の保持期間
execution:
cleanup:
enabled: true
daysToKeep: 30
bufferDaysFromNow: 7
minimumPipelineExecutions: 5
Clouddriver のチューニング
# clouddriver-local.yml
caching:
kubernetes:
v2:
cacheThreads: 4
cacheTTLOverride:
infrastructure: 60
onDemand: 10
sql:
cache:
readBatchSize: 500
writeBatchSize: 300
# Agent のスケジューリング
caching:
agentScheduler:
parallelism: 8
enabledPattern: ".*"
# 大規模クラスターでの設定
kubernetes:
accounts:
- name: large-cluster
cacheIntervalSeconds: 60
cacheThreads: 4
cachingPolicies:
- kubernetesKind: pod
maxEntriesPerAgent: 5000
- kubernetesKind: replicaSet
maxEntriesPerAgent: 1000
- kubernetesKind: event
maxEntriesPerAgent: 0
liveManifestCalls: false
9.3 バックアップとリカバリ
#!/bin/bash
# Spinnaker バックアップスクリプト
BACKUP_DIR="/opt/backup/spinnaker/$(date +%Y%m%d_%H%M%S)"
mkdir -p "${BACKUP_DIR}"
# 1. Front50 データのバックアップ(S3 の場合)
aws s3 sync s3://spinnaker-front50-data/front50/ "${BACKUP_DIR}/front50/"
# 2. MySQL データベースのバックアップ
for db in orca clouddriver front50 fiat kayenta; do
mysqldump -h mysql-host -u backup_user -p${MYSQL_PASSWORD} \
--single-transaction --routines --triggers \
${db} > "${BACKUP_DIR}/${db}.sql"
done
# 3. Halyard 設定のバックアップ
tar czf "${BACKUP_DIR}/halyard-config.tar.gz" /home/spinnaker/.hal/
# 4. パイプライン定義のエクスポート
for app in $(curl -s http://gate:8084/applications | jq -r '.[].name'); do
mkdir -p "${BACKUP_DIR}/pipelines/${app}"
curl -s "http://gate:8084/applications/${app}/pipelineConfigs" | \
jq '.' > "${BACKUP_DIR}/pipelines/${app}/pipelines.json"
done
# 5. バックアップの圧縮とリモート転送
tar czf "${BACKUP_DIR}.tar.gz" "${BACKUP_DIR}"
aws s3 cp "${BACKUP_DIR}.tar.gz" "s3://spinnaker-backups/$(date +%Y%m%d_%H%M%S).tar.gz"
echo "Backup completed: ${BACKUP_DIR}"
9.4 ログ管理
# logging 設定
logging:
level:
root: WARN
com.netflix.spinnaker: INFO
com.netflix.spinnaker.orca.pipeline: DEBUG
com.netflix.spinnaker.clouddriver.kubernetes: INFO
com.netflix.spinnaker.gate.security: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: /var/log/spinnaker/${spring.application.name}.log
max-size: 100MB
max-history: 7
total-size-cap: 1GB
10. CI/CD パイプラインの実践例
10.1 マイクロサービスの完全なデプロイメントパイプライン
以下は、マイクロサービスアプリケーションの完全な CI/CD パイプラインの例である。
CI (Jenkins/GitHub Actions) Spinnaker CD Pipeline
┌─────────────────────┐ ┌──────────────────────────────────────┐
│ 1. Code Checkout │ │ │
│ 2. Unit Tests │ │ Trigger: Docker Image Push │
│ 3. Static Analysis │ │ │ │
│ 4. Build │───→│ ┌──────▼──────┐ │
│ 5. Docker Build │ │ │ Verify Build │ │
│ 6. Docker Push │ │ └──────┬──────┘ │
│ 7. Notify Spinnaker │ │ │ │
└─────────────────────┘ │ ┌──────▼──────┐ │
│ │Deploy to Dev │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Integration │ │
│ │ Tests │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────────┐ │
│ │Deploy to Staging │ │
│ └──────┬──────────┘ │
│ │ │
│ ┌──────▼──────┐ ┌──────────────┐ │
│ │ Performance │ │ Security │ │
│ │ Tests │ │ Scan │ │
│ └──────┬──────┘ └──────┬───────┘ │
│ └──────┬─────────┘ │
│ ┌──────▼──────┐ │
│ │ Approval │ │
│ └──────┬──────┘ │
│ ┌──────▼──────────┐ │
│ │ Canary Deploy │ │
│ │ (Production) │ │
│ └──────┬──────────┘ │
│ ┌──────▼──────┐ │
│ │ Canary │ │
│ │ Analysis │ │
│ └──────┬──────┘ │
│ ┌──────▼──────────┐ │
│ │ Full Rollout │ │
│ │ (Production) │ │
│ └──────┬──────────┘ │
│ ┌──────▼──────┐ │
│ │ Verify & │ │
│ │ Notify │ │
│ └─────────────┘ │
└──────────────────────────────────────┘
10.2 GitHub Actions との連携
GitHub Actions ワークフロー:
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main]
tags: ['v*']
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: myorg/myapp
tags: |
type=semver,pattern={{version}}
type=sha,prefix=
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trigger Spinnaker Pipeline
if: startsWith(github.ref, 'refs/tags/v')
run: |
curl -X POST \
https://gate.spinnaker.example.com/webhooks/webhook/github-deploy \
-H 'Content-Type: application/json' \
-d '{
"artifacts": [{
"type": "docker/image",
"name": "myorg/myapp",
"version": "${{ steps.meta.outputs.version }}",
"reference": "myorg/myapp:${{ steps.meta.outputs.version }}"
}],
"parameters": {
"commitSha": "${{ github.sha }}",
"commitMessage": "${{ github.event.head_commit.message }}",
"author": "${{ github.actor }}"
}
}'
10.3 spin CLI の活用
Spinnaker の CLI ツール spin を使用したパイプライン管理の例。
# spin CLI のインストール
curl -LO https://storage.googleapis.com/spinnaker-artifacts/spin/$(curl -s https://storage.googleapis.com/spinnaker-artifacts/spin/latest)/linux/amd64/spin
chmod +x spin
sudo mv spin /usr/local/bin/
# 設定ファイル
cat > ~/.spin/config << 'EOF'
gate:
endpoint: https://gate.spinnaker.example.com
auth:
enabled: true
oauth2:
tokenUrl: https://oauth2.googleapis.com/token
authUrl: https://accounts.google.com/o/oauth2/v2/auth
clientId: ${OAUTH_CLIENT_ID}
clientSecret: ${OAUTH_CLIENT_SECRET}
scopes:
- openid
- email
- profile
EOF
# アプリケーション一覧
spin application list
# パイプライン一覧
spin pipeline list --application myapp
# パイプラインの取得(JSON)
spin pipeline get --name "Production Deploy" --application myapp > pipeline.json
# パイプラインの保存(更新/作成)
spin pipeline save --file pipeline.json
# パイプラインの実行
spin pipeline execute \
--name "Production Deploy" \
--application myapp \
--parameter-file params.json
# 実行の監視
spin pipeline execution list --application myapp --limit 5
# 特定の実行の詳細
spin pipeline execution get --id <execution-id>
11. 他ツールとの比較
11.1 Spinnaker vs Argo CD
| 観点 | Spinnaker | Argo CD |
|---|---|---|
| 設計思想 | マルチクラウド CD プラットフォーム | Kubernetes ネイティブ GitOps |
| 対応クラウド | AWS, GCP, Azure, K8s, CF 等 | Kubernetes のみ |
| デプロイ戦略 | Red/Black, Canary, Rolling, Custom | Sync (Rolling), Blue/Green (Rollouts) |
| カナリア分析 | Kayenta による自動カナリア分析 | Argo Rollouts + 外部メトリクス |
| GitOps | 限定的(Webhook トリガー) | ネイティブ対応 |
| UI | リッチな Web UI | シンプルな Web UI |
| 学習コスト | 高い(マイクロサービス構成の理解が必要) | 比較的低い |
| 運用負荷 | 高い(11+ マイクロサービス) | 低い(単一バイナリ) |
| パイプライン | 豊富なステージタイプ | Application Sync のみ(Workflows は別) |
| 承認フロー | Manual Judgment 組み込み | Sync Windows + 外部ツール |
| スケーラビリティ | 大規模向け(HA 構成対応) | 中規模向け |
| コミュニティ | CDF 傘下、成熟 | CNCF Graduated、活発 |
| 適用場面 | マルチクラウド、大規模組織 | Kubernetes 中心、中小規模 |
11.2 Spinnaker vs Jenkins + Plugins
| 観点 | Spinnaker | Jenkins + CD Plugins |
|---|---|---|
| 目的 | CD 専用プラットフォーム | 汎用 CI/CD |
| デプロイ戦略 | ネイティブサポート | プラグイン依存 |
| クラウド統合 | 深いネイティブ統合 | プラグインベース |
| インフラ可視性 | クラスター/サーバーグループの一覧 | なし(別ツール必要) |
| ロールバック | ワンクリックロールバック | 手動/スクリプト |
| パイプライン定義 | JSON/UI | Jenkinsfile (Groovy) |
| 運用モデル | マイクロサービス(複雑) | 単一アプリ(シンプル) |
| 拡張性 | プラグイン、カスタムステージ | 豊富なプラグインエコシステム |
11.3 Spinnaker vs Flux CD
| 観点 | Spinnaker | Flux CD |
|---|---|---|
| アプローチ | Push ベース + Pull ベース | Pull ベース(GitOps) |
| Kubernetes 対応 | プロバイダーの一つ | 専用 |
| VM 対応 | あり(AMI, GCE Image) | なし |
| Helm サポート | Bake Manifest ステージ | Helm Controller |
| Kustomize | サポートあり | Kustomize Controller |
| イメージ自動更新 | Docker トリガー | Image Automation Controller |
| 複雑なワークフロー | パイプラインで柔軟に定義 | 限定的 |
| マルチテナント | RBAC で制御 | Namespace ベース |
11.4 選定ガイドライン
Spinnaker を選ぶべきケース:
- マルチクラウド環境でのデプロイメントが必要
- 高度なデプロイメント戦略(カナリア分析含む)が必要
- 大規模組織でのガバナンス・承認フローが必要
- VM ベースと Kubernetes ベースの混在環境
- 専任の Platform Engineering チームがある
他ツールを検討すべきケース:
- Kubernetes のみの環境 → Argo CD / Flux CD
- シンプルな CI/CD が必要 → GitHub Actions / GitLab CI
- 小規模チーム・プロジェクト → 軽量ツールが適切
- GitOps を完全に採用したい → Argo CD / Flux CD
12. ベストプラクティスと設計指針
12.1 パイプライン設計のベストプラクティス
-
パイプラインの粒度
- アプリケーション単位でパイプラインを分割
- 共通処理はパイプラインテンプレートで共有
- 環境ごとのパイプラインではなく、パラメータで切り替え
-
冪等性の確保
- 同じパイプラインを複数回実行しても同じ結果になるよう設計
- サーバーグループ名にタイムスタンプや実行IDを含めない
-
ロールバック計画
- すべての本番パイプラインにロールバック手順を組み込む
- Red/Black 戦略で前バージョンを一定期間保持
- ロールバック用の専用パイプラインを準備
-
セキュリティ
- シークレットは必ず外部シークレット管理システムで管理
- サービスアカウントを使用し、個人アカウントでの自動実行を避ける
- 最小権限の原則に基づいた RBAC 設定
-
可観測性
- すべてのパイプラインに通知設定を追加
- デプロイメントメトリクスの収集と可視化
- 失敗時のアラート設定
12.2 組織への導入戦略
Phase 1: 基盤構築(1-2ヶ月)
├── Spinnaker のインストールと基本設定
├── 認証・認可の設定
├── 1-2 のパイロットアプリケーションでの検証
└── 運用手順の策定
Phase 2: パイロット展開(2-3ヶ月)
├── パイロットチームでの本格運用
├── パイプラインテンプレートの作成
├── CI ツールとの統合
├── モニタリングダッシュボードの構築
└── フィードバック収集と改善
Phase 3: 全社展開(3-6ヶ月)
├── 全チームへの段階的ロールアウト
├── トレーニングプログラムの実施
├── セルフサービスポータルの提供
├── ポリシーエンジンの導入
└── カナリア分析の導入
Phase 4: 最適化(継続的)
├── Managed Delivery の検討
├── パフォーマンスチューニング
├── コスト最適化
├── 新機能の評価と導入
└── コミュニティへの貢献
12.3 命名規則
アプリケーション名: <team>-<service>
例: payments-api, auth-service, frontend-web
パイプライン名: <action>-<environment>[-<variant>]
例: deploy-staging, deploy-production-canary, rollback-production
サーバーグループ: <app>-<stack>-<detail>-v<version>
例: payments-api-production-v001
クラスター: <app>-<stack>[-<detail>]
例: payments-api-production, auth-service-staging
ロードバランサー: <app>-<stack>-<type>
例: payments-api-production-external, auth-service-staging-internal
Kubernetes アカウント: <environment>-<region>-k8s
例: production-us-east-1-k8s, staging-ap-northeast-1-k8s
12.4 Cost 最適化のヒント
# Clouddriver の SQL キャッシュで Redis の使用を最小化
sql:
enabled: true
cache:
enabled: true
redis:
cache:
enabled: false
# 不要なキャッシングエージェントの無効化
kubernetes:
accounts:
- name: production
omitKinds:
- event # イベントはキャッシュ不要
- podPreset # 非推奨リソース
cachingPolicies:
- kubernetesKind: pod
maxEntriesPerAgent: 3000 # Pod 数の制限
# 実行履歴のクリーンアップ
execution:
cleanup:
enabled: true
daysToKeep: 14 # 2週間分のみ保持
minimumPipelineExecutions: 3
13. まとめと今後の展望
13.1 まとめ
Spinnaker は、Netflix が培った大規模サービス運用のノウハウを基に設計された、エンタープライズグレードの継続的デリバリープラットフォームである。本稿で解説した通り、その主要な特長は以下の通りである。
- マルチクラウド対応: AWS、GCP、Azure、Kubernetes など主要なクラウドプロバイダーに対応し、統一的なデプロイメントインターフェースを提供
- 高度なデプロイメント戦略: Red/Black、カナリア、ローリングアップデートなどの戦略をネイティブにサポート
- 自動カナリア分析: Kayenta によるメトリクスベースの自動カナリア判定で、リリースリスクを定量的に評価
- 豊富なパイプライン機能: ステージの並列実行、条件分岐、パイプラインテンプレートによる標準化
- エンタープライズセキュリティ: RBAC、サービスアカウント、外部シークレット管理との統合
- 拡張性: プラグインアーキテクチャ、カスタムステージ、ポリシーエンジン統合
一方、以下の課題も認識しておく必要がある。
- 運用の複雑性: 11以上のマイクロサービスで構成されるため、運用負荷が高い
- 学習コスト: 豊富な機能を使いこなすには相応の学習時間が必要
- リソース消費: 本番環境での HA 構成には相当のコンピュートリソースが必要
- GitOps 対応: ネイティブな GitOps サポートは限定的
13.2 今後の展望
Spinnaker エコシステムは引き続き進化を続けている。
- Kubernetes ネイティブ統合の深化: Kubernetes がデフォルトのデプロイメントターゲットとなり、より深い統合が進む
- Managed Delivery の成熟: 宣言的デリバリーモデルがより成熟し、パイプラインの自動生成が進む
- AI/ML 活用: デプロイメント最適化やカナリア分析における機械学習の活用
- サーバーレス対応の強化: AWS Lambda、Google Cloud Functions、Azure Functions への対応強化
- Operator パターンの普及: Spinnaker Operator による Kubernetes ネイティブな管理が標準に
- プラグインエコシステムの拡大: コミュニティによるプラグイン開発の活性化
Spinnaker は、大規模で複雑なデプロイメント要件を持つ組織にとって、依然として最も包括的な CD プラットフォームの一つである。適切に設計・運用することで、デプロイメントの速度と信頼性を大幅に向上させることが可能である。
13.3 参考リソース
- 公式ドキュメント: https://spinnaker.io/docs/
- GitHub リポジトリ: https://github.com/spinnaker
- Spinnaker Community: https://spinnaker.io/community/
- Continuous Delivery Foundation: https://cd.foundation/
- Spinnaker Slack: https://join.spinnaker.io/
- Kayenta ドキュメント: https://github.com/spinnaker/kayenta
- Spinnaker Operator: https://github.com/armory/spinnaker-operator
- Halyard リファレンス: https://spinnaker.io/docs/reference/halyard/
本稿は Spinnaker の主要な機能とアーキテクチャの全容を把握するための概要ドキュメントであり、各トピックの詳細については公式ドキュメントを参照されたい。