Multi-stage Application Delivery
In this guide you will learn how to deploy
an application across multiple stages while getting
full observability of each of your workload deployments.
This is shown based on an example where we deploy
an application into two environments (dev
and production
),
using Argo CD.
The promotion from dev
to production
is implemented using
promotionTasks
that can be executed after a successful
application deployment.
In this example, the promotion task creates a pull request
to deploy the same workload version that has been deployed in the
dev
stage into the production
stage.
Using the observability capabilities of Keptn,
we also add contextual metadata to each deployment,
such as the commit ID which triggered the deployment of a new
version.
This metadata is added to the OpenTelemetry traces
representing the deployment of an application version
in a particular stage.
Also, deployment traces for the production
stage will
contain references to the deployments of the dev
stage,
giving you full traceability of a workload version
across different stages.
Before you begin
This guide assumes the following:
- Installed Keptn with the
promotionTasksEnabled flag
set to
true
. - Installed Argo CD
- Followed the instructions for setting up observability
- Access to a GitHub repository for hosting your application manifests.
- A Personal Access token for your GitHub repository.
We recommend using a fine-grained token
with the following permissions for the repository:
- Read access to metadata
- Read/Write access to actions
Note that for the promotion to work properly, you need to enable read and write permissions for Github actions in your repository - see the screenshot below:
Preparing the Application Manifests
In this example, the application will be deployed in
two different namespaces, each representing a different
stage (dev
and production
).
To create the namespaces, execute the following commands:
kubectl create namespace podtato-head-dev
kubectl annotate namespace podtato-head-dev keptn.sh/lifecycle-toolkit=enabled
kubectl create namespace podtato-head-production
kubectl annotate namespace podtato-head-production keptn.sh/lifecycle-toolkit=enabled
The promotion task that triggers the action to
create a pull request for promoting an application version
from dev
to production
will be executed in the podtato-head-dev
stage.
Therefore, we need to create a secret containing the GitHub personal
access token in that namespace, using the following command:
GH_REPO_OWNER=<YOUR_GITHUB_USER>
GH_REPO=<YOUR_GITHUB_REPO>
GH_API_TOKEN=<YOUR_GITHUB_TOKEN>
kubectl create secret generic github-token -n podtato-head-dev --from-literal=SECURE_DATA="{\"githubRepo\":\"${GH_REPO}\",\"githubRepoOwner\":\"${GH_REPO_OWNER}\",\"apiToken\":\"${GH_API_TOKEN}\"}"
Next, add the promotion task as a GitHub action by committing the following file to the repository:
name: promote
on:
workflow_dispatch:
inputs:
traceParent:
description: 'OTEL parent trace'
required: false
type: string
permissions:
contents: write
pull-requests: write
jobs:
promote:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
# configure git client
git config --global user.email "<email address>"
git config --global user.name "<name>"
# create a new branch
git switch -c production/${{ github.sha }}
# promote the change
cp dev/values.yaml production/values.yaml
echo "traceParent: $TRACE_PARENT" >> production/values.yaml
# push the change to the new branch
git add production/values.yaml
git commit -m "Promote dev to production"
git push -u origin production/${{ github.sha }}
env:
TRACE_PARENT: ${{ inputs.traceParent }}
- run: |
gh pr create \
-B main \
-H production/${{ github.sha }} \
--title "Promote dev to production" \
--body "Automatically created by GHA"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Next, add the application manifests to the GitOps repository.
Note that in this example, the manifests for the dev
and production
stage are located in two different subdirectories within
the repository to keep it simple.
This might differ heavily depending on how your organization's
GitOps repositories and application stages are organized.
Inside your Git repository, create the following files
containing the manifests for the dev
and production
stage:
Dev Stage:
This file contains the manifests for the application in the dev
stage,
including the Kubernetes Deployment
/Service
definition,
as well as the KeptnAppContext
that is used to define tasks
for the post-deployment and promotion phases, as well as the related
KeptnTaskDefinitions
.
In addition to defining tasks, the KeptnAppContext
also contains
a metadata
section that includes properties that should be added
to the OpenTelemetry spans that are generated during the deployment of
the application.
apiVersion: apps/v1
kind: Deployment
metadata:
name: podtato-head-frontend
namespace: podtato-head-dev
spec:
selector:
matchLabels:
app: podtato-head-frontend
template:
metadata:
labels:
app: podtato-head-frontend
app.kubernetes.io/name: podtato-head-frontend
app.kubernetes.io/part-of: podtato-head
app.kubernetes.io/version: {{.Values.serviceVersion}}
spec:
containers:
- image: ghcr.io/podtato-head/podtato-server:{{.Values.serviceVersion}}
name: podtato-head-service
imagePullPolicy: Always
ports:
- containerPort: 9000
name: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/part-of: podtato-head
name: podtato-head-frontend
namespace: podtato-head-dev
spec:
ports:
- name: http
port: 8080
targetPort: 9000
protocol: TCP
selector:
app: podtato-head-frontend
type: ClusterIP
---
apiVersion: lifecycle.keptn.sh/v1
kind: KeptnAppContext
metadata:
name: podtato-head
namespace: podtato-head-dev
spec:
postDeploymentTasks:
- post-deployment
promotionTasks:
- promote
metadata:
commitID: {{.Values.commitID}}
---
apiVersion: lifecycle.keptn.sh/v1
kind: KeptnTaskDefinition
metadata:
name: post-deployment
namespace: podtato-head-dev
spec:
deno:
inline:
code: |
console.log("deployment completed");
---
apiVersion: lifecycle.keptn.sh/v1
kind: KeptnTaskDefinition
metadata:
name: promote
namespace: podtato-head-dev
spec:
deno:
secureParameters:
secret: github-token
inline:
code: |
let secureDataText = Deno.env.get("SECURE_DATA");
let secureData;
if (secureDataText != "") {
secureData = JSON.parse(secureDataText);
}
let contextText = Deno.env.get("KEPTN_CONTEXT");
let context;
if (contextText != "") {
context = JSON.parse(contextText);
}
let body = `{"ref":"main","inputs":{"traceParent":"${context.metadata.traceparent}"}}`;
let resp = await fetch(
"https://api.github.com/repos/" + secureData.githubRepoOwner + "/" + secureData.githubRepo + "/actions/workflows/promote.yaml/dispatches",
{
method: "POST",
body: body,
headers: {
'Accept': 'application/vnd.github+json',
'Authorization': `Bearer ${secureData.apiToken}`,
'X-GitHub-Api-Version': '2022-11-28'
},
});
console.log(resp);
The Chart.yaml
that is required by helm:
The values.yaml
file contains configurable properties
for the application, such as the version of the service that should be deployed,
and other metadata that is passed to the KeptnAppContext
, such as the Git
commit ID that caused the deployment of a new version (this value for the
commitID
property is set automatically using the $ARGOCD_APP_REVISION
environment variable provided by Argo CD).
During the promotion phase of a new deployment, a pull request to
copy the content of this file to the helm chart containing the helm chart for
the production
stage will be created.
Therefore, after merging the pull request, the version that has been deployed into
dev
and has passed all checks there, will then be deployed into production
.
- The commitID will be automatically set by Argo CD
Production Stage:
This file contains the manifests for the application in the dev
stage,
including the Kubernetes Deployment
/Service
definition,
as well as the KeptnAppContext
that contains
a metadata
section which includes properties that should be added
to the OpenTelemetry spans that are generated during the deployment of
the application.
apiVersion: apps/v1
kind: Deployment
metadata:
name: podtato-head-frontend
namespace: podtato-head-production
spec:
selector:
matchLabels:
app: podtato-head-frontend
template:
metadata:
labels:
app: podtato-head-frontend
app.kubernetes.io/name: podtato-head-frontend
app.kubernetes.io/part-of: podtato-head
app.kubernetes.io/version: {{.Values.serviceVersion}}
spec:
containers:
- image: ghcr.io/podtato-head/podtato-server:{{.Values.serviceVersion}}
name: podtato-head-service
imagePullPolicy: Always
ports:
- containerPort: 9000
name: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/part-of: podtato-head
name: podtato-head-frontend
namespace: podtato-head-production
spec:
ports:
- name: http
port: 8080
targetPort: 9000
protocol: TCP
selector:
app: podtato-head-frontend
type: ClusterIP
---
apiVersion: lifecycle.keptn.sh/v1
kind: KeptnAppContext
metadata:
name: podtato-head
namespace: podtato-head-production
spec:
metadata:
commitID: {{.Values.commitID}}
spanLinks:
- {{.Values.traceParent}} # (1)!
- Setting the spanLink here connects deployment traces of this stage with the traces of the dev stage
The Chart.yaml
that is required by helm:
The values.yaml
file contains configurable properties
for the application, such as the version of the service that should be deployed,
and other metadata that is passed to the KeptnAppContext
, such as the Git
commit ID that caused the deployment of a new version (this value for the
commitID
property is set automatically using the $ARGOCD_APP_REVISION
environment variable provided by Argo CD).
The content of this file is modified by the pull requests created in the
promotion phase of the deployment in dev
in order to promote a new
service version into production
.
- The commitID will be automatically set by Argo CD
- The traceParent will be automatically set by the GitHub action triggered by the promotion task
Committing and Applying Argo CD Manifests
After creating all the files listed above, do not forget to push them to your git repository:
After adding the files to the repository,
it is time to create the Argo CD applications,
one for the dev
stage, and one for the production
stage.
These applications will point to the subdirectories
containing the helm charts for the stages within the upstream
Git repository.
To create the applications on Argo CD, apply the following manifests:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: podtato-head-dev
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/<repo-owner>/<repo>'
path: dev
targetRevision: main
helm:
parameters:
- name: "commitID" # (1)!
value: "$ARGOCD_APP_REVISION"
destination:
server: 'https://kubernetes.default.svc'
namespace: podtato-head-dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- This ensures that the
commitID
property is set in thevalues.yaml
file is set when the helm chart is being applied.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: podtato-head-prod
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/<repo-owner>/<repo>'
path: production
targetRevision: main
helm:
parameters:
- name: "commitID" # (1)!
value: "$ARGOCD_APP_REVISION"
destination:
server: 'https://kubernetes.default.svc'
namespace: podtato-head-production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- This ensures that the
commitID
property is set in thevalues.yaml
file is set when the helm chart is being applied.
Once the manifest above has been applied to the cluster, Argo CD will eventually synchronize with the upstream repository and apply the application helm charts located there.
Note: Initially, both the
dev
andproduction
stages will be deployed after creating the Argo CD applications. Subsequent deployments of a new application version however will always be deployed by first updating them in thedev
stage, and then only by merging the PRs created by the promotion task will they be deployed intoproduction
.
After this initial deployment, there will be a KeptnAppVersion
in the dev
stage:
$ kubectl get keptnappversion -n podtato-head-dev
NAME APPNAME VERSION PHASE
podtato-head-v0.3.0-6b86b273 podtato-head v0.3.0 Completed
And there will be another KeptnAppVersion
in the production
stage.
$ kubectl get keptnappversion -n podtato-head-production
NAME APPNAME VERSION PHASE
podtato-head-v0.3.0-6b86b273 podtato-head v0.3.0 Completed
Promoting a workload
Now that everything is set up, it is time to deploy a new version into dev
and automatically promote it into production
if the deployment into dev
has been successful.
To do so, change the serviceVersion
value in dev/values.yaml
from v0.3.0
to v0.3.1
:
- The traceParent will be automatically set by the GitHub action triggered by the promotion task
After changing the value, commit and push the updated file to the upstream repository:
Eventually, Argo CD will notice those changes and apply the updated helm chart
to the cluster.
This is also reflected in a new KeptnAppVersion
being created in the dev
stage:
$ kubectl get keptnappversion -n podtato-head-dev
NAME APPNAME VERSION PHASE
podtato-head-v0.3.0-6b86b273 podtato-head v0.3.0 Completed
podtato-head-v0.3.1-d4735e3a podtato-head v0.3.1 Completed
After the new version has been deployed, and the post deployment phase is completed, the following happens:
-
The
KeptnTask
executed in the promotion phase triggers a GitHub action that creates a pull request for applying the updated values to the helm chart of theproduction
stage. You will see the PR in the Pull requests section of your upstream repository: -
After approving and merging the pull request into
main
, Argo CD eventually synchronizes the application in theproduction
stage, and the updated version is deployed. This is reflected in a newKeptnAppVersion
being created in thepodtato-head-production
namespace:
Inspecting the Deployment Traces
To keep track of how a workload version progresses through
the stages, we passed through the OpenTelemetry span id of the
promotion phase in the dev
stage to the applied
KeptnAppContext
in the production
stage.
Due to that, the deployment trace of the promoted version
in the production
stage contains the reference to the
promotion phase, which is also visible in the trace visualization in Jaeger:
Conclusion
In this guide, you have seen how Keptn can be used together with Argo CD and GitHub Actions to automate the promotion of a new application version from one stage to another. In addition to that, by linking together the traces across different stages and adding important metadata such as the Git commit IDs that caused a new deployment, Keptn makes it easy to inspect how a specific version is promoted across multiple stages.