- Section 23: CI/CD
- Table of Contents
- Development Workflow
- Git Repository Approaches
- Creating a GitHub Action
- Adding a CI Test Script
- Running Tests on PR Creation
- Output of Failing Tests
- Running Tests in Parallel
- Verifying a Test Run
- Selective Test Execution
- Deployment Options
- Creating a Hosted Cluster
- Reminder on Kubernetes Context
- Reminder on Swapping Contexts
- The Deployment Plan
- Building an Image in an Action
- Testing the Image Build
- Restarting the Deployment
- Applying Kubernetes Manifests
- Prod vs Dev Manifest Files
- Manual Secret Creation
- Don't Forget Ingress-Nginx!
- Testing Automated Deployment
- Additional Deploy Files
- A Successful Deploy!
- Buying a Domain Name
- Configuring the Domain Name
- One Small Fix
- One More Small Fix
- I Really Hope This Works
- Next Steps
- 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
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
- 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
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
- 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
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
- make a change and commit
- push a PR and see 4 tests run in parallel
- 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
- 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
- Install and Set Up kubectl
- doctl
- doctl auth init
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
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
- After merge to master
- Build a new auth image
- Push to docker hub
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
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
- 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
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
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
- make a change in auth folder
- commit the change and do a PR
- merge to master and deploy-auth action will execute
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
- 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
- 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
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: ''
});
}
};
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,
Goto chrome. Key in www.chesterheng.xyz to test
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 |