app/mastodon: deploy
Change-Id: I88c104d1a8d5627355b01a8c48dc235635fca5ed
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1421
Reviewed-by: implr <implr@hackerspace.pl>
diff --git a/app/mastodon/kube/mastodon.libsonnet b/app/mastodon/kube/mastodon.libsonnet
new file mode 100644
index 0000000..383c3ae
--- /dev/null
+++ b/app/mastodon/kube/mastodon.libsonnet
@@ -0,0 +1,326 @@
+local kube = import "../../../kube/kube.libsonnet";
+local postgres = import "../../../kube/postgres.libsonnet";
+local redis = import "../../../kube/redis.libsonnet";
+
+{
+ local app = self,
+ local cfg = app.cfg,
+
+ cfg:: {
+ namespace: error "cfg.namespace must be set",
+ # Domain as seen in the fediverse.
+ localDomain: error "cfg.localDomain must be set",
+ # Domain where the web interface is running. If different,
+ # localDomain's real server must be configured to forward
+ # /.well-known/webfinger to webDomain.
+ webDomain: cfg.localDomain,
+ images: {
+ mastodon: "tootsuite/mastodon:v4.0.2@sha256:21c20181a5d44ff553e9e8f7d8d2e53b2551cc8c7ac900760e056445b88e7438",
+ },
+ passwords: {
+ # generate however you like
+ postgres: error "cfg.secrets.postgres must be set",
+ # generate however you like
+ redis: error "cfg.secrets.redis must be set",
+ },
+ smtp: {
+ user: "mastodon",
+ from: "mastodon-noreply@hackerspace.pl",
+ # from mail server
+ password: error "cfg.smtp.password must be set",
+ },
+ secrets: {
+ # generate with podman run --rm -it tootsuite/mastodon:v4.0.2 bundle exec rake secret
+ keyBase: error "cfg.secrets.keyBase must be set",
+ # generate with podman run --rm -it tootsuite/mastodon:v4.0.2 bundle exec rake secret
+ otp: error "cfg.secrets.otp must be set",
+ # generate with podman run --rm -it tootsuite/mastodon:v4.0.2 bundle exec rake mastodon:webpush:generate_vapid_key
+ vapid: {
+ private: error "cfg.secrets.vapid.private must be set",
+ public: error "cfg.secrets.vapid.public must be set",
+ }
+ },
+ oidc: {
+ clientID: error "cfg.oidc.clientID must be set",
+ clientSecret: error "cfg.oidc.clientSecret must be set",
+ },
+ objectStorage: {
+ bucket: error "cfg.objectStorage.bucket must be set",
+ accessKeyId: error "cfg.objectStorage.accessKeyId must be set",
+ secretAccessKey: error "cfg.objectStorage.secretAccessKey must be set",
+ },
+
+ scaling: {
+ web: 1,
+ sidekiq: 1,
+ },
+ },
+
+ // Unified env var based config used for {web, streaming, sidekiq}.
+ // Sample available in https://github.com/mastodon/mastodon/blob/main/.env.production.sample
+ env:: {
+ LOCAL_DOMAIN: cfg.localDomain,
+ WEB_DOMAIN: cfg.webDomain,
+
+ // REDIS_PASS is not used directly by the apps, it's just used to embed
+ // a secret fragment into REDIS_URL.
+ REDIS_PASS: kube.SecretKeyRef(app.config, "redis-pass"),
+ REDIS_URL: "redis://:$(REDIS_PASS)@%s" % [app.redis.svc.host_colon_port],
+
+ DB_HOST: app.postgres.svc.host,
+ DB_USER: "mastodon",
+ DB_NAME: "mastodon",
+ DB_PASS: kube.SecretKeyRef(app.config, "postgres-pass"),
+ DB_PORT: "5432",
+
+ ES_ENABLED: "false",
+
+ SECRET_KEY_BASE: kube.SecretKeyRef(app.config, "secret-key-base"),
+ OTP_SECRET: kube.SecretKeyRef(app.config, "otp-secret"),
+
+ VAPID_PRIVATE_KEY: kube.SecretKeyRef(app.config, "vapid-private"),
+ VAPID_PUBLIC_KEY: kube.SecretKeyRef(app.config, "vapid-public"),
+
+ SMTP_SERVER: "mail.hackerspace.pl",
+ SMTP_PORT: "587",
+ SMTP_LOGIN: "mastodon",
+ SMTP_PASSWORD: kube.SecretKeyRef(app.config, "smtp-password"),
+ SMTP_FROM_ADDRESS: "mastodon-noreply@hackerspace.pl",
+
+ S3_ENABLED: "true",
+ S3_BUCKET: cfg.objectStorage.bucket,
+ AWS_ACCESS_KEY_ID: kube.SecretKeyRef(app.config, "object-access-key-id"),
+ AWS_SECRET_ACCESS_KEY: kube.SecretKeyRef(app.config, "object-secret-access-key"),
+ S3_HOSTNAME: "object.ceph-waw3.hswaw.net",
+ S3_ENDPOINT: "https://object.ceph-waw3.hswaw.net",
+
+ IP_RETENTION_PERIOD: "31556952",
+ SESSION_RETENTION_PERIOD: "31556952",
+
+ OIDC_ENABLED: "true",
+ OIDC_DISPLAY_NAME: "Use Warsaw Hackerspace SSO",
+ OIDC_ISSUER: "https://sso.hackerspace.pl",
+ OIDC_DISCOVERY: "false",
+ OIDC_SCOPE: "openid,profile:read",
+ OIDC_UID_FIELD: "uid",
+ OIDC_CLIENT_ID: cfg.oidc.clientId,
+ OIDC_REDIRECT_URI: "https://%s/auth/auth/openid_connect/callback" % [cfg.webDomain],
+ OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED: "true",
+ OIDC_CLIENT_SECRET: kube.SecretKeyRef(app.config, "oidc-client-secret"),
+ OIDC_AUTH_ENDPOINT: "https://sso.hackerspace.pl/oauth/authorize",
+ OIDC_TOKEN_ENDPOINT: "https://sso.hackerspace.pl/oauth/token",
+ OIDC_USER_INFO_ENDPOINT: "https://sso.hackerspace.pl/api/1/userinfo",
+ OIDC_JWKS_URI: "https://sso.hackerspace.pl/.well-known/jwks.json",
+ },
+
+ namespace: kube.Namespace(cfg.namespace),
+ local ns = self.namespace,
+
+ postgres: postgres {
+ cfg+: {
+ namespace: cfg.namespace,
+ appName: "mastodon",
+ database: "mastodon",
+ username: "mastodon",
+ prefix: "waw3-",
+ password: kube.SecretKeyRef(app.config, "postgres-pass"),
+ storageClassName: "waw-hdd-redundant-3",
+ storageSize: "100Gi",
+ },
+ },
+
+ redis: redis {
+ cfg+: {
+ namespace: cfg.namespace,
+ appName: "mastodon",
+ storageClassName: "waw-hdd-redundant-3",
+ prefix: "waw3-",
+ password: kube.SecretKeyRef(app.config, "redis-pass"),
+ },
+ },
+
+ web: ns.Contain(kube.Deployment("web")) {
+ spec+: {
+ minReadySeconds: 10,
+ replicas: cfg.scaling.web,
+ template+: {
+ spec+: {
+ initContainers_: {
+ migrate: kube.Container("migrate") {
+ image: cfg.images.mastodon,
+ env_: app.env {
+ SKIP_POST_DEPLOYMENT_MIGRATIONS: "true",
+ },
+ command: [
+ "bundle", "exec",
+ "rails", "db:migrate",
+ ],
+ },
+ },
+ containers_: {
+ default: kube.Container("default") {
+ image: cfg.images.mastodon,
+ env_: app.env,
+ command: [
+ "bundle", "exec",
+ "rails", "s", "-p", "3000",
+ ],
+ ports_: {
+ web: { containerPort: 3000 },
+ },
+ readinessProbe: {
+ httpGet: {
+ path: "/health",
+ port: "web",
+ },
+ failureThreshold: 10,
+ periodSeconds: 5,
+ },
+ resources: {
+ requests: {
+ cpu: "250m",
+ memory: "1024M",
+ },
+ limits: {
+ cpu: "1",
+ memory: "1024M",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+
+ sidekiq: ns.Contain(kube.Deployment("sidekiq")) {
+ spec+: {
+ replicas: cfg.scaling.sidekiq,
+ minReadySeconds: 10,
+ template+: {
+ spec+: {
+ containers_: {
+ default: kube.Container("default") {
+ image: cfg.images.mastodon,
+ env_: app.env,
+ command: [
+ "bundle", "exec",
+ "sidekiq",
+ ],
+ resources: {
+ requests: {
+ cpu: "250m",
+ memory: "1024M",
+ },
+ limits: {
+ cpu: "1",
+ memory: "1024M",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+
+ streaming: ns.Contain(kube.Deployment("streaming")) {
+ spec+: {
+ minReadySeconds: 10,
+ template+: {
+ spec+: {
+ containers_: {
+ default: kube.Container("default") {
+ image: cfg.images.mastodon,
+ env_: app.env {
+ "STREAMING_CLUSTER_NUM": "1",
+ },
+ command: [
+ "node", "./streaming",
+ ],
+ ports_: {
+ web: { containerPort: 4000 },
+ },
+ readinessProbe: {
+ httpGet: {
+ path: "/api/v1/streaming/health",
+ port: "web",
+ },
+ failureThreshold: 1,
+ periodSeconds: 5,
+ },
+ resources: {
+ requests: {
+ cpu: "250m",
+ memory: "1024M",
+ },
+ limits: {
+ cpu: "1",
+ memory: "1024M",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+
+ svcWeb: ns.Contain(kube.Service("web")) {
+ target_pod: app.web.spec.template,
+ },
+
+ svcStreaming: ns.Contain(kube.Service("streaming")) {
+ target_pod: app.streaming.spec.template,
+ },
+
+
+ ingress: ns.Contain(kube.Ingress("mastodon")) {
+ metadata+: {
+ annotations+: {
+ "kubernetes.io/tls-acme": "true",
+ "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+ "nginx.ingress.kubernetes.io/proxy-body-size": "0",
+ },
+ },
+ spec+: {
+ tls: [
+ {
+ hosts: [cfg.webDomain],
+ secretName: "mastodon-ingress-tls",
+ },
+ ],
+ rules: [
+ {
+ host: cfg.webDomain,
+ http: {
+ paths: [
+ { path: "/", backend: app.svcWeb.name_port },
+ { path: "/api/v1/streaming", backend: app.svcStreaming.name_port },
+ ],
+ },
+ },
+ ],
+ },
+ },
+
+ config: ns.Contain(kube.Secret("config")) {
+ data_: {
+ "postgres-pass": cfg.passwords.postgres,
+ "redis-pass": cfg.passwords.redis,
+
+ "secret-key-base": cfg.secrets.keyBase,
+ "otp-secret": cfg.secrets.otp,
+
+ "vapid-private": cfg.secrets.vapid.private,
+ "vapid-public": cfg.secrets.vapid.public,
+
+ "smtp-password": cfg.smtp.password,
+
+ "object-access-key-id": cfg.objectStorage.accessKeyId,
+ "object-secret-access-key": cfg.objectStorage.secretAccessKey,
+
+ "oidc-client-secret": cfg.oidc.clientSecret,
+ },
+ },
+}