diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a06f1e5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +node_modules +.nuxt +npm-debug.log +Dockerfile +.github +deployments +ecosystem.config.js diff --git a/.github/workflows/assembly-production-deployment-automation.yml b/.github/workflows/assembly-production-deployment-automation.yml index 38b29bd..5b55a07 100644 --- a/.github/workflows/assembly-production-deployment-automation.yml +++ b/.github/workflows/assembly-production-deployment-automation.yml @@ -1,27 +1,136 @@ -name: production-deployment-automation - -on: - push: - branches: - - 'master' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Deploy to Server - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.REMOTE_HOST }} - username: ${{ secrets.REMOTE_USER }} - key: ${{ secrets.SERVER_SSH_KEY }} - port: 22 - script: | - cd ${{ secrets.REMOTE_TARGET }} && \ - git reset --hard && git clean -df && \ - git pull && \ - yarn install && \ - yarn build --if-present && \ - yarn generate --if-present && \ - pm2 restart assembly --update-env +name: production-deployment-automation + +on: + push: + branches: + - 'master' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build_image: + name: Build Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Metadata for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + event=push,type=sha,format=short + event=tag,type=ref + + - name: Fetch Version + id: version + run: echo "RELEASE_VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT + + - name: Build and Push Docker Image + uses: docker/build-push-action@v3 + with: + context: . + push: true + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha, scope=assembly-nuxt + cache-to: type=gha, scope=assembly-nuxt, mode=max + + - name: Update Deployment file + run: | + #!/bin/bash + + set -e + set -o pipefail + + # Convert this to small case + MATCH="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + MATCH=${MATCH,,} + + # Write the contents to a file + echo "${{ steps.meta.outputs.tags }}" > tmp + + # Check if there is a vX.Y.Z | v.X.Y tag + TAG=$({ grep -Poh "$MATCH:v\d+.\d+.?(\d+)?" tmp || true; }) + + # If there is not, Go back to using sha- + if [ -z "${TAG}" ]; then + TAG=$({ grep -Poh "$MATCH:sha-.+" tmp || true; }) + fi + + RESP=$({ grep -Poh '' deployments/assembly-nuxt.assembly.instadapp.io.production.yml || true; }) + if [ -z "${RESP}" ]; then + echo "deployments/assembly-nuxt.assembly.instadapp.io.production.yml does not contain marker" + exit 1; + fi + sed -i "s||$TAG|" deployments/assembly-nuxt.assembly.instadapp.io.production.yml + + RESP=$({ grep -Poh '' deployments/assembly-nuxt.assembly.instadapp.io.production.yml || true; }) + if [ -z "${RESP}" ]; then + echo "deployments/assembly-nuxt.assembly.instadapp.io.production.yml does not contain marker" + exit 1; + fi + sed -i "s||${{ secrets.VAULT_ADDR }}|" deployments/assembly-nuxt.assembly.instadapp.io.production.yml + + RESP=$({ grep -Poh '' deployments/assembly-nuxt.assembly.instadapp.io.production.yml || true; }) + if [ -z "${RESP}" ]; then + echo "deployments/assembly-nuxt.assembly.instadapp.io.production.yml does not contain marker" + exit 1; + fi + sed -i "s||${{ secrets.VAULT_NS }}|" deployments/assembly-nuxt.assembly.instadapp.io.production.yml + + - name: Upload Deployment file + uses: actions/upload-artifact@v3 + with: + name: k8s-deployment-config + path: deployments/assembly-nuxt.assembly.instadapp.io.production.yml + retention-days: 1 + + deploy: + name: Deploy to Production + runs-on: ubuntu-latest + environment: + name: 'Production' + url: 'https://assembly.instadapp.io' + needs: build_image + permissions: + contents: read + packages: read + steps: + - name: Fetch Deployment file + id: download + uses: actions/download-artifact@v3 + with: + name: k8s-deployment-config + - name: Read CA Certificate + run: | + # Write CA to disk + #!/bin/sh + echo ${{ secrets.NYC_KUBERNETES_CERTIFICATE }} | base64 -d > ca.crt + - name: Deploy to NYC Cluster + run: kubectl apply -f ${{ steps.download.outputs.download-path }} --kubeconfig=/dev/null --server="${{ secrets.NYC_KUBERNETES_ADDRESS }}" --token="${{ secrets.NYC_KUBERNETES_TOKEN }}" --certificate-authority=ca.crt + - name: Verify NYC Deployment + run: kubectl rollout status deployment/assembly-nuxt --server="${{ secrets.NYC_KUBERNETES_ADDRESS }}" --token="${{ secrets.NYC_KUBERNETES_TOKEN }}" --certificate-authority=ca.crt + - name: Erase CA Certificate + run: | + # Erase CA from disk + #!/bin/sh + rm ca.crt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..57b3e0d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# -------------> The build Image +FROM node:14 AS build + +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +COPY package.json ./ +COPY yarn.lock ./ + +RUN yarn install --frozen-lockfile + +# Bundle app source +COPY . . + +RUN npx nuxt build + +RUN npx nuxt generate + +RUN yarn install --frozen-lockfile --production + +# ---------------> The Production Image +FROM node:14-alpine + +WORKDIR /usr/src/app + +COPY --from=build /usr/src/app /usr/src/app + +CMD [ "npx", "nuxt", "start" ] diff --git a/deployments/assembly-nuxt.assembly.instadapp.io.production.yml b/deployments/assembly-nuxt.assembly.instadapp.io.production.yml new file mode 100644 index 0000000..25668d0 --- /dev/null +++ b/deployments/assembly-nuxt.assembly.instadapp.io.production.yml @@ -0,0 +1,109 @@ +# Deployment configuration for this application +apiVersion: apps/v1 +kind: Deployment +metadata: + name: assembly-nuxt + namespace: default + labels: + app: assembly-nuxt +spec: + minReadySeconds: 5 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 2 + maxUnavailable: 0 + replicas: 2 + selector: + matchLabels: + app: assembly-nuxt + template: + metadata: + labels: + app: assembly-nuxt + annotations: + vault.security.banzaicloud.io/vault-addr: + vault.security.banzaicloud.io/vault-namespace: + vault.security.banzaicloud.io/vault-serviceaccount: 'default' + vault.security.banzaicloud.io/vault-role: 'assembly-nuxt' + spec: + topologySpreadConstraints: + - maxSkew: 2 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app: assembly-nuxt + containers: + - name: assembly-nuxt + image: + imagePullPolicy: Always + resources: + requests: + cpu: '1' + memory: '1G' + limits: + cpu: '2' + memory: '5.5G' + ports: + - containerPort: 5000 + env: + - name: NUXT_HOST + value: 0.0.0.0 + - name: NODE_ENV + value: production + - name: PORT + value: '5000' + - name: INFURA_ID + value: 'vault:frontend/assembly.instadapp.io/assembly-nuxt#INFURA_ID' + - name: PORTIS_ID + value: 'vault:frontend/assembly.instadapp.io/assembly-nuxt#PORTIS_ID' + readinessProbe: + httpGet: + path: / + port: 5000 + initialDelaySeconds: 3 + periodSeconds: 5 + livenessProbe: + httpGet: + path: / + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 10 + imagePullSecrets: + - name: dockerconfigjson-github-com +--- +# Service configuration for this application +apiVersion: v1 +kind: Service +metadata: + name: assembly-nuxt + namespace: default + labels: + app: assembly-nuxt +spec: + ports: + - port: 80 + targetPort: 5000 + selector: + app: assembly-nuxt +--- +# Autoscaling configuration for this application +apiVersion: autoscaling/v2beta2 +kind: HorizontalPodAutoscaler +metadata: + name: assembly-nuxt +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: assembly-nuxt + minReplicas: 2 + maxReplicas: 20 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80