diff --git a/apps/templates/ghost-blog-folder.yaml b/apps/templates/ghost-blog-folder.yaml new file mode 100644 index 0000000..b1db297 --- /dev/null +++ b/apps/templates/ghost-blog-folder.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ghost-blog + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/rubenhensen/k8scd.git + targetRevision: HEAD + path: ghost + destination: + server: https://kubernetes.default.svc + namespace: ghost-blog + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true diff --git a/ghost-blog/ghost-config-es.yaml b/ghost-blog/ghost-config-es.yaml new file mode 100644 index 0000000..367b5f6 --- /dev/null +++ b/ghost-blog/ghost-config-es.yaml @@ -0,0 +1,64 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ghost-config-prod + namespace: ghost-blog +spec: + refreshInterval: "15m" + secretStoreRef: + name: vault-backend + kind: ClusterSecretStore + target: + name: ghost-config-prod + creationPolicy: Owner + data: + - secretKey: config.production.json + remoteRef: + key: kv/ghost-blog/config + template: + engine: gotmpl + data: | + { + "url": "https://blog.hensen.io", + "admin": { + "url": "https://blog.hensen.io" + }, + "server": { + "port": 2368, + "host": "0.0.0.0" + }, + "mail": { + "transport": "SMTP", + "from": "{{ .email_from }}", + "options": { + "service": "{{ .email_service }}", + "host": "{{ .email_host }}", + "port": {{ .email_port }}, + "secure": {{ .email_secure }}, + "auth": { + "user": "{{ .email_user }}", + "pass": "{{ .email_password }}" + } + } + }, + "logging": { + "transports": [ + "stdout" + ] + }, + "database": { + "client": "mysql", + "connection": + { + "host": "ghost-mysql-service", + "user": "{{ .db_user }}", + "password": "{{ .db_password }}", + "database": "{{ .db_name }}", + "port": "3306" + } + }, + "process": "local", + "paths": { + "contentPath": "/home/nonroot/app/ghost/content" + } + } diff --git a/ghost-blog/ghost-deployment.yaml b/ghost-blog/ghost-deployment.yaml new file mode 100644 index 0000000..6333b27 --- /dev/null +++ b/ghost-blog/ghost-deployment.yaml @@ -0,0 +1,154 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ghost-blog + namespace: ghost-blog + labels: + app: ghost-blog + app.kubernetes.io/name: ghost-blog + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: ghost + app.kubernetes.io/part-of: ghost-blog +spec: + replicas: 1 + selector: + matchLabels: + app: ghost-blog + minReadySeconds: 5 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 3 + revisionHistoryLimit: 4 + progressDeadlineSeconds: 600 + template: + metadata: + namespace: ghost-blog + labels: + app: ghost-blog + spec: + automountServiceAccountToken: false + volumes: + - name: ghost-content + persistentVolumeClaim: + claimName: ghost-content + - name: ghost-config-prod + secret: + secretName: ghost-config-prod + defaultMode: 420 + - name: tmp + emptyDir: + sizeLimit: 64Mi + + initContainers: + - name: permissions-fix + imagePullPolicy: IfNotPresent + image: docker.io/busybox:stable-musl + env: + - name: GHOST_INSTALL + value: /home/nonroot/app/ghost + - name: GHOST_CONTENT + value: /home/nonroot/app/ghost/content + - name: NODE_ENV + value: production + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + command: + - /bin/sh + - '-c' + - | + set -e + export DIRS='files logs apps themes data public settings images media' + echo 'Check if base dirs exists, if not, create them' + echo "Directories to check: $DIRS" + for dir in $DIRS; do + if [ ! -d $GHOST_CONTENT/$dir ]; then + echo "Creating $GHOST_CONTENT/$dir directory" + mkdir -pv $GHOST_CONTENT/$dir || echo "Error creating $GHOST_CONTENT/$dir directory" + fi + chown -Rfv 65532:65532 $GHOST_CONTENT/$dir && echo "chown ok on $dir" || echo "Error changing ownership of $GHOST_CONTENT/$dir directory" + done + exit 0 + volumeMounts: + - name: ghost-content + mountPath: /home/nonroot/app/ghost/content + readOnly: false + + containers: + - name: ghost-blog + image: ghcr.io/sredevopsorg/ghost-on-kubernetes:latest + imagePullPolicy: Always + ports: + - name: ghost + containerPort: 2368 + protocol: TCP + readinessProbe: + httpGet: + path: /ghost/api/v4/admin/site/ + port: ghost + httpHeaders: + - name: X-Forwarded-Proto + value: https + - name: Host + value: blog.hensen.io + periodSeconds: 10 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 3 + initialDelaySeconds: 10 + livenessProbe: + httpGet: + path: /ghost/api/v4/admin/site/ + port: ghost + httpHeaders: + - name: X-Forwarded-Proto + value: https + - name: Host + value: blog.hensen.io + periodSeconds: 300 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 1 + initialDelaySeconds: 30 + env: + - name: NODE_ENV + value: production + resources: + limits: + cpu: 800m + memory: 800Mi + requests: + cpu: 200m + memory: 400Mi + volumeMounts: + - name: ghost-content + mountPath: /home/nonroot/app/ghost/content + readOnly: false + - name: ghost-config-prod + readOnly: true + mountPath: /home/nonroot/app/ghost/config.production.json + subPath: config.production.json + - name: tmp + mountPath: /tmp + readOnly: false + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 65532 + restartPolicy: Always + terminationGracePeriodSeconds: 15 + dnsPolicy: ClusterFirst + securityContext: + seccompProfile: + type: RuntimeDefault diff --git a/ghost-blog/ingress.yaml b/ghost-blog/ingress.yaml new file mode 100644 index 0000000..3b99ca7 --- /dev/null +++ b/ghost-blog/ingress.yaml @@ -0,0 +1,32 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ghost-blog-ingress + namespace: ghost-blog + labels: + app: ghost-blog + app.kubernetes.io/name: ghost-blog-ingress + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: ingress + app.kubernetes.io/part-of: ghost-blog + annotations: + cert-manager.io/cluster-issuer: prod-cluster-issuer + nginx.ingress.kubernetes.io/proxy-body-size: 1G +spec: + ingressClassName: nginx + tls: + - hosts: + - blog.hensen.io + secretName: ghost-tls-secret + rules: + - host: blog.hensen.io + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: ghost-service + port: + name: ghost diff --git a/ghost-blog/mysql-es.yaml b/ghost-blog/mysql-es.yaml new file mode 100644 index 0000000..4aad1c7 --- /dev/null +++ b/ghost-blog/mysql-es.yaml @@ -0,0 +1,34 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ghost-mysql-env + namespace: ghost-blog +spec: + refreshInterval: "15m" + secretStoreRef: + name: vault-backend + kind: ClusterSecretStore + target: + name: ghost-mysql-env + creationPolicy: Owner + data: + - secretKey: MYSQL_DATABASE + remoteRef: + key: kv/ghost-blog/mysql + property: database + - secretKey: MYSQL_USER + remoteRef: + key: kv/ghost-blog/mysql + property: username + - secretKey: MYSQL_PASSWORD + remoteRef: + key: kv/ghost-blog/mysql + property: password + - secretKey: MYSQL_ROOT_PASSWORD + remoteRef: + key: kv/ghost-blog/mysql + property: root_password + - secretKey: MYSQL_HOST + remoteRef: + key: kv/ghost-blog/mysql + property: host diff --git a/ghost-blog/mysql.yaml b/ghost-blog/mysql.yaml new file mode 100644 index 0000000..195c117 --- /dev/null +++ b/ghost-blog/mysql.yaml @@ -0,0 +1,112 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: ghost-mysql + namespace: ghost-blog + labels: + app: ghost-mysql + app.kubernetes.io/name: ghost-mysql + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: database + app.kubernetes.io/part-of: ghost-blog +spec: + serviceName: ghost-mysql-service + replicas: 1 + selector: + matchLabels: + app: ghost-mysql + template: + metadata: + labels: + app: ghost-mysql + spec: + initContainers: + - name: ghost-mysql-init + securityContext: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + image: docker.io/busybox:stable-musl + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - | + set -e + echo 'Changing ownership of mysql mount directory to 65534:65534' + chown -Rfv 65534:65534 /mnt/mysql || echo 'Error changing ownership of mysql mount directory to 65534:65534' + echo 'Changing ownership of tmp mount directory to 65534:65534' + chown -Rfv 65534:65534 /mnt/tmp || echo 'Error changing ownership of tmp mount directory to 65534:65534' + echo 'Changing ownership of socket mount directory to 65534:65534' + chown -Rfv 65534:65534 /mnt/var/run/mysqld || echo 'Error changing ownership of socket mount directory to 65534:65534' + volumeMounts: + - name: ghost-mysql-volume + mountPath: /mnt/mysql + subPath: mysql-empty-subdir + readOnly: false + - name: ghost-mysql-tmp + mountPath: /mnt/tmp + readOnly: false + - name: ghost-mysql-socket + mountPath: /mnt/var/run/mysqld + readOnly: false + resources: + requests: + memory: 50Mi + cpu: 50m + limits: + memory: 100Mi + cpu: 100m + + containers: + - name: ghost-mysql + securityContext: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65534 + image: docker.io/mysql:8.4 + imagePullPolicy: IfNotPresent + envFrom: + - secretRef: + name: ghost-mysql-env + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m + ports: + - containerPort: 3306 + protocol: TCP + name: mysql + volumeMounts: + - name: ghost-mysql-volume + mountPath: /var/lib/mysql + subPath: mysql-empty-subdir + readOnly: false + - name: ghost-mysql-tmp + mountPath: /tmp + readOnly: false + - name: ghost-mysql-socket + mountPath: /var/run/mysqld + readOnly: false + + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + + volumes: + - name: ghost-mysql-volume + persistentVolumeClaim: + claimName: ghost-mysql-pvc + - name: ghost-mysql-tmp + emptyDir: + sizeLimit: 128Mi + - name: ghost-mysql-socket + emptyDir: + sizeLimit: 128Mi diff --git a/ghost-blog/namespace.yaml b/ghost-blog/namespace.yaml new file mode 100644 index 0000000..7d0fedc --- /dev/null +++ b/ghost-blog/namespace.yaml @@ -0,0 +1,11 @@ +# apiVersion: v1 +# kind: Namespace +# metadata: +# name: ghost-blog +# labels: +# app: ghost-blog +# app.kubernetes.io/name: ghost-blog +# app.kubernetes.io/instance: ghost-blog +# app.kubernetes.io/version: '5.92' +# app.kubernetes.io/component: namespace +# app.kubernetes.io/part-of: ghost-blog diff --git a/ghost-blog/pvc.yaml b/ghost-blog/pvc.yaml new file mode 100644 index 0000000..a82bd4e --- /dev/null +++ b/ghost-blog/pvc.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ghost-content + namespace: ghost-blog + labels: + app: ghost-blog + app.kubernetes.io/name: ghost-content + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: storage + app.kubernetes.io/part-of: ghost-blog +spec: + storageClassName: "longhorn" + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ghost-mysql-pvc + namespace: ghost-blog + labels: + app: ghost-mysql + app.kubernetes.io/name: ghost-mysql-pvc + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: database-storage + app.kubernetes.io/part-of: ghost-blog +spec: + storageClassName: "longhorn" + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi diff --git a/ghost-blog/services.yaml b/ghost-blog/services.yaml new file mode 100644 index 0000000..43551ad --- /dev/null +++ b/ghost-blog/services.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: Service +metadata: + name: ghost-service + namespace: ghost-blog + labels: + app: ghost-blog + app.kubernetes.io/name: ghost-service + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: service-frontend + app.kubernetes.io/part-of: ghost-blog +spec: + ports: + - port: 2368 + protocol: TCP + targetPort: ghost + name: ghost + type: ClusterIP + selector: + app: ghost-blog + +--- +apiVersion: v1 +kind: Service +metadata: + name: ghost-mysql-service + namespace: ghost-blog + labels: + app: ghost-mysql + app.kubernetes.io/name: ghost-mysql-service + app.kubernetes.io/instance: ghost-blog + app.kubernetes.io/version: '5.92' + app.kubernetes.io/component: service-database + app.kubernetes.io/part-of: ghost-blog +spec: + ports: + - port: 3306 + protocol: TCP + targetPort: mysql + name: mysql + type: ClusterIP + selector: + app: ghost-mysql