Deployment Automation

Overview

The **Deployment Automation** module provides an infrastructure-as-code solution that automates the deployment of blockchain node daemons, indexers, and API services onto Kubernetes clusters. This automation is designed to efficiently provision and configure all essential blockchain coinstack components while managing dependencies, service lifecycles, networking, and observability integrations.

The module leverages Pulumi, a programmable infrastructure tool, to declaratively define Kubernetes resources such as StatefulSets, Deployments, Services, ConfigMaps, IngressRoutes, and Certificates, enabling reproducible, scalable, and maintainable deployments. This system abstracts the complexities of manual Kubernetes configuration and ensures consistent environments across blockchain coinstacks.


Core Concepts and Purpose

This module solves the problem of complex, error-prone manual Kubernetes deployments by providing a unified, extensible, and programmable way to deploy blockchain infrastructure consistent with operational best practices.


How Deployment Automation Works

Entry Point: Coinstack Deployment

Each blockchain coinstack defines a Pulumi entry script (e.g., `node/coinstacks/litecoin/pulumi/index.ts`) that:

  1. Reads environment samples (sample.env) and configuration files.

  2. Retrieves Kubernetes access credentials and namespace.

  3. Constructs service arguments for each coinstack service (daemon, indexer, etc.) with ports, environment variables, probes, and config maps.

  4. Calls the core method deployCoinstack with all relevant parameters.

Example snippet from Litecoin coinstack deployment script:

const coinServiceArgs = config.statefulService?.services?.map((service): CoinServiceArgs => {
  switch (service.name) {
    case 'daemon':
      return {
        ...service,
        ports: { 'daemon-rpc': { port: 8332 } },
        readinessProbe: { periodSeconds: 30, failureThreshold: 10 },
      }
    case 'indexer':
      return {
        ...service,
        ...defaultBlockbookServiceArgs,
        configMapData: { 'indexer-config.json': readFileSync('../indexer/config.json').toString() },
      }
    default:
      throw new Error(`no support for coin service: ${service.name}`)
  }
})

return deployCoinstack({
  appName,
  coinServiceArgs,
  coinstack,
  coinstackType: 'node',
  config,
  kubeconfig,
  namespace,
  sampleEnv,
})

Deploying a Coinstack (deployCoinstack)

The `deployCoinstack` function (in `pulumi/src/coinstack.ts`) orchestrates deployment by:

This approach centralizes coinstack deployment logic and enforces consistent naming, labeling, and configuration patterns.

Key excerpt from `deployCoinstack`:

const secretData = getSecretData(sampleEnv)
new k8s.core.v1.Secret(assetName, { metadata: { name: assetName, namespace }, stringData: secretData }, { provider })

await deployApi({ ...args, assetName, baseImageName, provider })

const coinServices = (coinServiceArgs ?? []).map((_args) => createCoinService(_args, assetName))

await deployStatefulService(appName, assetName, provider, namespace, config, coinServices, volumes)

Defining Stateful Services (createCoinService and deployStatefulService)

Excerpt showing container and volume specification:

const serviceContainer: k8s.types.input.core.v1.Container = {
  name,
  image: args.image,
  command: initScript && !args.command ? ['/init.sh'] : args.command,
  env,
  ports: ports.map(({ port: containerPort, name }) => ({ containerPort, name })),
  startupProbe: startupProbe && { exec: { command: ['/startup.sh'] } },
  livenessProbe: livenessProbe && { exec: { command: ['/liveness.sh'] } },
  readinessProbe: readinessProbe && { exec: { command: ['/readiness.sh'] } },
  volumeMounts: [
    { name: `data-${args.name}`, mountPath: args.dataDir ?? '/data' },
    { name: 'config-map', mountPath: '/init.sh', subPath: `${args.name}-init.sh` },
    // ... other scripts
  ],
}

API Service Deployment (deployApi)

The API services are deployed separately as Kubernetes Deployments (stateless), managed by `deployApi` in `pulumi/src/api.ts`. This function:

Excerpt illustrating image building and Deployment creation:

const tag = await getCoinstackHash(coinstack, buildArgs, coinstackType)
const imageName = config.dockerhub ? `${config.dockerhub.username}/${repositoryName}:${tag}` : `shapeshiftdao/${repositoryName}:${tag}`

// Build and push image if needed (not shown)

const apiDeployment = new k8s.apps.v1.Deployment(
  name,
  {
    spec: {
      replicas: config.api.replicas,
      template: {
        spec: {
          containers: [{
            name: tier,
            image: imageName,
            ports: [{ containerPort: port, name: 'http' }],
            readinessProbe: { httpGet: { path: '/health', port } },
            livenessProbe: { httpGet: { path: '/health', port } },
            // ...
          }],
        },
      },
    },
  },
  { provider, dependsOn: deployDependencies }
)

Interaction with Other System Parts


Important Concepts and Design Patterns


Visual Diagram: Deployment Automation Workflow

flowchart TD
  Start[Start Deployment] --> ReadConfig[Read Env & Config Files]
  ReadConfig --> PrepareServices[Prepare Coin Services Args]
  PrepareServices --> DeploySecret[Create Kubernetes Secret]
  DeploySecret --> DeployAPI[Deploy API Deployment & Service]
  DeployAPI --> DeployStateful[Deploy StatefulSets for Daemon & Indexer]
  DeployStateful --> CreateIngress[Configure Ingress & TLS Certificates]
  CreateIngress --> SetupMonitoring[Setup Prometheus ServiceMonitors]
  SetupMonitoring --> End[Deployment Complete]

Summary

The **Deployment Automation** module is the backbone of the ShapeShift Unchained infrastructure setup, enabling teams to deploy blockchain coinstacks reliably and consistently on Kubernetes. Through Pulumi, it abstracts complex Kubernetes resource management into programmable, reusable functions that handle service lifecycle scripts, health probes, image management, ingress routing, and monitoring integration. It supports multiple coinstacks by allowing custom configurations per blockchain service, ensuring the operational health and secure accessibility of blockchain nodes and APIs.