Integrating Backup into your CI/CD Pipelines using Kanister
Deploy local Minikube cluster
minikube start --addons volumesnapshots,csi-hostpath-driver --apiserver-port=6443 --container-runtime=containerd -p mc-demo --kubernetes-version=1.21.2
How to Install Kanister
add kanister helm repository
helm repo add kanister https://charts.kanister.io/
create kanister namespace
kubectl create namespace kanister
deploy kanister using helm
helm install myrelease --namespace kanister kanister/kanister-operator --set image.tag=0.71.0
Once we have kanister deployed we should now show the CustomResourceDefinitions, this will show actionsets, blueprints, profiles.
kubectl get customresourcedefinitions.apiextensions.k8s.io | grep "kanister"
Deploy ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl port-forward svc/argocd-server -n argocd 8443:443
Username is admin and password can be obtained with this command. open a web browser localhost
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
Create a bucket in minikube with minio
Enable load balancer in minikube
# run this command in another terminal
minikube tunnel --profile=mc-demo
Install minio
helm repo add minio https://charts.min.io/
kubectl create ns minio
helm install kasten-minio minio/minio --namespace=minio --version 8.0.10 \
--set persistence.size=5Gi
# feed ACCESS_KEY variable and SECRET_KEY and display them
AWS_ACCESS_KEY_ID=$(kubectl -n minio get secret kasten-minio -o jsonpath="{.data.accesskey}" | base64 --decode)
echo $AWS_ACCESS_KEY_ID
AWS_SECRET_KEY=$(kubectl -n minio get secret kasten-minio -o jsonpath="{.data.secretkey}" | base64 --decode)
echo $AWS_SECRET_KEY
# Expose minio in a load balancer
kubectl expose svc/kasten-minio --name=minio-lb -n minio --type=LoadBalancer
EXTERNAL_IP=$(kubectl get svc -n minio minio-lb -o jsonpath='{.spec.clusterIP}')
echo "http://$EXTERNAL_IP:9000/"
open a web browser to this addres and use $ACCESS_KEY
and $SECRET_KEY
to access th minio GUI.
Create a kanister
bucket.
Create a Kanister Profile
This will give us some where to store our backups, this will be done using the KanCTL CLI tool
kanctl create profile \
--namespace kanister \
--bucket kanister \
--skip-SSL-verification \
--endpoint http://$EXTERNAL_IP:9000/ \
s3compliant \
--access-key $AWS_ACCESS_KEY_ID \
--secret-key $AWS_SECRET_KEY
confirm you now have a profile created
kubectl get profile -n kanister
Creating a Kanister Blueprint
Kanister uses Blueprints to define these database-specific workflows and open-source Blueprints are available for several popular applications. It's also simple to customize existing Blueprints or add new ones.
kubectl create -f https://raw.githubusercontent.com/kanisterio/kanister/master/examples/stable/mysql/mysql-blueprint.yaml -n kanister
this blueprint is created in the Kanister namespace check with environment
kubectl get blueprint -n kanister
Use argo to deploy your app
Create the argo project
Create a mysql namespace.
kubectl create ns mysql
Create in argo a new project mysql-app :
- project name : mysql
- use the default project
- git repo : your git repo (eg: https://github.com/michaelcourcy/argocd-kanister.git)
- path : base
- namespace: mysql
- choose the default or availables values for the rest
Once deployed, check the service account kanister-presync can create an action or read a profile set in the kanister namespace.
kubectl auth can-i create actionset --as=system:serviceaccount:mysql:kanister-presync -n kanister
kubectl auth can-i create profile --as=system:serviceaccount:mysql:kanister-presync -n kanister
The answer should be yes for both.
Create some data
kubectl exec -ti mysql-0 -n mysql -- bash
mysql --user=root --password=ultrasecurepassword
CREATE DATABASE test;
USE test;
CREATE TABLE pets (name VARCHAR(20), owner VARCHAR(20), species VARCHAR(20), sex CHAR(1), birth DATE, death DATE);
INSERT INTO pets VALUES ('Puffball','Diane','hamster','f','1999-03-30',NULL);
SELECT * FROM pets;
+----------+-------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+----------+-------+---------+------+------------+-------+
| Puffball | Diane | hamster | f | 1999-03-30 | NULL |
+----------+-------+---------+------+------------+-------+
1 row in set (0.00 sec)
exit
exit
Sync your project
By resyncing your project you're going to trigger the creation of a backup. check on minio.
Introduce some "bad" change in your application stack.
Let's imagine you create a mysqlclient app which is going to drop your database in your code, that's a mistake. But mistake happen.
Create this pod base/mysql-client.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: mysql-client
name: mysql-client
spec:
containers:
- image: mysql:8.0.26
name: mysql-client
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
key: mysql-root-password
name: mysql
command:
- sh
- -o
- errexit
- -c
- |
mysql -h mysql --user=root --password=$MYSQL_ROOT_PASSWORD -e "DROP DATABASE test;"
dnsPolicy: ClusterFirst
restartPolicy: Never
status: {}
You commit, push and sync with argo and check your data
kubectl exec -ti mysql-0 -n mysql -- bash
mysql --user=root --password=ultrasecurepassword
SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
Horror !!! The sync has deleted the database but fortunaletly kanister protects your database in the execution of the presync.
Restore your database using kanctl
The sync above represents a simple change in code that could affect our data, at this stage the bad mysql-client.yaml
should be removed or configured correctly before continuing with the restore process.
Before we restore we need to choose the appropriate restore point.
kubectl get actionset -n kanister
NAME AGE
mysqlapp-backup-tue-jan-25-17-46-46-utc-2022 10m
mysqlapp-backup-tue-jan-25-17-50-45-utc-2022 6m56s
When we have the list we can choose the correct actionset to restore from.
kanctl --namespace kanister create actionset --action restore --from "mysqlapp-backup-tue-jan-25-17-50-45-utc-2022"
actionset restore-mysqlapp-backup-tue-jan-25-17-50-45-utc-2022-hsjvv created
Now we can see in detail what is happening as part of the restore process.
kubectl --namespace kanister describe actionset restore-mysqlapp-backup-tue-jan-25-17-50-45-utc-2022-hsjvv
We can then go and check on our data and database again and make sure that our test database is restored along with our data.
kubectl exec -ti mysql-0 -n mysql -- bash
mysql --user=root --password=ultrasecurepassword
CREATE DATABASE test;
USE test;
SELECT * FROM pets;
+----------+-------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+----------+-------+---------+------+------------+-------+
| Puffball | Diane | hamster | f | 1999-03-30 | NULL |
+----------+-------+---------+------+------------+-------+
1 row in set (0.00 sec)
SHOW DATABASES;
exit
exit