Skip to content

Instantly share code, notes, and snippets.

@ddunkin
Last active September 21, 2022 15:10
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ddunkin/a74ad80503ec034ecb296e2d01ab5872 to your computer and use it in GitHub Desktop.
Save ddunkin/a74ad80503ec034ecb296e2d01ab5872 to your computer and use it in GitHub Desktop.
Pulumi functions to setup and add a Google Cloud SQL Proxy sidecar to a Kubernetes deployment
import * as pulumi from '@pulumi/pulumi';
import * as gcp from '@pulumi/gcp';
import * as k8s from '@pulumi/kubernetes';
import * as k8sInputApi from '@pulumi/kubernetes/types/input';
const serviceAccountKeys = new Map<string, gcp.serviceAccount.Key>();
/**
* Creates a service account and key, and sets the cloudsql.client role in IAM.
*/
export function createCloudSqlProxyServiceAccount(args: gcp.serviceAccount.AccountArgs) {
return pulumi.output(args.accountId).apply(accountId => {
const serviceAccount = new gcp.serviceAccount.Account(accountId, args);
serviceAccountKeys.set(
accountId,
new gcp.serviceAccount.Key(`${accountId}-key`, {
serviceAccountId: serviceAccount.id,
})
);
new gcp.projects.IAMMember(accountId, {
member: serviceAccount.email.apply(email => `serviceAccount:${email}`),
role: 'roles/cloudsql.client',
});
return serviceAccount;
});
}
const credentialsSecrets = new Map<string, k8s.core.v1.Secret>();
function getOrCreateSecret(serviceAccountId: string, namespaceName: pulumi.Input<string> | undefined) {
let credentialsSecret = credentialsSecrets.get(serviceAccountId);
if (!credentialsSecret) {
const serviceAccountKey = serviceAccountKeys.get(serviceAccountId);
if (!serviceAccountKey) {
throw new pulumi.RunError(`Service account not set up: ${serviceAccountId}`);
}
credentialsSecret = new k8s.core.v1.Secret(`${serviceAccountId}-credentials`, {
metadata: {
namespace: namespaceName,
},
stringData: {
'credentials.json': serviceAccountKey.privateKey.apply(x => Buffer.from(x, 'base64').toString('utf8')),
},
});
credentialsSecrets.set(serviceAccountId, credentialsSecret);
}
return credentialsSecret;
}
/**
* Injects cloudsql-proxy sidecar and dependencies into a deployment.
*/
export function injectCloudSqlProxy(
dbInstance: gcp.sql.DatabaseInstance,
serviceAccountId: pulumi.Input<string>,
args: k8sInputApi.apps.v1.Deployment
) {
const credentialsSecretName = pulumi.all([args.metadata, serviceAccountId]).apply(([metadata, serviceAccountId]) => {
const credentialsSecret = getOrCreateSecret(serviceAccountId, metadata && metadata.namespace);
return credentialsSecret.metadata.apply(x => x.name);
});
args.spec = pulumi
.all([
args.spec,
dbInstance.project,
dbInstance.region,
dbInstance.name,
dbInstance.databaseVersion,
credentialsSecretName,
])
.apply(([spec, project, region, instanceName, databaseVersion, credentialsSecretName]) => {
// project = project || gcp.config.project;
region = region || gcp.config.region;
// prettier-ignore
const dbPort = databaseVersion!.startsWith('MYSQL') ? 3306 :
databaseVersion!.startsWith('POSTGRES') ? 5432 :
undefined;
spec.template = pulumi.output(spec.template!).apply(template => {
template.spec = pulumi.output(template.spec!).apply(pod => {
pod.containers = pulumi.output(pod.containers).apply(containers => {
containers = containers || [];
containers.push({
name: 'cloudsql-proxy',
image: 'gcr.io/cloudsql-docker/gce-proxy:1.11',
command: [
'/cloud_sql_proxy',
`-instances=${project}:${region}:${instanceName}=tcp:${dbPort}`,
'-credential_file=/var/run/secrets/cloudsql/credentials.json',
],
securityContext: {
runAsUser: 2, // non-root user
allowPrivilegeEscalation: false,
},
volumeMounts: [
{
name: 'cloudsql-instance-credentials',
mountPath: '/var/run/secrets/cloudsql',
readOnly: true,
},
],
});
return containers;
});
pod.volumes = pulumi.output(pod.volumes).apply(volumes => {
volumes = volumes || [];
volumes.push({
name: 'cloudsql-instance-credentials',
secret: {
secretName: credentialsSecretName,
},
});
return volumes;
});
return pod;
});
return template;
});
return spec;
});
return args;
}
const dbInstance = new gcp.sql.DatabaseInstance('my-instance', {
// ...
});
const serviceAccount = createCloudSqlProxyServiceAccount({
accountId: 'my-cloudsql-proxy',
});
const serviceAccountId = serviceAccount.apply(x => x.accountId);
const deployment = new k8s.apps.v1.Deployment(
'my-deployment',
injectCloudSqlProxy(dbInstance, serviceAccountId, {
// ...
})
);
@wyozi
Copy link

wyozi commented Apr 6, 2021

FYI Creating new resources inside apply can be problematic: https://www.pulumi.com/docs/intro/concepts/inputs-outputs/#apply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment