Leading-edge DevOps automation plus GitOps deployments
slides: autodeploy.bretfisher.com
–
Stick a YAML file in .github/workflows/ for each workflow with this basic structure:
name: learn-github-actions # must be unique in repo
on: [push, pull_request] # or dozens of other events
jobs:
check-bats-version: # Multiple jobs run in parallel by default
runs-on: ubuntu-latest # change this to windows-latest or macos-latest or your own runners
steps:
- uses: actions/checkout@v3 # change this to x.x.x to pin to a specific version
- uses: actions/setup-node@v3 # pin to commit hash for highest security
with:
node-version: '14' # `with` lets you pass options to the action
- run: npm install -g bats # easily run shell commands with `run`
- run: bats -v
- Introduce yourself to the group (Keep it short, 60 seconds or less, please)
- Where are you Zooming from in the world?
- Question: What GHA workflows are you wanting to build or migrate from old CI tools?

Let’s focus on wordsmith-web for now
wordsmith-webNew workflow, then click setup a workflow yourself.github/workflows/docker-build.ymltags to our repo (last line) ghcr.io/<orgname>/wordsmith-web:latest<orgname> must be lowercase to work with Docker!
:latestorg.opencontainers.image.title=wordsmith-api
org.opencontainers.image.source=https://github.com/MostlyDevOps/wordsmith-api
org.opencontainers.image.version=gha-4715332302
org.opencontainers.image.created=2023-04-16T21:16:33.149Z
org.opencontainers.image.revision=f217725e1bf9e1032debb15df43b06513907d58bdocker-build.yml and replace all text with our advanced exampleghcr.io/<orgname>/wordsmith-webTagging in a production team is an artform
tags: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=pr
type=ref,event=branch
type=semver,pattern={{version}}wordsmith-web
Read repository contents and packages permissionsnone (docs)permissions:
actions: read|write|none
checks: read|write|none
contents: read|write|none
deployments: read|write|none
id-token: read|write|none
issues: read|write|none
discussions: read|write|none
packages: read|write|none
pages: read|write|none
pull-requests: read|write|none
repository-projects: read|write|none
security-events: read|write|none
statuses: read|write|nonepermissions: {}More automation means you need more guardrails
main branch protection rule<something>-<date> or <something>-<git-sha> are good<something>-<date>-<git-sha> is good latest for friendly dev use, but don’t use it in prodpr-<number> tag# .github/dependabot.yml in every repo with GHAs
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"You can do this in the extra credit slides later
Avoid multiple commits in the same branch/PR from running at the same time
Add this at workflow level, before jobs, in every workflow
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: trueYou can do this in the extra credit slides later
.github repo (not as D.R.Y.)(full GHA resources list on bretfisher.com)
This will add a popular PR Commenter Action
Very handy for helping humans know what tags were created
First, add this highlighted line to the job permissions in docker-build.yml
permissions:
contents: read
packages: write # needed to push docker image to ghcr.io
pull-requests: write # needed to create and update comments in PRsThen, add the Actions steps I created a template for to the end of your Docker build workflow.
Commit to a new PR and watch builds. Notice a new comment in the PR!

Avoid multiple commits in the same branch/PR from running at the same time
Usually, when you push a new commit, you don’t care if the last commit passed Actions
This snippet will cancel any previously-started, yet still active runs of this workflow on the same branch
Add this at workflow root level, usually after on: but before jobs:, in every workflow
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: trueThe workflow_dispatch event. Follow these docs
gh CLILet’s add a Dependabot config to our wordsmith-web repo
.github folder called dependabot.yml---
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"Then you’ll see PRs like this:

This only works for image tags that use semver tags
package-ecosystem to the dependabot.yml file - package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "[docker] "Then you’ll see PRs like this:

This also works for Kubernetes and Helm! Repeat the above YAML for each directory you want to update.
Note: Dependabot can update many dependencies, check it out
Extra, Extra Credit: Watch my Super-Linter walkthrough live stream
wordsmith-web repo, go to the Actions tabWhat was your lightbulb moment today about GitHub Actions?
After you speak, pick the next raised hand to speak
Watch a video of this section here

.github and put workflows in there

This is my fav
name: Docker Build
on:
push:
branches:
- main
pull_request:
jobs:
call-docker-build:
name: Call Docker Build
uses: mostlydevops/actions/.github/workflows/reusable-docker-build.yaml@main
permissions:
packages: write
pull-requests: write
with:
image-name: ghcr.io/mostlydevops/wordsmith-webactions repoactions.github/dependabot.yml file with this content:version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"Note: If the repo was private, this setting would show up. We would need to change it:
Settings > Actions > General > Access 
Imagine all these being centrally controlled and automated for all software repos in your org
.github/workflows that you want to reuse elsewhere on:workflow_call event to the workflowinputs and secrets to that event input and secret values into the workflow stepsinputs and secrets to the calling workflowsactions repo<org>/actions/.github/workflows/reusable-trivy.yml
This Trivy workflow will scan our images for vulnerabilities
reusable-trivy.yml with this content:name: Trivy Scan Image
on:
workflow_call:
jobs:
build:
name: CVE Image Scan
runs-on: ubuntu-latest
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.11.2
with:
image-ref: ghcr.io/${{ github.repository }}:latest
env:
TRIVY_USERNAME: ${{ github.actor }}
TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}workflow_call events. It will ignore push and pull_request eventswordsmith-web repo.github/workflows/call-trivy.yml and paste this in<org>. Then change <pr-branch-name> to the branch you just created in the actions repodraft, because you’ll change it to main branch before mergingname: Call Trivy
on:
push:
branches: [main]
pull_request:
jobs:
scan:
name: Scan
uses: <org>/actions/.github/workflows/reusable-trivy.yml@<pr-branch-name>Question 1: What workflow will you likely build first in your work projects?
Question 2: What workflow would most impress your boss?
After you speak, pick the next raised hand to speak
with and secretsjobs:
call-docker-build:
name: Call Docker Build
uses: bretfisher/docker-build-workflow/.github/workflows/reusable-docker-build.yaml@main
secrets:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
with:
dockerhub-enable: true
image-names: ghcr.io/${{ github.repository }}
tag-rules: |
type=ref,event=pr
type=raw,value=gha-${{ github.run_id }}Reusable workflows accept data via inputs
inputs array to our reusable workflow on:workflow_call: event${{ inputs.<input-name> }}with and secrets key:values to our calling workflow<org>/actions/.github/workflows/reusable-trivy.yml
inputs lines under the workflow_call: eventon:
workflow_call:
inputs:
image:
description: Image to scan
required: true
type: stringtrivy action step at bottom - name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.11.2
with:
image-ref: ${{ inputs.image }}wordsmith-web workflow PR<org>/wordsmith-web/.github/workflows/call-trivy.yml
<org> to yoursjobs:
scan:
name: Scan
uses: <org>/actions/.github/workflows/reusable-trivy.yml@<pr-branch-name>
with:
image: 'ghcr.io/<org>/wordsmith-web:latest'Commit that and watch the Action run in the wordsmith-web repo
We now have a working reusable workflow
latest We’ll need to ensure our Docker Build creates a unique image tag for each PR commit
Also, we’ll need to wait for Docker Build to finish before trying to scan
actions, gha-reusable, or reusable-workflows are all fine namesreusable-*.ymlcall-*.ymlworkflow_call with push or pull_request events in the same file./template/ full of calling workflow examplesGITHUB_TOKEN and these permsname: Docker Build
on:
push:
branches:
- main
pull_request:
jobs:
call-docker-build:
name: Call Docker Build
uses: mostlydevops/actions/.github/workflows/reusable-docker-build.yaml@main
permissions:
packages: write
pull-requests: write
with:
image-name: ghcr.io/mostlydevops/wordsmith-webIn your actions repo
actions repo, create a new file .github/workflows/reusable-docker-build.ymlIn your wordsmith-web and wordsmith-api repos, replace the workflow
.github/workflows/docker-build.yml.github/workflows/call-docker-build.yml and add this content30 uses: to point to your new reusable workflow filewordsmith-web PRwordsmith-apiOnce you got that workflow working, you’ll be ready for Extra Credit and Argo CD next week
bretfisher/docker-build-workflow If you didn't already, read through the comments in the calling and called workflows
Features of note:
inputs for maximum flexibilitystable-<date>-<sha>With unlimited time and resources, what workflow would you build for your team?
(full GHA resources list on bretfisher.com)
workflow_call<org>/wordsmith-web/.github/workflows/call-trivy.yml
Solved by implementing our homework: Docker Build Reusable Workflow
wordsmith-web:gha-${{ github.run_id }}outputs:
# only outputs the unique gha- image tag that's unique to each GHA run
image-tag: ${{ steps.image-tag.outputs.image-tag }}Solved by implementing our homework: Docker Build Reusable Workflow
Solved by adding the Trivy job to the Docker Build workflow with a needs: key
reusable-trivy.yml workflow in our actions repocall-docker-build.yml workflow in our wordsmith-web repo<org> to your org name# 2nd job in Workflow, 2 spaces indented
scan:
name: Scan Image
needs: call-docker-build
uses: <org>/actions/.github/workflows/reusable-trivy.yml@main
with:
image: 'ghcr.io/<org>/wordsmith-web:${{ needs.call-docker-build.outputs.image-tag }}'Creating a starter "calling" workflow for Docker builds
Provide easy access to your org’s workflow templates
Once added, this UI will show up when creating a new workflow in the GitHub UI

It saves time needed to hunt down your actions repo to find the latest templates
Be sure you did the Docker Build Reusable Workflow homework first
Workflow starter templates must go in <org>/.github/workflow-templates/
.github in your orgwordsmith-web into this new repo at ./workflow-templates/call-docker-build.ymlcall-docker-build.properties.json to the same folder with this content:{
"name": "Call Docker Build",
"description": "Call the reusable workflow in the actions repo",
"iconName": "octicon smiley",
"categories": [
"Dockerfile"
],
"filePatterns": [
"^Dockerfile"
]
}We can assume the actual workflow will run correctly for this example
Let’s add it to wordsmith-api through the GitHub Actions UI
wordsmith-api repo, and click the "Actions" tabnew workflow button and check for your custom Starter workflowImagine all these being centrally controlled and automated for all software repos in your org
web and api?uses: path for the reusable workflowsecrets: and with: were empty arrays and invalid YAML. Comment them outgithub.com/orgs/<org-name>/packages shows two images stable-20230714-b3cc954latest and stable-<date>-<sha> images to deploy)–
kubectl apply or helm installWeaveworks coined the term GitOps in 2017, now documented in the OpenGitOps project

kubectl delete deploy/wordsmith-api in your cluster, GitOps should restore it)Just like no tool is DevOps

kubectl applyA full Argo CD install has 7 pods (non HA)


ApplicationSet now enables Ops cluster control while Devs add/remove apps at willargocd namespacebeta
kind: Application to their namespaceApplication resource YAML for each app to deployApplicationSet tells Argo CD where to find app YAML elsewhere–
repoURL and path of ApplicationSetMore questions on last week’s content
Q1: Some of you are already using Argo CD. What does your setup and infra look like for Argo CD today? cluster or multi-cluster? self-service or PR-gates? Do you give dev’s access to the Web UI? Do you lock them to read-only?
Q2: For everyone: What design decisions do you think you’ll implement? Will you implement self-service or are PR-gates enough?
argocd CLI tool makes it easier, but it’s not declarativekubectl apply is powerful! (no backups needed)kubectl apply plus argocd CLI argocd-autopilot projectMy favorite links for understanding GitOps
Viktor Farcic wrote a good post, and then another on "GitOps tools" not following their own principles
The goal is to add one
Docs on External Secrets Operator
AKA "Ways Argo CD can convert something into Kubernetes manifest YAML"

kind: Application resourceapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: wordsmith-staging01
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: 'https://github.com/<org>/wordsmith-k8s.git'
path: environments/staging01
targetRevision: <branch>
destination:
server: 'https://kubernetes.default.svc'
namespace: wordsmith-staging01
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=truewordsmith-staging01.yaml on your local machine (where kubectl is)<org> and <branch>–
kubectlkubectl apply -f wordsmith-staging01.yamlWe could also create it without YAML via argocd app create... or Web UI
web Deployment–
bretfisher/wordsmith-web, that’s not rightenvironments/staging01/kustomization.yaml–
Oh boy, we need to fix that override YAML to use our new stable images from last week
staging01 envstable-* tag for each image wordsmith-web and wordsmith-apikustomization.yaml file and let’s change each image name and tagimages:
- name: bretfisher/wordsmith-api
newName: ghcr.io/<org>/wordsmith-api
newTag: stable-<your-unique-tag>
- name: bretfisher/wordsmith-web
newName: ghcr.io/<org>/wordsmith-web
newTag: stable-<your-unique-tag>When you commit this file to your default branch, Argo CD will auto-update the app
By the way, this images: array is a Kustomize "Build-ins" feature called ImageTagTransformer

kind: Application YAML, we have to kubectl apply againApplication definition because we applied it out-of-band (kubectl)Application YAMLApplication for a 2nd environment, we’d need to kubectl apply itApplicationSet resourceWhich Argo CD setup design do you think you’ll use?
ApplicationSet create our ApplicationApplicationSet has many advantages over Application
ApplicationSet resource’s job is to create many Application resourcesApplication YAML for each appApplicationSetwordsmith-k8s repo: applicationsets/wordsmith-k8s.yamlApplicationSet YAML will have two main parts, generators and the templatekind: Application for each app it findsApplication per subdirkubectl apply -f it to our cluster in a minuteapiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: wordsmith
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/MostlyDevOps/wordsmith-k8s.git
revision: main
directories:
- path: environments/*
template:
metadata:
name: 'wordsmith-{{path.basename}}'
labels:
ApplicationSet: wordsmith-k8s.yaml
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/MostlyDevOps/wordsmith-k8s.git
targetRevision: main
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: 'wordsmith-{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueApplicationSet?An idea for allowing Argo CD to monitor ApplicationSet changes
ApplicationSet resource out-of-band with kubectlApplication resource that watches a dirApplicationSet YAML in that dir, it’ll watch them allapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
finalizers:
- resources-finalizer.argocd.argoproj.io
name: applicationsets
namespace: argocd
spec:
destination:
namespace: argocd
server: https://kubernetes.default.svc
project: default
source:
path: applicationsets
repoURL: https://github.com/mostlydevops/wordsmith-k8s.git
syncPolicy:
automated:
allowEmpty: true
prune: true
selfHeal: true
syncOptions:
- allowEmpty=trueThe more you learn about it, the more you’ll realize how flexible it is
AppProject, and point ApplicationSet to Dev’s repos, then devs can self-service their own apps w/o any Argo CD YAMLWIP: Work in Progress
https://github.com/BretFisher/gitops-argocd
design-01 is a day-1 super-simple file structuredesign-02 is more complex, yet offers way more flexibilitydesign-03 would be the most advanced and automated (WIP)https://github.com/MostlyDevOps/wordsmith-k8s
I like this for dev or staging environments
1.2.3)dev)stable-YYYY-MM-DD-mm-ss).argocd-source.yamlkustomize edit set imageQuestions on Monday’s content
What Argo CD ApplicationSet Generators do you think you’ll use (or use now)?
BretFisher/github-actions-templates/.github/workflows/reusable-gitops-pr.yaml
Application resources. Read about thatargocd namespace: kubectl get applicationset -Akubectl get application -Akubectl describe -n argocd appset/wordsmithkubectl api-resources | grep argokubectl get -n argocd podsargocd-server pod for server configargocd-application-controller for Application resourcesargocd-applicationset-controller for ApplicationSet resourcesrepo-server for git repo access and pollingargocd-dex-server for SSO authargocd-notifications-controller for notification config and triggersargocd-redis for caching repo and resource datakubectl apply --dry-run=server -f file.yaml on K8s with Argo CD –
All the optional homework from the course