diff --git a/resources/google/gke.ts b/resources/google/gke.ts index 7841c47d..b1fdf56f 100644 --- a/resources/google/gke.ts +++ b/resources/google/gke.ts @@ -9,6 +9,9 @@ export const cluster = new google.container.v1.Cluster( releaseChannel: { channel: 'REGULAR' }, location: region, autopilot: { enabled: true }, + addonsConfig: { + gcsFuseCsiDriverConfig: { enabled: true }, + }, }, { provider: mainProvider, protect: true }, ); diff --git a/resources/index.ts b/resources/index.ts index 0ac27f68..3fd8f78a 100644 --- a/resources/index.ts +++ b/resources/index.ts @@ -18,7 +18,7 @@ import './kubernetes/provider'; // Kubernetes Deployments import './kubernetes/deployments/abax-minuba'; import './kubernetes/deployments/abax-procore'; -// import './kubernetes/deployments/matrix'; +import './kubernetes/deployments/matrix'; import './kubernetes/deployments/abax-vwfs'; import './kubernetes/todoist-github/deployment'; import './kubernetes/todoist-github/ingress'; diff --git a/resources/kubernetes/components/standard-deployment.ts b/resources/kubernetes/components/standard-deployment.ts index a581a1c1..f1e4d96b 100644 --- a/resources/kubernetes/components/standard-deployment.ts +++ b/resources/kubernetes/components/standard-deployment.ts @@ -148,6 +148,20 @@ export interface StandardDeploymentArgs */ securityContext?: pulumi.Input; + /** + * Annotations is an unstructured key value map stored with a resource that may be set by external + * tools to store and retrieve arbitrary metadata. + * They are not queryable and should be preserved when modifying objects. + * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations + */ + annotations?: Record>; + + /** + * ServiceAccountName is the name of the ServiceAccount to use to run this pod. + * More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + */ + serviceAccountName?: pulumi.Input; + /** * Arguments to the entrypoint. The container image's CMD is used if this is not provided. * Variable references $(VAR_NAME) are expanded using the container's environment. @@ -229,6 +243,8 @@ export class StandardDeployment extends pulumi.ComponentResource { args: entrypointArgs = undefined, volumeMounts = [], securityContext = undefined, + annotations = {}, + serviceAccountName = undefined, } = args; const publicPort = ports.find(p => p.name === 'public'); @@ -318,7 +334,6 @@ export class StandardDeployment extends pulumi.ComponentResource { } : undefined, }; - this.deployment = new k8s.apps.v1.Deployment( name, { @@ -326,6 +341,7 @@ export class StandardDeployment extends pulumi.ComponentResource { name, annotations: { 'pulumi.com/skipAwait': 'true', + ...annotations, }, }, spec: { @@ -343,6 +359,7 @@ export class StandardDeployment extends pulumi.ComponentResource { }, spec: { securityContext, + serviceAccountName, volumes, initContainers: pulumi.output(initContainers).apply(ic => ic.map(initContainer => ({ diff --git a/resources/kubernetes/deployments/matrix.ts b/resources/kubernetes/deployments/matrix.ts index 1fb19e6b..aae30e42 100644 --- a/resources/kubernetes/deployments/matrix.ts +++ b/resources/kubernetes/deployments/matrix.ts @@ -3,6 +3,8 @@ import * as pulumi from '@pulumi/pulumi'; import * as yaml from 'js-yaml'; import { StandardDatabase } from '../components/standard-database'; import { StandardDeployment } from '../components/standard-deployment'; +import { matrixDataBucket } from '../matrix/google'; +import { matrixK8sServiceAccount } from '../matrix/k8s'; import { provider } from '../provider'; const config = new pulumi.Config('matrix'); @@ -21,45 +23,7 @@ export const synapseDatabase = new StandardDatabase( const registrationSecret = config.requireSecret('registration-secret'); -const mediaVolume = new k8s.core.v1.PersistentVolumeClaim( - 'matrix-media-volume', - { - metadata: { - name: 'matrix-media-volume', - }, - spec: { - accessModes: ['ReadWriteOnce'], - resources: { - requests: { - storage: '1Gi', - }, - }, - storageClassName: 'standard', - }, - }, - { provider }, -); - -const dataVolume = new k8s.core.v1.PersistentVolumeClaim( - 'matrix-data-volume', - { - metadata: { - name: 'matrix-data-volume', - }, - spec: { - accessModes: ['ReadWriteOnce'], - resources: { - requests: { - storage: '1Gi', - }, - }, - storageClassName: 'standard', - }, - }, - { provider }, -); - -const secretVolume = new k8s.core.v1.Secret( +const secret = new k8s.core.v1.Secret( 'matrix-registration-secret', { metadata: { @@ -75,7 +39,9 @@ const secretVolume = new k8s.core.v1.Secret( }, { provider }, ); + const host = config.require('host'); + export const homeserverConfig = new k8s.core.v1.ConfigMap( 'matrix-homeserver-config', { @@ -93,7 +59,7 @@ export const homeserverConfig = new k8s.core.v1.ConfigMap( enable_registration_captcha: true, registration_requires_token: true, report_stats: true, - media_store_path: '/synapse/media_store', + media_store_path: '/synapse/data/media_store', pid_file: '/synapse/data/homeserver.pid', signing_key_path: '/synapse/data/bjerk.io.signing.key', database: { @@ -124,6 +90,13 @@ export const synapseDeployment = new StandardDeployment( image: config.require('synapse-image'), tag: config.require('synapse-tag'), host, + annotations: { + 'gke-gcsfuse/volumes': 'true', + 'gke-gcsfuse/cpu-limit': '500m', + 'gke-gcsfuse/memory-limit': '1Gi', + 'gke-gcsfuse/ephemeral-storage-limit': '50Gi', + }, + serviceAccountName: matrixK8sServiceAccount.metadata.name, healthCheckHttpPath: '/health', databaseDetails: synapseDatabase.databaseDetails, volumes: [ @@ -131,7 +104,7 @@ export const synapseDeployment = new StandardDeployment( name: 'secrets', emptyDir: {}, secret: { - secretName: secretVolume.metadata.name, + secretName: secret.metadata.name, }, }, { @@ -142,15 +115,14 @@ export const synapseDeployment = new StandardDeployment( }, }, { - name: 'data', - persistentVolumeClaim: { - claimName: dataVolume.metadata.name, - }, - }, - { - name: 'media', - persistentVolumeClaim: { - claimName: mediaVolume.metadata.name, + name: 'gcs-fuse-csi-ephemeral', + csi: { + driver: 'gcs.csi.infra.gke.io', + readOnly: true, + volumeAttributes: { + bucketName: matrixDataBucket.name, + mountOptions: 'implicit-dirs', + }, }, }, ], @@ -164,13 +136,9 @@ export const synapseDeployment = new StandardDeployment( mountPath: '/config', }, { - name: 'data', + name: 'gcs-fuse-csi-ephemeral', mountPath: '/synapse/data', }, - { - name: 'media', - mountPath: '/synapse/media_store', - }, ], // This is needed to tell synapse to load the secrets and config from these files command: [ diff --git a/resources/kubernetes/matrix/fuse-csi.ts b/resources/kubernetes/matrix/fuse-csi.ts new file mode 100644 index 00000000..bbb1bf93 --- /dev/null +++ b/resources/kubernetes/matrix/fuse-csi.ts @@ -0,0 +1,17 @@ +import * as google from '@pulumi/google-native'; +import * as pulumi from '@pulumi/pulumi'; +import { mainProvider, project } from '../../google/project'; +import { matrixSynapseServiceAccount } from './google'; +import { matrixK8sServiceAccount } from './k8s'; + +new google.iam.v1.ServiceAccountIamBinding( + 'iamServiceAccountIamBinding', + { + name: matrixSynapseServiceAccount.name, + role: 'roles/iam.serviceAccountTokenCreator', + members: [ + pulumi.interpolate`serviceAccount:${project.projectId}.svc.id.goog[${matrixK8sServiceAccount.metadata.namespace}/${matrixK8sServiceAccount.metadata.name}]`, + ], + }, + { provider: mainProvider }, +); diff --git a/resources/kubernetes/matrix/google.ts b/resources/kubernetes/matrix/google.ts new file mode 100644 index 00000000..db6d9b15 --- /dev/null +++ b/resources/kubernetes/matrix/google.ts @@ -0,0 +1,39 @@ +import * as google from '@pulumi/google-native'; +import { region } from '../../config'; +import { mainProvider } from '../../google/project'; + +// Create a Google Cloud Storage bucket +export const matrixDataBucket = new google.storage.v1.Bucket( + 'matrix-synapse-media-bucket', + { + name: 'matrix-synapse-media-bucket', + location: region, + }, + { provider: mainProvider }, +); + +// Create a Google Cloud service account +export const matrixSynapseServiceAccount = new google.iam.v1.ServiceAccount( + 'matrix-synapse-service-account', + { + accountId: 'matrix-synapse-service-account', + name: 'matrix-synapse-service-account', + displayName: 'Matrix Service Account', + }, + { provider: mainProvider }, +); + +// Grant the service account the role of Storage Object Viewer on the bucket +new google.storage.v1.BucketIamBinding( + 'bucket-iam-binding', + { + name: matrixDataBucket.name, + role: 'roles/storage.objectViewer', + members: [ + matrixSynapseServiceAccount.email.apply( + email => `serviceAccount:${email}`, + ), + ], + }, + { provider: mainProvider }, +); diff --git a/resources/kubernetes/matrix/k8s.ts b/resources/kubernetes/matrix/k8s.ts new file mode 100644 index 00000000..1483e05b --- /dev/null +++ b/resources/kubernetes/matrix/k8s.ts @@ -0,0 +1,28 @@ +import * as k8s from '@pulumi/kubernetes'; +import { provider } from '../provider'; +import { matrixSynapseServiceAccount } from './google'; + +export const matrixK8sServiceAccountNamespace = new k8s.core.v1.Namespace( + 'matrix-service-account-namespace', + { + metadata: { + name: 'service-account', + }, + }, + { provider }, +); + +export const matrixK8sServiceAccount = new k8s.core.v1.ServiceAccount( + 'matrix-service-account', + { + metadata: { + name: 'branches', + namespace: matrixK8sServiceAccountNamespace.metadata.name, + annotations: { + 'iam.gke.io/gcp-service-account': + matrixSynapseServiceAccount.email.apply(email => email), + }, + }, + }, + { provider }, +);