├── Dockerfile ├── README.md └── k8s-backup.sh /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | ENV TZ Europe/Berlin 3 | 4 | WORKDIR /srv 5 | 6 | RUN apk update && apk add --no-cache bash \ 7 | tzdata less python3 curl mlocate groff openssl 8 | 9 | RUN curl -O https://bootstrap.pypa.io/get-pip.py \ 10 | && python3 get-pip.py \ 11 | && rm -f get-pip.py 12 | 13 | RUN cp /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 14 | RUN pip install awscli --upgrade 15 | RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.17.17/bin/linux/amd64/kubectl \ 16 | && chmod +x kubectl && mv kubectl /usr/local/bin/kubectl \ 17 | && mkdir -p /root/.kube 18 | VOLUME ["/root/.kube"] 19 | 20 | COPY k8s-backup.sh /usr/local/bin/k8s-backup.sh 21 | RUN chmod +x /usr/local/bin/k8s-backup.sh 22 | 23 | ENTRYPOINT ["k8s-backup.sh"] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # k8s-backup 2 | Kubernetes backup solution by exporting all k8s-components into YAML files and Uploading them to S3 Bucket archived and encrypted with a password. 3 | 4 | Optionally an url for a healthcheck can get defined ($HEALTHCHECK_URL). 5 | 6 | - The following K8s components/objects are extracted: 7 | - Secrets 8 | - Config Maps 9 | - Deployments 10 | - Services 11 | - Ingress 12 | - Persistent Volumes 13 | - Cronjobs 14 | 15 | ## Usage 16 | ``` 17 | docker run -it \ 18 | -e S3_BUCKET=my-s3-bucket-name \ 19 | -e AWS_ACCESS_KEY_ID=my-aws-access-key \ 20 | -e AWS_SECRET_ACCESS_KEY=my-aws-secret-key \ 21 | -e CLUSTER_NAME=my-cluster-name \ 22 | -e KUBE_ARCHIVE_PW=my-secret-password \ 23 | -e HEALTHCHECK_URL=https://hc-ping.com/your-uuid-here 24 | -v path-to-kube-config-dir:/root/.kube \ 25 | k8s-backup:latest 26 | ``` 27 | 28 | To decrypt the backup/archive run `openssl enc -aes-256-cbc -d -in name.tar.gz.enc | tar xz`. 29 | 30 | ## Development 31 | 32 | This project is hosted at https://github.com/ambient-innovation/k8s-backup 33 | 34 | The docker image is hosted on dockerhub at https://hub.docker.com/r/ambientinnovation/k8s-backup. 35 | 36 | To make changes, proceed as follows: 37 | 38 | 1. Make your changes to the code, just push to the repo. It is configured as automated build. The branch 39 | "master" will receive the tag "latest" and each Git Tag will create a corresponding docker tag. 40 | -------------------------------------------------------------------------------- /k8s-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ########### 3 | 4 | # Global Configurations 5 | #====================== 6 | set -e 7 | BACKUP_DIR=/usr/local/backup 8 | AWS_CMD=/usr/bin/aws 9 | TIME_STAMP=$(date +%Y-%m-%d_%H-%M) 10 | ###################### 11 | function get_secret { 12 | kubectl get secret -n ${1} -o=yaml --field-selector type!=kubernetes.io/service-account-token | sed -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' 13 | } 14 | 15 | function get_configmap { 16 | kubectl get configmap -n ${1} -o=yaml | sed -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' 17 | } 18 | 19 | function get_ingress { 20 | kubectl get ing -n ${1} -o=yaml | sed -e '/status:/,+2d' -e '/\- ip: \([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}/d' -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' 21 | } 22 | 23 | function get_service { 24 | kubectl get service -n ${1} -o=yaml | sed -e '/ownerReferences:/,+5d' -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' -e '/clusterIP: \([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}/d' 25 | } 26 | 27 | function get_deployment { 28 | kubectl get deployment -n ${1} -o=yaml | sed -e '/deployment\.kubernetes\.io\/revision: "[0-9]\+"/d' -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' -e '/status:/,+18d' 29 | } 30 | 31 | function get_cronjob { 32 | kubectl get cronjob -n ${1} -o=yaml | sed -e '/status:/,+1d' -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' 33 | } 34 | 35 | function get_pvc { 36 | kubectl get pvc -n ${1} -o=yaml | sed -e '/control\-plane\.alpha\.kubernetes\.io\/leader\:/d' -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' 37 | } 38 | 39 | function get_pv { 40 | for pvolume in `kubectl get pvc -n ${1} -o=custom-columns=:.spec.volumeName` 41 | do 42 | kubectl get pv -o=yaml --field-selector metadata.name=${pvolume} | sed -e '/resourceVersion: "[0-9]\+"/d' -e '/uid: [a-z0-9-]\+/d' -e '/selfLink: [a-z0-9A-Z/]\+/d' 43 | done 44 | } 45 | 46 | function export_ns { 47 | mkdir -p ${BACKUP_DIR}/${CLUSTER_NAME}/ 48 | cd ${BACKUP_DIR}/${CLUSTER_NAME}/ 49 | for namespace in `kubectl get namespaces --no-headers=true | awk '{ print $1 }' | grep -v -e "cattle-prometheus" -e "cattle-system" -e "kube-system" -e "kube-public"` 50 | do 51 | echo "Namespace: $namespace" 52 | echo "+++++++++++++++++++++++++" 53 | mkdir -p $namespace 54 | 55 | for object_kind in configmap ingress service secret deployment cronjob pvc 56 | do 57 | if kubectl get ${object_kind} -n ${namespace} 2>&1 | grep "No resources" > /dev/null; then 58 | echo "No resources found for ${object_kind} in ${namespace}" 59 | else 60 | get_${object_kind} ${namespace} > ${namespace}/${object_kind}.${namespace}.yaml && echo "${object_kind}.${namespace}"; 61 | 62 | if [ ${object_kind} = "pvc" ]; then 63 | get_pv ${namespace} > ${namespace}/pv.${namespace}.yaml && echo "pv.${namespace}"; 64 | fi 65 | fi 66 | done 67 | echo "+++++++++++++++++++++++++" 68 | done 69 | } 70 | 71 | ########################################################### 72 | ## Archiving k8s data with password to upload it to AWS S3. 73 | ## This password is available on our password manager. 74 | ############################################################ 75 | function archive_ns { 76 | cd ${BACKUP_DIR} 77 | tar cz ${CLUSTER_NAME} | openssl enc -aes-256-cbc -e -k ${KUBE_ARCHIVE_PW} > ${BACKUP_DIR}/${CLUSTER_NAME}-${TIME_STAMP}.tar.gz.enc 78 | } 79 | 80 | # Upload Backups 81 | #=============== 82 | function upload_backup_to_s3 { 83 | ${AWS_CMD} s3 cp ${BACKUP_DIR}/${CLUSTER_NAME}-${TIME_STAMP}.tar.gz.enc s3://${S3_BUCKET}/${CLUSTER_NAME}/ 84 | if [ $? -eq 0 ]; then 85 | echo "${CLUSTER_NAME}-${TIME_STAMP}.tar.gz.enc is successfully uploaded" 86 | rm -rf ${BACKUP_DIR}/${CLUSTER_NAME} ${BACKUP_DIR}/k8s-data-${TIME_STAMP}.tar.gz.enc 87 | else 88 | echo "${CLUSTER_NAME}-${TIME_STAMP}.tar.gz.enc failed to be uploaded" 89 | exit 1 90 | fi 91 | } 92 | 93 | # Execute Healthcheck Ping 94 | function healthcheck_ping { 95 | if [ -n "${HEALTHCHECK_URL}" ]; then 96 | curl -m 10 --retry 5 "${HEALTHCHECK_URL}"; 97 | fi 98 | } 99 | 100 | ########### 101 | export_ns 102 | archive_ns 103 | upload_backup_to_s3 104 | healthcheck_ping 105 | --------------------------------------------------------------------------------