From 8da6e47f679cea778742d97b6dc0ab87f498e9e7 Mon Sep 17 00:00:00 2001 From: FranciZhong Date: Fri, 4 Oct 2024 05:05:14 +1000 Subject: [PATCH] merge: deployment workflows --- .dockerignore | 5 +- .github/workflows/{deploy-gke.yml => cd.yml} | 48 ++++---- .github/workflows/db-migrate.yml | 6 +- .github/workflows/deploy.yml | 111 ++++++++++++++++++ k8s/chatemo-deployment.yml | 83 +++++++++++++ k8s/chatemo-service.yml | 12 ++ k8s/postgres-deployment.yml | 45 +++++++ k8s/postgres-pvc.yml | 11 ++ k8s/postgres-service.yml | 12 ++ k8s/redis-deployment.yml | 41 +++++++ k8s/redis-pvc.yml | 11 ++ k8s/redis-service.yml | 12 ++ .../migration.sql | 0 src/components/layout/ChatLayout.tsx | 10 +- src/lib/constants.ts | 1 + 15 files changed, 375 insertions(+), 33 deletions(-) rename .github/workflows/{deploy-gke.yml => cd.yml} (59%) create mode 100644 .github/workflows/deploy.yml create mode 100644 k8s/chatemo-deployment.yml create mode 100644 k8s/chatemo-service.yml create mode 100644 k8s/postgres-deployment.yml create mode 100644 k8s/postgres-pvc.yml create mode 100644 k8s/postgres-service.yml create mode 100644 k8s/redis-deployment.yml create mode 100644 k8s/redis-pvc.yml create mode 100644 k8s/redis-service.yml rename prisma/migrations/{20240928072107_init => 20241003151000_init}/migration.sql (100%) diff --git a/.dockerignore b/.dockerignore index 65e60a1..081ff4c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,7 @@ node_modules npm-debug.log .git .env -.env.* \ No newline at end of file +.env.* + +# Ignore generated credentials from google-github-actions/auth +gha-creds-*.json \ No newline at end of file diff --git a/.github/workflows/deploy-gke.yml b/.github/workflows/cd.yml similarity index 59% rename from .github/workflows/deploy-gke.yml rename to .github/workflows/cd.yml index 5001f70..9fcc6d2 100644 --- a/.github/workflows/deploy-gke.yml +++ b/.github/workflows/cd.yml @@ -1,22 +1,21 @@ -name: 'Build and Deploy to GKE' +name: 'CD' on: push: branches: - - '"deploy-workflow"' + - 'main' pull_request: branches: - - '"main"' + - 'main' env: - PROJECT_ID: 'chatemo' - GAR_LOCATION: 'australia-southeast1' - GKE_CLUSTER: 'chatemo-cluster' - GKE_ZONE: 'australia-southeast1-c' - DEPLOYMENT_NAME: 'chatemo' - REPOSITORY: 'francizhong' - IMAGE: 'chatemo' - WORKLOAD_IDENTITY_PROVIDER: 'projects/642179943052/locations/global/workloadIdentityPools/github-pool/providers/git-actions-provider' + PROJECT_ID: ${{ secrets.PROJECT_ID }} + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }} + GKE_ZONE: ${{ secrets.GKE_ZONE }} + DEPLOYMENT_NAME: ${{ secrets.DEPLOYMENT_NAME }} + REPOSITORY: ${{ secrets.REPOSITORY }} + IMAGE: ${{ secrets.IMAGE }} jobs: setup-build-publish-deploy: @@ -39,7 +38,9 @@ jobs: name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v2' with: - workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + project_id: '${{ env.PROJECT_ID }}' + workload_identity_provider: '${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}' + service_account: ${{ secrets.SERVICE_ACCOUNT }} # Authenticate Docker to Google Cloud Artifact Registry - name: 'Docker Auth' @@ -58,28 +59,27 @@ jobs: # Build the Docker image - name: 'Build and push Docker container' + id: build-image run: |- + gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://${GAR_LOCATION}-docker.pkg.dev + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + LATEST_DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:latest" docker build \ --tag "${DOCKER_TAG}" \ + --tag "${LATEST_DOCKER_TAG}" \ --build-arg GITHUB_SHA="${GITHUB_SHA}" \ --build-arg GITHUB_REF="${GITHUB_REF}" \ . docker push "${DOCKER_TAG}" + docker push "${LATEST_DOCKER_TAG}" + echo "::set-output name=image::${DOCKER_TAG}" - # Set up kustomize - - name: 'Set up Kustomize' - run: |- - curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz - chmod u+x ./kustomize - - # Deploy the Docker image to the GKE cluster - - name: 'Deploy to GKE' + # Update the image in the GKE deployment + - name: 'Update Image in GKE' run: |- - # replacing the image name in the k8s template - ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA - ./kustomize build . | kubectl apply -f - - kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl set image deployment/${{ env.DEPLOYMENT_NAME }} ${{env.IMAGE}}=${{ steps.build-image.outputs.image }} + kubectl rollout status deployment/${{ env.DEPLOYMENT_NAME }} kubectl get services -o wide diff --git a/.github/workflows/db-migrate.yml b/.github/workflows/db-migrate.yml index 036726c..29f3a4e 100644 --- a/.github/workflows/db-migrate.yml +++ b/.github/workflows/db-migrate.yml @@ -1,10 +1,10 @@ -name: Deploy +name: Prisma DB Migrate on: push: paths: - prisma/migrations/** # Only run this workflow when migrations are updated branches: - - main + - deploy-workflow pull_request: paths: @@ -28,4 +28,4 @@ jobs: - name: Apply all pending migrations to the database run: npx prisma migrate deploy env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} + DATABASE_URL: ${{ secrets.EXTERNAL_DATABASE_URL }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3c86372 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,111 @@ +name: 'Initialize Deployment to GKE' + +on: + workflow_dispatch: + +env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }} + GKE_ZONE: ${{ secrets.GKE_ZONE }} + REPOSITORY: ${{ secrets.REPOSITORY }} + IMAGE: ${{ secrets.IMAGE }} + +jobs: + setup-build-publish-deploy: + name: 'Deploy Postgres, Redis and Build Chatemo as the latest' + runs-on: 'ubuntu-latest' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@v4' + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@v2' + with: + project_id: '${{ env.PROJECT_ID }}' + workload_identity_provider: '${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}' + service_account: ${{ secrets.SERVICE_ACCOUNT }} + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@v3' + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@v2' + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Set up config map and secrets + - name: 'Apply config map and secret' + run: |- + kubectl create configmap env-config \ + --from-literal=NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL }} \ + --from-literal=GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} \ + --from-literal=AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \ + --from-literal=AWS_REGION=${{ secrets.AWS_REGION }} \ + --from-literal=AWS_S3_BUCKET_NAME=${{ secrets.AWS_S3_BUCKET_NAME }} \ + --dry-run=client -o yaml | kubectl apply -f - + + kubectl create secret generic env-secret \ + --from-literal=REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }} \ + --from-literal=POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} \ + --from-literal=DATABASE_URL=${{ secrets.DATABASE_URL }} \ + --from-literal=NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }} \ + --from-literal=GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} \ + --from-literal=AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ + --dry-run=client -o yaml | kubectl apply -f - + + # Deploy Postgres + - name: 'Deploy Postgres in GKE' + run: |- + kubectl apply -f ./k8s/postgres-pvc.yml + kubectl apply -f ./k8s/postgres-deployment.yml + kubectl apply -f ./k8s/postgres-service.yml + kubectl rollout status deployment/postgres + + # Deploy Redis + - name: 'Deploy Redis in GKE' + run: |- + kubectl apply -f ./k8s/redis-pvc.yml + kubectl apply -f ./k8s/redis-deployment.yml + kubectl apply -f ./k8s/redis-service.yml + kubectl rollout status deployment/redis + + # Build the Docker image with latest tag + - name: 'Build and push Docker container' + id: build-image + run: |- + gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://${GAR_LOCATION}-docker.pkg.dev + + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:latest" + + docker build \ + --tag "${DOCKER_TAG}" \ + . + + docker push "${DOCKER_TAG}" + echo "::set-output name=image::${DOCKER_TAG}" + + # Deploy Redis + - name: 'Deploy Chatemo in GKE' + run: |- + kubectl apply -f ./k8s/chatemo-deployment.yml + kubectl apply -f ./k8s/chatemo-service.yml + kubectl rollout status deployment/chatemo + kubectl get services -o wide diff --git a/k8s/chatemo-deployment.yml b/k8s/chatemo-deployment.yml new file mode 100644 index 0000000..1ca348e --- /dev/null +++ b/k8s/chatemo-deployment.yml @@ -0,0 +1,83 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: chatemo +spec: + replicas: 2 + selector: + matchLabels: + app: chatemo + template: + metadata: + labels: + app: chatemo + spec: + restartPolicy: Always + containers: + - name: chatemo + image: australia-southeast1-docker.pkg.dev/chatemo/francizhong/chatemo:latest + imagePullPolicy: Always + ports: + - containerPort: 3000 + resources: + requests: + memory: '512Mi' + cpu: '500m' + limits: + memory: '1Gi' + cpu: '1' + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: env-secret + key: DATABASE_URL + - name: REDIS_HOST + value: 'redis' + - name: REDIS_PORT + value: '6379' + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: env-secret + key: REDIS_PASSWORD + - name: NEXTAUTH_URL + valueFrom: + configMapKeyRef: + name: env-config + key: NEXTAUTH_URL + - name: NEXTAUTH_SECRET + valueFrom: + secretKeyRef: + name: env-secret + key: NEXTAUTH_SECRET + - name: GOOGLE_CLIENT_ID + valueFrom: + configMapKeyRef: + name: env-config + key: GOOGLE_CLIENT_ID + - name: GOOGLE_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: env-secret + key: GOOGLE_CLIENT_SECRET + - name: AWS_ACCESS_KEY_ID + valueFrom: + configMapKeyRef: + name: env-config + key: AWS_ACCESS_KEY_ID + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: env-secret + key: AWS_SECRET_ACCESS_KEY + - name: AWS_REGION + valueFrom: + configMapKeyRef: + name: env-config + key: AWS_REGION + - name: AWS_S3_BUCKET_NAME + valueFrom: + configMapKeyRef: + name: env-config + key: AWS_S3_BUCKET_NAME diff --git a/k8s/chatemo-service.yml b/k8s/chatemo-service.yml new file mode 100644 index 0000000..fc7b040 --- /dev/null +++ b/k8s/chatemo-service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: chatemo +spec: + type: LoadBalancer + selector: + app: chatemo + ports: + - protocol: TCP + port: 80 + targetPort: 3000 diff --git a/k8s/postgres-deployment.yml b/k8s/postgres-deployment.yml new file mode 100644 index 0000000..07176be --- /dev/null +++ b/k8s/postgres-deployment.yml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + restartPolicy: Always + containers: + - name: postgres + image: postgres:17-alpine + ports: + - containerPort: 5432 + resources: + requests: + memory: '512Mi' + cpu: '500m' + limits: + memory: '1Gi' + cpu: '1' + env: + - name: POSTGRES_DB + value: 'chatemo' + - name: POSTGRES_USER + value: 'postgres' + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: env-secret + key: POSTGRES_PASSWORD + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-storage + subPath: chatemo-data + volumes: + - name: postgres-storage + persistentVolumeClaim: + claimName: postgres-pvc diff --git a/k8s/postgres-pvc.yml b/k8s/postgres-pvc.yml new file mode 100644 index 0000000..4c0e661 --- /dev/null +++ b/k8s/postgres-pvc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi diff --git a/k8s/postgres-service.yml b/k8s/postgres-service.yml new file mode 100644 index 0000000..e8bb129 --- /dev/null +++ b/k8s/postgres-service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + type: LoadBalancer + selector: + app: postgres + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 diff --git a/k8s/redis-deployment.yml b/k8s/redis-deployment.yml new file mode 100644 index 0000000..d99c983 --- /dev/null +++ b/k8s/redis-deployment.yml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + restartPolicy: Always + containers: + - name: redis + image: redis:7-alpine + ports: + - containerPort: 6379 + resources: + requests: + memory: '512Mi' + cpu: '500m' + limits: + memory: '1Gi' + cpu: '1' + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: env-secret + key: REDIS_PASSWORD + args: ['redis-server', '--requirepass', '$(REDIS_PASSWORD)'] + volumeMounts: + - mountPath: /data + name: redis-storage + volumes: + - name: redis-storage + persistentVolumeClaim: + claimName: redis-pvc diff --git a/k8s/redis-pvc.yml b/k8s/redis-pvc.yml new file mode 100644 index 0000000..0fb0f11 --- /dev/null +++ b/k8s/redis-pvc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-pvc +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi diff --git a/k8s/redis-service.yml b/k8s/redis-service.yml new file mode 100644 index 0000000..f8883e9 --- /dev/null +++ b/k8s/redis-service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + type: LoadBalancer + selector: + app: redis + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 diff --git a/prisma/migrations/20240928072107_init/migration.sql b/prisma/migrations/20241003151000_init/migration.sql similarity index 100% rename from prisma/migrations/20240928072107_init/migration.sql rename to prisma/migrations/20241003151000_init/migration.sql diff --git a/src/components/layout/ChatLayout.tsx b/src/components/layout/ChatLayout.tsx index 04ec608..d693092 100644 --- a/src/components/layout/ChatLayout.tsx +++ b/src/components/layout/ChatLayout.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ModalType } from '@/lib/constants'; +import { ModalType, SOCKET_HOST } from '@/lib/constants'; import { AgentEvent, ChannelEvent, @@ -99,10 +99,10 @@ const ChatLayout: React.FC = (props) => { // init socket and event listeners useEffect(() => { if (!socket) { - console.log( - `Try to build a socket connected to ${process.env.NEXT_PUBLIC_SOCKET_HOST}` - ); - connect(process.env.NEXT_PUBLIC_SOCKET_HOST || 'localhost:3000'); + // console.log( + // `Try to build a socket connected to ${process.env.NEXT_PUBLIC_SOCKET_HOST}` + // ); + connect(process.env.NEXT_PUBLIC_SOCKET_HOST || SOCKET_HOST); } }, [socket, connect]); diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 76b1164..b29c4f0 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -111,6 +111,7 @@ export const allowedImageTypes = [ ]; export const DEFAULT_TRANSFORM_DELAY = 300; +export const SOCKET_HOST = 'http://www.chatemo.chat'; export const GITHUB_LINK = 'https://github.com/FranciZhong/chatemo'; export const MAX_IMAGE_FILE_SIZE = 5 * 1024 * 1024;