Skip to content

Latest commit

 

History

History
672 lines (519 loc) · 17.7 KB

section-23.md

File metadata and controls

672 lines (519 loc) · 17.7 KB

Section 23: CI/CD

Table of Contents

Development Workflow

⬆ back to top

Git Repository Approaches

  • Mono Repo Approach (selected)
  • Repo-Per-Service Approach (not selected, many overheads)

  • copy ticketing folder to Desktop
  • open ticketing folder with VS Code
git init
  • create .gitignore
node_modules
.DS_Store
git add .
git commit -m "initial commit"
  • goto github
  • create a new repo: ticketing
  • goto ticketing folder
git remote add origin https://github.com/chesterheng/ticketing.git
git push origin master

⬆ back to top

Creating a GitHub Action

name: tests

on: 
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd auth && npm install && npm run test:ci

⬆ back to top

Adding a CI Test Script

  • to run test only one time
  "test:ci": "jest"
git status
git add .
git commit -m "463. Adding a CI Test Script"
git pull origin master
git push origin master

⬆ back to top

Running Tests on PR Creation

Local Machine

  • Make change to code for tickets service
  • Commit code to a git branch (any besides master!)
git branch -m 464-running-tests-on-pr-creation
git add .
git commit -m "464. Running Tests on PR Creation"
git push --set-upstream origin 464-running-tests-on-pr-creation
  • Push branch to github

⬆ back to top

Output of Failing Tests

  • Change expect result to make test fails
git add .
git commit -m "465. Output of Failing Tests"
git push

Github

  • Github receives updated branch
  • You manually create a pull request to merge branch into master
  • Github automatically runs tests for project

⬆ back to top

Running Tests in Parallel

name: tests-orders

on: 
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd orders && npm install && npm run test:ci
name: tests-payments

on: 
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd payments && npm install && npm run test:ci
name: tests-tickets

on: 
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd tickets && npm install && npm run test:ci

⬆ back to top

Verifying a Test Run

  • make a change and commit
  • push a PR and see 4 tests run in parallel

⬆ back to top

Selective Test Execution

  • paths parameter
  • run auth test if path change is auth folder
name: tests-auth

on: 
  pull_request:
    paths:
      - 'auth/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd auth && npm install && npm run test:ci

⬆ back to top

Deployment Options

  • A credit card will be required to move forward
  • $1 for a domain name, $0.72 a day to run the cluster
  • You can probably find a coupon code to pay $0 for the cluster

3 nodes, each with 2gb ram + 1 cpu PLUS EXTRAS

Digital Ocean AWS Google Cloud Azure
$40/month $126/month $113/month $72/month
Really easy to use Hardest Easy Easy
  • Digital Ocean is selected

⬆ back to top

Creating a Hosted Cluster

⬆ back to top

Reminder on Kubernetes Context

⬆ back to top

Reminder on Swapping Contexts

Action Command
Authenticating with Doctl doctl auth init
Get connection info for our new cluster doctl kubernetes cluster kubeconfig save <cluster_name>
List all contexts kubectl config view
Use a different context kubectl config use-context <context_name>
doctl kubernetes cluster kubeconfig save ticketing
kubectl get pods
kubectl get nodes
kubectl config view
kubectl config use-context docker-desktop
kubectl get nodes
kubectl get pods

⬆ back to top

The Deployment Plan

⬆ back to top

Building an Image in an Action

name: deploy-auth

on:
  push:
    branches: 
      - master
    paths:
      - 'auth/**'

jobs:
  build:
   runs-on: ubuntu-latest
   steps:
     - uses: actions/checkout@v2
     - run: cd auth && docker build -t chesterheng/auth .
     - run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
       env:
         DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
         DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
     - run: docker push chesterheng/auth

⬆ back to top

Testing the Image Build

  • After merge to master
  • Build a new auth image
  • Push to docker hub

⬆ back to top

Restarting the Deployment

name: deploy-auth

on:
  push:
    branches: 
      - master
    paths:
      - 'auth/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd auth && docker build -t chesterheng/auth .
      - run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
      - run: docker push chesterheng/auth
      - uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
      - run: doctl kubernetes cluster kubeconfig save ticketing
      - run: kubectl rollout restart deployment auth-depl

⬆ back to top

Applying Kubernetes Manifests

name: deploy-manifests

on:
  push:
    branches: 
      - master
    paths:
      - 'infra/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
      - run: doctl kubernetes cluster kubeconfig save ticketing
      - run: kubectl apply -f infra/k8s

⬆ back to top

Prod vs Dev Manifest Files

  • create k8s-dev folder to store ingress-srv.yaml for development environmemt
  • create k8s-prod folder to store ingress-srv.yaml for production environmemt
name: deploy-manifests

on:
  push:
    branches: 
      - master
    paths:
      - 'infra/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
      - run: doctl kubernetes cluster kubeconfig save ticketing
      - run: kubectl apply -f infra/k8s && kubectl apply -f infra/k8s-prod

⬆ back to top

Manual Secret Creation

kubectl config view
kubectl config use-context do-sgp1-ticketing
kubectl create secret generic jwt-secret --from-literal=JWT_KEY=sadghgjgshdajh
kubectl create secret generic stripe-secret --from-literal=STRIPE_KEY=sk_test_...
kubectl get sesrets

⬆ back to top

Don't Forget Ingress-Nginx!

NGINX Ingress Controller - Digital Ocean

kubectl config view
kubectl config use-context do-sgp1-ticketing
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/do/deploy.yaml

⬆ back to top

Testing Automated Deployment

  • make a change in auth folder
  • commit the change and do a PR
  • merge to master and deploy-auth action will execute

⬆ back to top

Additional Deploy Files

kubectl get pods
kubectl logs auth-depl-db79bbf7f-dsjnr

Add in the following similar deploy files

  • deploy-client.yaml
  • deploy-expiration.yaml
  • deploy-orders.yaml
  • deploy-payments.yaml
  • deploy-tickets.yaml
name: deploy-client

on:
  push:
    branches:
      - master
    paths:
      - 'client/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: cd client && docker build -t chesterheng/client .
      - run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
      - run: docker push chesterheng/client
      - uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
      - run: doctl kubernetes cluster kubeconfig save ticketing
      - run: kubectl rollout restart deployment client-depl

⬆ back to top

A Successful Deploy!

  • make changes in auth folder
  • make changes in client folder
  • make changes in expiration folder
  • make changes in orders folder
  • make changes in payments folder
  • make a PR and merge

⬆ back to top

Buying a Domain Name

⬆ back to top

Configuring the Domain Name

  • add digitalocean DNS to domain name
    • ns1.digitalocean.com
    • ns2.digitalocean.com
    • ns3.digitalocean.com

  • update live domain name at ingress-srv.yaml
# k8s/ingress-srv.yaml
- host: www.chesterheng.xyz

⬆ back to top

One Small Fix

Accessing pods over a managed load-balancer from inside the cluster

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-service
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/use-regex: 'true'
spec:
  rules:
    - host: www.chesterheng.xyz
      http:
        paths:
          - path: /api/payments/?(.*)
            backend:
              serviceName: payments-srv
              servicePort: 3000
          - path: /api/users/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/tickets/?(.*)
            backend:
              serviceName: tickets-srv
              servicePort: 3000
          - path: /api/orders/?(.*)
            backend:
              serviceName: orders-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-srv
              servicePort: 3000
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: 'true'
    service.beta.kubernetes.io/do-loadbalancer-hostname: 'www.chesterheng.xyz'
  labels:
    helm.sh/chart: ingress-nginx-2.0.3
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.32.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/component: controller
// build-client.js
import axios from 'axios';

export default ({ req }) => {
  if(typeof window === 'undefined') {
    // we are on the server

    return axios.create({
      baseURL: 'http://www.chesterheng.xyz',
      headers: req.headers
    });
  } else {
    // we are on the browser

    return axios.create({
      baseURL: ''
    });
  }
};

⬆ back to top

One More Small Fix

You may recall that we configured all of our services to only use cookies when the user is on an HTTPS connection. This will cause auth to fail while we do this initial deploy of our app, since we don't have HTTPS setup right now.

To disable the HTTPS checking, go to the app.ts file in the auth, orders, tickets, and payments services. At the cookie-session middleware, change the following:

secure: process.env.NODE_ENV !== 'test',

to:

secure: false,

⬆ back to top

I Really Hope This Works

Goto chrome. Key in www.chesterheng.xyz to test

⬆ back to top

Next Steps

Destroy

  • load balancer - $10/month
  • kubernetes cluster - $30/month

Feature Details
Add in HTTPS See cert-manager.io
Add in Email Support Send a user an email after they have paid for an order. Create a new service using Mailchimp/Sendgrid/similar
Add in 'build' steps for our prod cluster Right now we are still running our services + the client in 'dev' mode. Add in additional Dockerfiles to build each service prior to deployment
Create a staging cluster Our teammates might want to test out our app manually before we deploy it. Maybe we could add in a new Github workflow to watch for pushes to a new branch of 'staging'. Create a new cluster that you will deploy to when you push to this branch

⬆ back to top