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 が解決する課題

現代のソフトウェア開発において、継続的デリバリーは不可欠な実践となっている。しかし、以下のような課題が存在する。

  1. マルチクラウド環境の複雑性: 複数のクラウドプロバイダーを利用する場合、各プロバイダー固有のデプロイメントツールやAPIを個別に管理する必要がある
  2. デプロイメント戦略の標準化: Blue/Green デプロイメント、カナリアリリース、ローリングアップデートなどの高度なデプロイメント戦略を一貫して実装することが困難
  3. ガバナンスとコンプライアンス: デプロイメントプロセスにおける承認フロー、監査ログ、ポリシー適用の統一的な管理
  4. 可視性と透明性: デプロイメントの状態、履歴、依存関係を一元的に把握することの難しさ
  5. ロールバックの信頼性: 問題発生時の迅速かつ確実なロールバック機構の確保

Spinnaker はこれらの課題に対して、統一されたプラットフォームとして包括的なソリューションを提供する。

1.3 Spinnaker の歴史と背景

Spinnaker の起源は、Netflix 内部で使用されていた「Asgard」というデプロイメントツールに遡る。Asgard は AWS に特化したウェブベースのデプロイメントツールであったが、Netflix のインフラストラクチャが複雑化するにつれて、より柔軟でマルチクラウド対応のプラットフォームが求められるようになった。

2014年、Netflix は Google と共同で Spinnaker の開発を開始した。Google は自社の内部ツールである「Google Deployment Manager」の知見を活かし、クラウドネイティブなデプロイメントプラットフォームの設計に貢献した。2015年11月にオープンソースとして公開され、以降急速にコミュニティが拡大した。

主要なマイルストーンは以下の通りである。

イベント
2014Netflix と Google が共同開発を開始
2015オープンソースとして公開
2017Kubernetes プロバイダーのサポート追加
2018Managed Pipeline Templates (MPT) v2 導入
2019Continuous Delivery Foundation (CDF) に参加
2020Managed Delivery 機能の強化
2021AWS ECS、Lambda のネイティブサポート強化
2022-2024Kubernetes ネイティブ統合の深化、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 のパイプラインは、アプリケーションのデリバリープロセスを定義するワークフローである。パイプラインは一連の「ステージ」で構成され、各ステージは特定のアクション(デプロイ、テスト、承認など)を実行する。

パイプラインの主要な構成要素:

  1. Configuration(設定): パイプライン全体の設定(トリガー、パラメータ、通知、期待されるアーティファクトなど)
  2. Stages(ステージ): 個々のアクションを定義する処理単位
  3. Triggers(トリガー): パイプラインの自動起動条件
  4. Parameters(パラメータ): パイプライン実行時に指定する変数
  5. Notifications(通知): パイプラインの状態変化時の通知設定
  6. 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/imagekubernetes/configMaphelm/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.executionsOrcaパイプライン実行数
stage.invocationsOrcaステージ呼び出し数
task.completionTimeOrcaタスク完了時間
queue.depthOrcaキューの深さ
queue.ready.depthOrca処理可能なキュー深さ
cache.refresh.timeClouddriverキャッシュ更新時間
operations.countClouddriverクラウド操作数
controller.invocationsGateAPI 呼び出し数
hystrix.countShortCircuitedGateサーキットブレーカー発動数
storageServiceSupport.autoRefreshFront50自動リフレッシュ時間
canaryJudge.scoreKayentaカナリアスコア
echo.triggers.countEchoトリガー処理数
echo.pubsub.messagesProcessedEchoPub/Sub メッセージ処理数
jvm.memory.usedAllJVM メモリ使用量
jvm.gc.pauseAllGC 一時停止時間

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

観点SpinnakerArgo CD
設計思想マルチクラウド CD プラットフォームKubernetes ネイティブ GitOps
対応クラウドAWS, GCP, Azure, K8s, CF 等Kubernetes のみ
デプロイ戦略Red/Black, Canary, Rolling, CustomSync (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

観点SpinnakerJenkins + CD Plugins
目的CD 専用プラットフォーム汎用 CI/CD
デプロイ戦略ネイティブサポートプラグイン依存
クラウド統合深いネイティブ統合プラグインベース
インフラ可視性クラスター/サーバーグループの一覧なし(別ツール必要)
ロールバックワンクリックロールバック手動/スクリプト
パイプライン定義JSON/UIJenkinsfile (Groovy)
運用モデルマイクロサービス(複雑)単一アプリ(シンプル)
拡張性プラグイン、カスタムステージ豊富なプラグインエコシステム

11.3 Spinnaker vs Flux CD

観点SpinnakerFlux 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 パイプライン設計のベストプラクティス

  1. パイプラインの粒度

    • アプリケーション単位でパイプラインを分割
    • 共通処理はパイプラインテンプレートで共有
    • 環境ごとのパイプラインではなく、パラメータで切り替え
  2. 冪等性の確保

    • 同じパイプラインを複数回実行しても同じ結果になるよう設計
    • サーバーグループ名にタイムスタンプや実行IDを含めない
  3. ロールバック計画

    • すべての本番パイプラインにロールバック手順を組み込む
    • Red/Black 戦略で前バージョンを一定期間保持
    • ロールバック用の専用パイプラインを準備
  4. セキュリティ

    • シークレットは必ず外部シークレット管理システムで管理
    • サービスアカウントを使用し、個人アカウントでの自動実行を避ける
    • 最小権限の原則に基づいた RBAC 設定
  5. 可観測性

    • すべてのパイプラインに通知設定を追加
    • デプロイメントメトリクスの収集と可視化
    • 失敗時のアラート設定

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 が培った大規模サービス運用のノウハウを基に設計された、エンタープライズグレードの継続的デリバリープラットフォームである。本稿で解説した通り、その主要な特長は以下の通りである。

  1. マルチクラウド対応: AWS、GCP、Azure、Kubernetes など主要なクラウドプロバイダーに対応し、統一的なデプロイメントインターフェースを提供
  2. 高度なデプロイメント戦略: Red/Black、カナリア、ローリングアップデートなどの戦略をネイティブにサポート
  3. 自動カナリア分析: Kayenta によるメトリクスベースの自動カナリア判定で、リリースリスクを定量的に評価
  4. 豊富なパイプライン機能: ステージの並列実行、条件分岐、パイプラインテンプレートによる標準化
  5. エンタープライズセキュリティ: RBAC、サービスアカウント、外部シークレット管理との統合
  6. 拡張性: プラグインアーキテクチャ、カスタムステージ、ポリシーエンジン統合

一方、以下の課題も認識しておく必要がある。

  • 運用の複雑性: 11以上のマイクロサービスで構成されるため、運用負荷が高い
  • 学習コスト: 豊富な機能を使いこなすには相応の学習時間が必要
  • リソース消費: 本番環境での HA 構成には相当のコンピュートリソースが必要
  • GitOps 対応: ネイティブな GitOps サポートは限定的

13.2 今後の展望

Spinnaker エコシステムは引き続き進化を続けている。

  1. Kubernetes ネイティブ統合の深化: Kubernetes がデフォルトのデプロイメントターゲットとなり、より深い統合が進む
  2. Managed Delivery の成熟: 宣言的デリバリーモデルがより成熟し、パイプラインの自動生成が進む
  3. AI/ML 活用: デプロイメント最適化やカナリア分析における機械学習の活用
  4. サーバーレス対応の強化: AWS Lambda、Google Cloud Functions、Azure Functions への対応強化
  5. Operator パターンの普及: Spinnaker Operator による Kubernetes ネイティブな管理が標準に
  6. プラグインエコシステムの拡大: コミュニティによるプラグイン開発の活性化

Spinnaker は、大規模で複雑なデプロイメント要件を持つ組織にとって、依然として最も包括的な CD プラットフォームの一つである。適切に設計・運用することで、デプロイメントの速度と信頼性を大幅に向上させることが可能である。

13.3 参考リソース


本稿は Spinnaker の主要な機能とアーキテクチャの全容を把握するための概要ドキュメントであり、各トピックの詳細については公式ドキュメントを参照されたい。