Sharing Outputs with Continuous Deployment
Export Outputs of a Terraform Stack to a Kubernetes CD Service
It's frequently necessary to take infrastructure created in a stack and use the data in a Service being deployed to K8s. A few usecases where this can be important:
- exporting IAM role ARNs for access to various AWS services like S3 or SQS
- exporting DB connection strings to configure as k8s secrets for a webserver
- exporting a dynamically created S3 bucket name to use in your deployed service
This also facilitates end-to-end self-service, as you no longer need a human in-the-loop to apply that last mile configuration, plus you get continuous reconciliation in the event a terraform change implies a recreation of those resources.
End To End Example
We do this a lot in our service catalog, available here: https://github.com/pluralsh/scaffolds/tree/main/catalogs. A basic example would be something like our Airbyte setup, where the terraform stack has an outputs file like so:
output "access_key_id" {
value = aws_iam_access_key.airbyte.id
}
output "secret_access_key" {
value = aws_iam_access_key.airbyte.secret
sensitive = true
}
output "postgres_host" {
value = try(module.db.db_instance_address, "")
}
output "postgres_password" {
value = random_password.password.result
sensitive = true
}
output "oidc_cookie_secret" {
value = random_password.oidc_cookie.result
sensitive = true
}
output "oidc_client_id" {
value = plural_oidc_provider.airbyte.client_id
sensitive = true
}
output "oidc_client_secret" {
value = plural_oidc_provider.airbyte.client_secret
sensitive = true
}Airbyte needs fixed aws access keys to communicate with S3 and also there's a dynamically generated OIDC client that's used for auth against it's webserver, alongside postgres credentials.
with a InfrastructureStack resource will like this:
apiVersion: deployments.plural.sh/v1alpha1
kind: InfrastructureStack
metadata:
name: airbyte-data
namespace: apps
spec:
detach: false
type: TERRAFORM
approval: true
manageState: true
git:
ref: main
folder: terraform/apps/airbyte/data
repositoryRef:
name: infra
namespace: infra
clusterRef:
name: mgmt
namespace: infraAll of this data needs to be used by the service that is actually deployed to K8s, so it will explicitly "import" that stack with its imports declaration, like so:
apiVersion: deployments.plural.sh/v1alpha1
kind: ServiceDeployment
metadata:
name: airbyte-data
namespace: apps
spec:
namespace: airbyte
git:
folder: helm/airbyte/data
ref: main
repositoryRef:
kind: GitRepository
name: infra
namespace: infra
helm:
version: "1.x.x"
chart: airbyte
release: airbyte
ignoreHooks: false
url: https://airbytehq.github.io/helm-charts
valuesFiles:
- airbyte.yaml.liquid
imports:
- stackRef:
name: airbyte-data # notice this is the same as the metadata.name and metadata.namespace of the `InfrastructureStack` CRD to resolve the ref
namespace: apps
configuration:
cluster: data
hostname: airbyte.example.com
bucket: airbyte-bucket
region: us-east-2
clusterRef:
kind: Cluster
name: data
namespace: infraWhen that is present, it will allow us to template the outputs under the key imports["airbyte-data"].{output_field_name} in any .liquid values or yaml file for the service, an example for the airbyte helm chart airbyte.yaml.liquid values file:
global:
deploymentMode: oss
edition: community
airbyteUrl: {{ configuration.hostname }}
storage:
type: S3
storageSecretName: airbyte-airbyte-secrets
s3:
region: {{ configuration.region }}
authenticationType: credentials
accessKeyId: {{ imports["airbyte-data"].access_key_id }}
accessKeyIdSecretKey: AWS_ACCESS_KEY_ID
secretAccessKey: {{ imports["airbyte-data"].secret_access_key }}
secretAccessKeySecretKey: AWS_SECRET_ACCESS_KEY
bucket:
log: {{ configuration.bucket }}
state: {{ configuration.bucket }}
workloadOutput: {{ configuration.bucket }}
database:
type: external
database: airbyte
host: {{ imports["airbyte-data"].postgres_host }}
port: "5432"
secretName: airbyte-airbyte-secrets
user: airbyte
userSecretKey: DATABASE_USER
password: {{ imports["airbyte-data"].postgres_password }}
passwordSecretKey: DATABASE_PASSWORD
postgresql:
enabled: false
externalDatabase:
database: airbyte
host: {{ imports["airbyte-data"].postgres_host }}
user: airbyte
existingSecret: ~
password: {{ imports["airbyte-data"].postgres_password }}
port: 5432
webapp:
ingress:
enabled: false
podAnnotations:
security.plural.sh/oauth-env-secret: airbyte-proxy-config
podLabels:
security.plural.sh/inject-oauth-sidecar: "true"You an read more about templating here. In this case it's going to pass these variables through helm and configure the necessary secrets and yaml structures with the provided information.
Info:
The imports structure is a map from stack name to stack outputs, eg a nested map type like { string => { string => any } }