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-web
New workflow
, then click setup a workflow yourself
.github/workflows/docker-build.yml
tags
to our repo (last line) ghcr.io/<orgname>/wordsmith-web:latest
<orgname>
must be lowercase to work with Docker!:latest
org.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=f217725e1bf9e1032debb15df43b06513907d58b
docker-build.yml
and replace all text with our advanced exampleghcr.io/<orgname>/wordsmith-web
Tagging 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 permissions
none
(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|none
permissions: {}
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: true
You 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 PRs
Then, 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: true
The 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 thereThis 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-web
actions
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 secrets
jobs:
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: string
trivy
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-*.yml
call-*.yml
workflow_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-web
In your actions
repo
actions
repo, create a new file .github/workflows/reusable-docker-build.yml
In 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-api
Once 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.yml
call-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-b3cc954
latest
and stable-<date>-<sha>
images to deploy)–
kubectl apply
or helm install
Weaveworks 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 apply
A 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 ApplicationSet
More 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=true
wordsmith-staging01.yaml
on your local machine (where kubectl is)<org>
and <branch>
–
kubectl
kubectl apply -f wordsmith-staging01.yaml
We 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-api
kustomization.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 Application
ApplicationSet
has many advantages over Application
ApplicationSet
resource’s job is to create many Application
resourcesApplication
YAML for each appApplicationSet
wordsmith-k8s
repo: applicationsets/wordsmith-k8s.yaml
ApplicationSet
YAML will have two main parts, generators
and the template
kind: 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=true
ApplicationSet
?An idea for allowing Argo CD to monitor ApplicationSet
changes
ApplicationSet
resource out-of-band with kubectl
Application
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=true
The 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.yaml
kustomize edit set image
Questions 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 -A
kubectl get application -A
kubectl describe -n argocd appset/wordsmith
kubectl api-resources | grep argo
kubectl get -n argocd pods
argocd-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