My personal Go and K8s playground.
REST api written in Go and K8s deployment: MetalLB loadbalancer, Prometheus, Haproxy, Postgres, Helm 3, Lens, Krew, RBAC, Auditing, Falco, SOPS, Flux v2 and plenty of admin tricks.
This K8s cluster is crio version from my other repo. Platform is Ubuntu 20.04 + KVM
kubectl apply -f gorestapi-deployment.yaml
kubectl apply -f gorestapi-svc.yaml
K8s cmd line testing:
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gorestapi ClusterIP 10.110.175.227 <none> 8080/TCP 4m55s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 58d
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.5/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.5/manifests/metallb.yaml
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
$ cat config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: restapipool
protocol: layer2
addresses:
- 10.0.1.245-10.0.1.250
Above config will give addr pool 245 - 250 to MetalLB
kubectl apply -f config.yaml
Test IP routing: Change gorestapi-svc.yaml type: ClusterIP => type: LoadBalancer
Check if app gets routable external ip (type: LoadBalancer):
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gorestapi LoadBalancer 10.110.175.227 10.0.1.245 8080:32370/TCP 9m19s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 58d
More info: https://github.com/jcmoraisjr/haproxy-ingress/tree/master/examples/deployment
Label nodes 1-3 to mark them for ingress-controller (I have 4 worker nodes):
kubectl label node worker1 role=ingress-controller
node/worker1 labeled
kubectl label node worker2 role=ingress-controller
node/worker2 labeled
kubectl label node worker3 role=ingress-controller
node/worker3 labeled
Check labels: kubectl get nodes --selector='role=ingress-controller'
NAME STATUS ROLES AGE VERSION
worker1 Ready <none> 57d v1.20.0
worker2 Ready <none> 57d v1.20.0
worker3 Ready <none> 57d v1.20.0
kubectl create ns ingress-controller
openssl req \
-x509 -newkey rsa:2048 -nodes -days 365 \
-keyout tls.key -out tls.crt -subj '/CN=localhost'
kubectl --namespace ingress-controller create secret tls tls-secret --cert=tls.crt --key=tls.key
rm -v tls.crt tls.key
kubectl apply -f haproxy-ingress-deployment.yaml
Check ingress-controller daemonset:
kubectl get daemonsets -n ingress-controller
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
haproxy-ingress 3 3 3 3 3 role=ingress-controller 70s
kubectl apply -f haproxy-svc.yaml
kubectl apply -f gorestapi-ingress.yaml
Now we have MetalLB to give static IP 10.0.1.248 to Haproxy which is Watching for ingress class 'haproxy' to route traffic to restapi Pod port 8080.
Note! This setup is only for my playground, so I'm not using persistent volumes here.
kubectl apply -f postgres-configmap.yaml
kubectl apply -f postgres-deployment-svc.yaml
Note! We have type: NodePort to access our db from host.
Check Postgres port:
kubectl get svc
postgres NodePort 10.100.109.205 none 5432:32531/TCP 28m
Test db connection: https:
psql -h 10.0.1.204 -U admin --password -p 32531 omadb
Password:
omadb=# quit
Problem: if gorestapi start before db is ready, it will fail, so we have to wait and check until Postgress is online.
Get wait-for-it script:
git clone https://github.com/bells17/wait-for-it-for-busybox
Create configmap:
kubectl create configmap wait-for-it --dry-run=client -o yaml --from-file=wait-for-it.sh > wait-for-it-configmap.yaml
Deploy configmap (I always like to checkit up before deployment):
kubectl apply -f wait-for-it-configmap.yaml
configmap/wait-for-it created
Testing above wait-for-it script with standalone alpine, I just didn't get it work in Busybox:
kubectl logs alpine
wait-for-it.sh: waiting for postgres:5432 without a timeout
wait-for-it.sh: postgres:5432 is available after 0 seconds
Same logs from initContainer:
kubectl logs gorestapi-566b5db78b-z87qt -c wait-for-postgres
wait-for-it.sh: waiting for postgres:5432 without a timeout
wait-for-it.sh: postgres:5432 is available after 0 seconds
In Dockerfile:
RUN go get github.com/golang-migrate/migrate/v4/database/postgres
RUN go get github.com/golang-migrate/migrate/v4/source/file
Above is needed for not having missing import error
In gorestapi-ingress.yaml:
ingress.kubernetes.io/ssl-redirect: "false"
Above is needed because default haproxy config for ssl redirect is 'true' and this demo app does not have ssl.
haproxy-ingress-deployment.yaml RBAC fix:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
- ingressclasses
- ingresses/status
Rsyslog sidecar for http access logs in haproxy-ingress-deployment.yaml:
- name: access-logs
image: jumanjiman/rsyslog
ports:
- name: udp
containerPort: 514
protocol: UDP
http-log-format: "%ci:%cp\ method=%HM\ uri=%HU\ rcvms=%TR\ serverms=%Tr\ activems=%Ta\ bytes=%B\ status=%ST"
%ci = client_ip
%cp = client_port
%HM = http method (GET / POST)
%HU = request URI path
%TR = request timeout
%Tr = response time
%Ta = active time of the request
%B = bytes_read
%ST = status_code
In this non Helm Haproxy demo I had two different sidecar alternatives for collecting haproxy logs: rsyslog and netcat. Here's rsyslog example:
kubectl logs -f haproxy-ingress-bnn7s -n ingress-controller -c access-logs
2021-02-24T13:18:33.493980+00:00 localhost 10.0.1.1: 44810 method=GET uri=/data rcvms=0 serverms=2 activems=2 bytes=87 status=200
2021-02-24T13:18:34.516629+00:00 localhost 10.0.1.1: 44812 method=GET uri=/data rcvms=0 serverms=3 activems=4 bytes=87 status=200
Note! Here is first tested Prometheus helm version prometheus-community/prometheus, I changed version couple steps later because it was missing Servicemonitor.
I don't have storage in my demo lab, so I need to disable peristentVolumeClaims:
helm install prometheus prometheus-community/prometheus -n monitoring --set alertmanager.persistentVolume.enabled=false --set server.persistentVolume.enabled=false --set pushgateway.persistentVolume.enabled=false
Check it up: kubectl get pods -n monitoring
NAME READY STATUS RESTARTS AGE
prometheus-alertmanager-6866b96d6f-fk2qt 2/2 Running 0 89s
prometheus-kube-state-metrics-95d956569-f7gx6 1/1 Running 0 89s
prometheus-node-exporter-2vscs 1/1 Running 0 89s
prometheus-node-exporter-b27hw 1/1 Running 0 89s
prometheus-node-exporter-hcgzd 1/1 Running 0 89s
prometheus-node-exporter-kn4ps 1/1 Running 0 89s
prometheus-pushgateway-bd8d484d6-d5wcr 1/1 Running 0 89s
prometheus-server-7f67fc9bdb-2mhqx 2/2 Running 0 89s
Default K8s installation need some fixing for Prometheus:
$ kubectl get pod kube-controller-manager-master1 -n kube-system -o yaml | grep 'bind-address'
- --bind-address=127.0.0.1
$ kubectl describe pod etcd-master1 -n kube-system | grep url
Annotations: kubeadm.kubernetes.io/etcd.advertise-client-urls: https://10.0.1.131:2379
--advertise-client-urls=https://10.0.1.131:2379
--initial-advertise-peer-urls=https://10.0.1.131:2380
--listen-client-urls=https://127.0.0.1:2379,https://10.0.1.131:2379
--listen-metrics-urls=http://127.0.0.1:2381
--listen-peer-urls=https://10.0.1.131:2380
etcd configs can be found from here: /etc/kubernetes/manifests/etcd.yaml
Open connection to Prometheus server:
kubectl --address localhost,10.0.1.131 -n monitoring port-forward prometheus-server-7f67fc9bdb-2mhqx 8090:9090
Test from outside of cluster (that query will list all K8s resources):
curl --data-urlencode 'query=up{}' http://10.0.1.131:8090/api/v1/query | jq
Above prometheus-community/prometheus version has problem with kind ServiceMonitor, so I'll try next version prometheus-community/kube-prometheus-stack:
helm install prometheus-stack prometheus-community/kube-prometheus-stack --namespace monitoring --set prometheusOperator.hostNetwork=true --set defaultRules.rules.kubernetesStorage=false --set prometheusOperator.tls.internalPort=10251
Open Prometheus connection:
kubectl --address localhost,10.0.1.131 -n monitoring port-forward svc/prometheus-stack-kube-prom-prometheus 32090:9090
Test again from outside:
curl --data-urlencode 'query=up{}' http://10.0.1.131:32090/api/v1/query | jq
We have some Prometheus access problems to be fixed:
- Prometheus is trying to get kube controller manager metrics from deprecated port 10252, new port is 10259
- Same as above with kube scheduler, prome is trying deprecated port 10251, new port is 10257
- Prometheus is trying to access master ip, when as seen above, bind-adderess is 127.0.0.1
- Prometheus is trying to access etcd from http://masterip:2379, but as we can see from above, etcd is offering https connection
- Prometheus is trying to access kube-proxy metrics from masterip:10249 when it's available in localhost:10249
I'll use haproxy to fix above problems 1-3. I made custom build jrcjoro1/haproxy-fix that will redirect prometheus scrape to right place.
For testing haproxy-fix redirect, I used curl-test.yaml:
kubectl apply -f haproxy-fix-deployment.yaml
kubectl apply -f curl-test.yaml
For problem 4. we need to set client cert auth. First let's check etcd connection from control-plane cmd line:
sudo curl --cert /etc/kubernetes/pki/etcd/peer.crt --key /etc/kubernetes/pki/etcd/peer.key --cacert /etc/kubernetes/pki/etcd/ca.crt https://10.0.1.131:2379/health
{"health":"true"}
Create Prometheus secret etcd-client with script:
./generate_prome_etcd_auth.sh
secret/etcd-client created
NAME TYPE DATA AGE
etcd-client Opaque 3 0s
Re-install prometheus-stack with etcd auth secret:
Uninstall:
helm uninstall prometheus-stack -n monitoring
release "prometheus-stack" uninstalled
Complete wipeout:
kubectl delete ns monitoring
Re-install etcd secrets:
./generate_prome_etcd_auth.sh
secret/etcd-client created
NAME TYPE DATA AGE
etcd-client Opaque 3 1s
Install Prometheus with custom values:
helm install -f custom-prometheus-values.yaml prometheus-stack prometheus-community/kube-prometheus-stack --namespace monitoring
Fixing problem 5. First check kube-proxy metrics api:
curl -i http://127.0.0.1:10249/metrics
Edit kube-proxy configmap:
kubectl edit cm kube-proxy -n kube-system <= change metricsBindAddress: "" => metricsBindAddress: 0.0.0.0
Reload kube-proxy:
kubectl delete pod -l k8s-app=kube-proxy -n kube-system
Re-test with control-plane ip:
curl -i http://10.0.1.131:10249/metrics
Now Prometheus can discover all targets :)
First remove non Helm installation:
kubectl delete -f haproxy-ingress-deployment.yaml
kubectl delete -f haproxy-svc.yaml
kubectl delete ns ingress-controller
Search for haproxy:
helm search repo haproxy-ingress
NAME CHART VERSION APP VERSION DESCRIPTION
haproxy-ingress/haproxy-ingress 0.12.0 v0.12 Ingress controller for HAProxy loadbalancer
Check default values:
helm pull haproxy-ingress/haproxy-ingress
Install:
helm install haproxy-ingress haproxy-ingress/haproxy-ingress --create-namespace --namespace ingress-controller --version 0.12.0 --set controller.hostNetwork=true --set controller.stats.enabled=true --set controller.metrics.enabled=true --set controller.serviceMonitor.enabled=true --set-string controller.metrics.service.annotations."prometheus\.io/port"="9101" --set-string controller.metrics.service.annotations."prometheus\.io/scrape"="true"
Test haproxy metrics api:
curl -i http://10.104.136.22:9101/metrics
Install Haproxy configmap:
kubectl apply -f haproxy-configmap.yaml
Check servicemonitor:
kubectl get servicemonitors -n ingress-controller
NAME AGE
haproxy-ingress 87s
Check haproxy annotations:
kubectl describe service haproxy-ingress-metrics -n ingress-controller | grep prometheus
prometheus.io/port: 9101
prometheus.io/scrape: true
Update prometheus with haproxy scape configs:
helm upgrade --reuse-values -f custom-prometheus-values2.yaml prometheus-stack prometheus-community/kube-prometheus-stack --namespace monitoring
Release "prometheus-stack" has been upgraded. Happy Helming!
Some notes about helm flags:
'reuse-values' will merge additional custom values to chart and keep previous settings in place.
'reset-values' would reset all values back to default values.yaml chart except those provided by custom chart.
Test if you can see haproxy-ingress and haproxy-exporter up:
curl --data-urlencode 'query=up{}' http://10.0.1.131:32090/api/v1/query | jq
Alternatively you can point browser to http://http://10.0.1.131:32090/targets and check if above targets are grean.
If ok, you can now use all haproxy_ related Prometheus functions to query metrics.
Lens is very nice graphical user interface for K8s cluster.
Download Lens: https://github.com/lensapp/lens/releases/tag/v4.1.4 Install: sudo apt install ./Lens-4.1.4.amd64.deb
Copy ~/.kube/config of the remote Kubernetes host to your local dir.
Add cluster to Lens by giving path to above config.
Krew is kubectl plugin to find and get other kubectl plugins.
Install info: https://krew.sigs.k8s.io/docs/user-guide/setup/install/
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.tar.gz"
tar zxvf krew.tar.gz
./krew-linux_amd64 install krew
ServiceAccount: Communication with kube api server if you do not specify a service account, it is automatically assigned the default service account:
kubectl get pod testcurl -o yaml | grep 'serviceAccount'
serviceAccount: default
serviceAccountName: default
Default namespace does not have anyt other ServiceAccounts:
kubectl get serviceaccounts -n default
NAME SECRETS AGE
default 1 86d
While in ingress-controller namespace we have:
kubectl get serviceaccounts -n ingress-controller
NAME SECRETS AGE
default 1 3d1h
haproxy-ingress 1 3d1h
Role: Set permissions within namespace:
apiGroups: kubectl api-resources -o wide
resources: target objects, eg. configmaps, pods, secrets, namespaces
verbs: what you can do with ohject, eg. get, create, update, watch, list, patch, delete, deletecollection
Cluster role: Set cluster wide permissions.
Role binding: Link ServiceAccount and Role together: roleRef (kind, name, apiGroup) + subjects (entity that will make operations, subjects can be groups, users or ServiceAccounts)
Cluster role binding: Grant cluster wide access: roleRef + subjects
Default roles in Kubernetes are: view, edit, admin, cluster-admin
For example:
kubectl get serviceAccounts -n ingress-controller
NAME SECRETS AGE
default 1 3d23h
haproxy-ingress 1 3d23h
We have haproxy-ingress serviceAccount in ingress-controller namespace
rbac-lookup: https://github.com/FairwindsOps/rbac-lookup
Let's install rbac-lookup: kubectl krew install rbac-lookup
haproxy-ingress has ClusterRole haproxy-ingress:
kubectl-rbac_lookup haproxy-ingress
SUBJECT SCOPE ROLE
haproxy-ingress ingress-controller Role/haproxy-ingress
haproxy-ingress ingress-controller Role/haproxy-ingress
haproxy-ingress cluster-wide ClusterRole/haproxy-ingress
haproxy-ingress cluster-wide ClusterRole/haproxy-ingress
Other userful commands:
kubectl-rbac_lookup --kind user
kubectl-rbac_lookup --kind group
haproxy-ingress ClusterRole is granted to:
kubectl describe clusterrole haproxy-ingress
Name: haproxy-ingress
Labels: app.kubernetes.io/instance=haproxy-ingress
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=haproxy-ingress
app.kubernetes.io/version=v0.12
helm.sh/chart=haproxy-ingress-0.12.0
Annotations: meta.helm.sh/release-name: haproxy-ingress
meta.helm.sh/release-namespace: ingress-controller
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
events [] [] [create patch]
services [] [] [get list watch]
ingressclasses.extensions [] [] [get list watch]
ingresses.extensions [] [] [get list watch]
ingressclasses.networking.k8s.io [] [] [get list watch]
ingresses.networking.k8s.io [] [] [get list watch]
nodes [] [] [list watch get]
configmaps [] [] [list watch]
endpoints [] [] [list watch]
pods [] [] [list watch]
secrets [] [] [list watch]
ingresses.extensions/status [] [] [update]
ingresses.networking.k8s.io/status [] [] [update]
So from above we can see that user haproxy-ingress can list and watch pods, you can verify same info with can-i:
kubectl auth can-i list pods --as haproxy-ingress
yes
Can user haproxy-ingress delete pods? kubectl auth can-i delete pods --as haproxy-ingress no
Rakkess: https://github.com/corneliusweig/rakkess
With rakkess we can inspect authorizations granted to user:
kubectl krew install access-matrix
To get full list of grants for serviceAccount haproxy-ingress: kubectl access-matrix -n ingress-controller --as haproxy-ingress
With kubectl-who-can we can find subjects that can perform a spesific action:
kubectl krew install who-can
For example subjects that can list pods in ingress-controller namespace: kubectl who-can list pods -n ingress-controller
In Kubernetes auditing first we need to define audit policy that will define rules of what will be recorded and what data will be included. We will use audit-policy.yaml for that, level that request is matching:
none: don't log events
Metadata: log request metadata
Request: log event metadata and request body
RequestResponse: log event metadata, request and response bodies
Structure of audit policy:
level: none, Metadata, Request, RequestResponse
users: e.g. system:serviceaccount: (singular) is the prefix for service account usernames, system:authenticated all authenticated users. system:unauthenticated all unauthenticated users etc.
userGroups: e.g. system:serviceaccounts: (plural) is the prefix for service account groups
verbs: get, create, update, watch, list, patch, delete
resources: API groups or group + resources in that group
namespaces: namespaces that this rule matches.
nonResourceURLs: Rules can apply to API resources (such as "pods" or "secrets"), non-resource URL paths (such as "/api"), or neither, but not both. If neither is specified, the rule is treated as a default for all URLs.
For kube-apiserver testing purpose, I will use minimal-audit-policy.yaml which will log everything in metadata level. Logs will be in JSON format:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
We need to set audit backend by adding some configurations to kube-apiserver: /etc/kubernetes/manifests/kube-apiserver.yaml:
- kube-apiserver
- --audit-log-path=/var/log/kube-audit/audit.json
- --audit-log-maxage=5
- --audit-log-maxbackup=5
- --audit-log-maxsize=1
- --audit-log-truncate-enabled
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
Set mount options for audit:
volumeMounts:
- mountPath: /etc/kubernetes/audit-policy.yaml
name: audit
readOnly: true
- mountPath: /var/log/kube-audit
name: audit-log
readOnly: false
volumes:
- hostPath:
path: /etc/kubernetes/audit-policy.yaml
type: File
name: audit
- hostPath:
path: /var/log/kube-audit
type: DirectoryOrCreate
name: audit-log
Above config will create dir /var/log/kube-audit where log files will be created. You can follow files with tail -f /var/log/kube-audit/audit.json | jq
kube-apiserver will watch for config changes and reload automatically. If you need to reload configs e.g. new audit-policy.yaml, you need to delete kube-apiserver and new Pod will be created automatically:
kubectl delete pod/kube-apiserver-master1 -n kube-system
If you set - --audit-log-path=- all logs will go to stdout and you can follow logs with:
kubectl logs -f kube-apiserver-master1 -n kube-system
Repository audit-policy.yaml is just a starting point which you can use to define what events to wan't to be logged and what are just continuous high volume events.
Note! I have issues with kube-apiserver, so falco deployment is still under development
Falco is security event detection tool for kubernetes.
helm repo add falcosecurity https://falcosecurity.github.io/charts
"falcosecurity" has been added to your repositories
helm search repo falco
NAME CHART VERSION APP VERSION DESCRIPTION
falcosecurity/falco 1.7.7 0.27.0 Falco
falcosecurity/falco-exporter 0.5.1 0.5.0 Prometheus Metrics Exporter for Falco output ev...
falcosecurity/falcosidekick 0.2.9 2.21.0 A simple daemon to help you with falco's outputs
Check Falco default values:
helm pull falcosecurity/falco
Install falco:
helm install -f custom-falco-values.yaml falco falcosecurity/falco --create-namespace --namespace falco
Uninstall:
helm uninstall falco -n falco
Falco service IP:
kubectl get service/falco -o=jsonpath={.spec.clusterIP} -n falco
For falco we need to set below setting for /etc/kubernetes/manifests/: --audit-webhook-config-file=/etc/kubernetes/webhook-config.yaml
First I'll install hub which is a wrapper around git. It's not needed for Flux, so you can skip this step, I installed hub just because we are doing git stuff here with Flux:
sudo apt search ^hub$
sudo apt install hub
git config --global hub.protocol https
git config --list
hub.protocol=https
Hub will prompt for GitHub username & password the first time it needs to access the API and exchange it for an OAuth token, which it saves in ~/.config/hub
Set couple env variables for Flux installation, I grab them from above hub installation:
export GITHUB_TOKEN=$(cat ~/.config/hub | grep token | awk '{print $2}')
export GITHUB_USER=$(cat ~/.config/hub | grep user | awk '{print $3}')
Download and install flux:
curl -s https://toolkit.fluxcd.io/install.sh | sudo bash
Check version:
flux --version
flux version 0.9.1
Check flux prerequisities:
flux check --pre
I have repo structure:
Parameter info:
flux bootstrap github --help
--owner: Github user
--repository: Repo-name. The bootstrap command creates a repository if one doesn't exist
--branch: main
--private: false. This is public repo
--personal: true. This is personal account: 'if true, the owner is assumed to be a GitHub user'
--path: path in repo relative to repo root. When you have different setups like production, staging etc. or like I have test1, test2 etc. you set path to setup here
Bootstrap flux into cluster/overlays/test1:
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=flux-test \
--branch=main \
--private=false \
--personal \
--path=./clusters/test1
Above bootstrap will create github repo 'flux-test' with README.md and ./clusters/test1/flux-system with gotk-components.yaml, gotk-sync.yaml and kustomization.yaml and install repo deploy key. Flux will be installed into flux-system namespace:
A kustomization requires a source. Here the source is git repo 'flux-test', the source just points to that repo. Create a source from git repo:
flux create source git podinfo \
--url=https://github.com/jouros/flux-test \
--branch=main \
--interval=30s \
--export > ./clusters/test1/test1-source.yaml
In above config source is checked every 30s, if the source changes, the kustomization which is related to that source, will be notified. Above config also refer to spesific branch 'main'.
Check:
kubectl get gitrepositories.source.toolkit.fluxcd.io -A
or
flux get sources git
NAMESPACE NAME URL READY STATUS AGE
flux-system flux-system ssh://git@github.com/jouros/flux-test True Fetched revision: main/e680931682e234b86a2808ba20ac06b3ddbf2784 30m
flux-system podinfo https://github.com/jouros/flux-test True Fetched revision: main/e680931682e234b86a2808ba20ac06b3ddbf2784 5m31s
Create kustomization manifest for podinfo:
--source: source with kind: GitRepository
--path: path to the directory containing a kustomization.yaml file
--prune: enable garbage collection
--validation: client = local dry-run validation, server = APIServer dry-run
--interval: The interval at which to retry a previously failed reconciliation
flux create kustomization apps1 \
--source=podinfo \
--path="./apps/kustomize/test1/podinfo" \
--prune=true \
--validation=client \
--interval=5m \
--export > ./clusters/test1/apps1-kustomization.yaml
In above config kustomization is related to previously defined source 'podinfo'. Applications are deployded from ./apps/kustomize/test1/podinfo.
Check:
flux get kustomizations
At this stage I have podinfo app running with kustomize where I changed 'minReplicas' value for cluster setup test1.
Delete:
flux delete kustomization apps1
flux delete source git podinfo
Uninstall flux:
flux uninstall --namespace=flux-system
Next I'll add helm GitOps:
mkdir ./apps/base/charts/
helm create busybox + some editing
Test: helm install busybox ./apps/base/charts/busybox/
Create flux helm:
flux create hr busybox \
--interval=10m \
--source=GitRepository/podinfo \
--chart=./apps/base/charts/busybox/
✚ generating HelmRelease
► applying HelmRelease
✔ HelmRelease created
◎ waiting for HelmRelease reconciliation
✔ HelmRelease busybox is ready
✔ applied revision 0.1.0
I'm using previously defined git source 'podinfo'.
check:
flux get helmreleases
NAME READY MESSAGE REVISION SUSPENDED
busybox True Release reconciliation succeeded 0.1.0 False
Busybox can also be seen with:
helm list -A
Operations:
I changed sleep to 600 in templates/deployment.yaml and version to 0.2.0. After git push, flux updated revision to 0.2.0.
By default, Prometheus discovers PodMonitors and ServiceMonitors within its namespace, that are labeled with the same release tag as the prometheus-operator release. Sometimes, you may need to discover custom PodMonitors/ServiceMonitors, for example used to scrape data from third-party applications. An easy way of doing this, without compromising the default PodMonitors/ServiceMonitors discovery, is allowing Prometheus to discover all PodMonitors/ServiceMonitors within its namespace, without applying label filtering. To do so, you can set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues and prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues to false.
Update Prometheus deployment with above explained values:
helm upgrade --reuse-values prometheus-stack prometheus-community/kube-prometheus-stack --namespace monitoring --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
Release "prometheus-stack" has been upgraded. Happy Helming!
Add gotk-monitoring.yaml to flux-test/clusters/test1/flux-system/ and push to repo. Check podMonitors:
kubectl get podMonitors -n monitoring
NAME AGE
helm-controller 49s
kustomize-controller 49s
notification-controller 49s
source-controller 49s
What metrics we can get:
Ready status metrics: gotk_reconcile_condition{kind, name, namespace, type="Ready", status="True"}
status can be: True, False, Unknown or deleted.
e.g. number of deployments:
sum(gotk_reconcile_condition{namespace=~"default|flux-system", type="Ready", status=~"True", kind=~"Kustomization|HelmRelease"})
Time spent reconciling:
gotk_reconcile_duration_seconds_bucket{kind, name, namespace, le}
gotk_reconcile_duration_seconds_sum{kind, name, namespace}
gotk_reconcile_duration_seconds_count{kind, name, namespace}
e.g. average reconciliation:
sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~"default|flux-system", kind=~"Kustomization|HelmRelease"}[5m])) by (kind)
My host:
sudo dpkg --print-architecture
amd64
Download sops:
curl -OL https://github.com/mozilla/sops/releases/download/v3.7.0/sops_3.7.0_amd64.deb
My test plan is to have sops encrypted Configmap which will overwrite default environment values from my busybox helm chart. I have data.txt values:
env:
name1: first
value1: "1"
name2: second
value2: "2"
name3: third
value3: "3"
From which I create configmap:
kubectl create configmap busyboxdata --from-file data.txt --dry-run -o yaml > busybox-configmap.yaml
Now I have: flux-test/apps/kustomize/test1/busybox/busybox-configmap.yaml
apiVersion: v1
data:
data.txt: |
env:
name1: first
value1: "1"
name2: second
value2: "2"
name3: third
value3: "3"
kind: ConfigMap
metadata:
creationTimestamp: null
name: busyboxdata
I'll add this config to above dir:
cat <<EOF > ./.sops.yaml
creation_rules:
- path_regex: .*.yaml
encrypted_regex: ^(data|stringData)$
pgp: ${KEY_FP}
EOF
.sops.yaml:
If your secrets are stored under a specific directory, like a git repository, you can create a .sops.yaml configuration file at the root directory to define which keys are used for which filename.
To exclude above .sops.yaml from Flux, I add below to root of repo:
touch .sourceignore
echo '**/.sops.yaml' >> .sourceignore
Encrypt:
sops --encrypt --in-place busybox-configmap.yaml
After above step busybox-configmap.yaml will look like this:
apiVersion: v1
data:
data.txt: ENC[AES256_GCM,data:suNQHWl4BnerkRQx9WzoudPnq6Lul4N07A==,iv:sjhnKe51HlGXQ3keO7CSvUY+dDssPtJgt8Ug0j4fFa0=,tag:en7WIqGBKJJS6H7wHfLpug==,type:str]
kind: ConfigMap
metadata:
creationTimestamp: null
name: busyboxdata
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2021-03-25T16:36:22Z"
mac: ENC[AES256_GCM,data:g6A0NQrL5+qAdHFySsUUG0W5P9Se7MqFaPThLhIX4u8UrAOyxxXQc0Keh/r/sVLBN/OBH3L276ywhK7o1UvK0z1zwvhdsbLsLHRDpaptAviF9ePxAbTp2U2RQJXgkxN9cbDboBUNxtl0sfu/8EZtltMVr7on2Kwnal7JTFAj1M0=,iv:A82AL4+GCQuWnYminGNeELjLW1BlXifPQ/+BqAAr5WY=,tag:I3do9vEcFwCid8huOqzhPw==,type:str]
pgp:
- created_at: "2021-03-25T16:36:22Z"
enc: |
-----BEGIN PGP MESSAGE-----
hQIMAx/ZFgZfXuJNAQ//a6Soh/F6mEoxwW8jcVy4NxZUXW/aQJ16uNQur8xkuuHN
AH15urUTBd8xN+3zQTuM0jun10G8cGOcdA0yBGeEQe8Axsh/ZNYkwijj2LjTpk8k
7PGlSKALqZsOJ6hKl9VEOQ3+h1LBG6gFh4s916e/AYXXGd8pYi0kJiRGDclkt8U0
4BotCNlaQ1BE67223SX9rjdVEAbs1lMgL/37+J3AEnIN12FdovOeXKxsHxtpEP2A
a3uKeWGMW5FDPH9udTmlDiW5ekVRMAOggc7Ihtd74P5IwnSdbEZsZom0q49jv6/7
VZ+IYKIpTpnfevgfEFmSDxOODGTp2X5btP+51jSvVl/CNKrWYCVvBudhHRuB3uHT
vUp41n2hRznyq9dph9x3TEobaGwj3jokl+jwa6vc7DoeNHfPWsA2yyH5TavJinxP
pufACD0XCfrL9+79yEWVt/IZDgIVupxjSIEz5+ID275Mx/5/XpHDwHlhTX/gXR0a
Fssj6rm/1C4VJftZfFIkMOm1I/MoHUim1Syu/BL4j0WemZ+uI+RJVaxxzx0Q6Tyl
EQHf3ZlK/Nzu4SqMiS4If7vwyYhRmZ6vMK9pHzObv1Zjbd5edueydD4tEzJoAA2r
hHiPBIRkqLjYFdjmkm2DnzS0y+fFHdUoOi6hbRkPH5V4ULa/ThBEy/SzOWY89ADS
XAElbOOdk5J5hImU+ZFS1zQLeOasf4Nwdbnd1jKG+aWBzlD8iUnsa2BpRqsGollj
1kkZthi89CiQ7iknAWo5Yez0Y65p8IF0IPJ8XW46KP0jDbpjxQBHSFUu6g18
=MW5w
-----END PGP MESSAGE-----
fp: 041173C69061E4F841DD8E080650CAE18738112D
encrypted_regex: ^(data|stringData)$
version: 3.7.0
I installed sops-gpg secret with pgp key into flux-system namespace:
gpg --export-secret-keys --armor "${KEY_FP}" |
kubectl create secret generic sops-gpg \
--namespace=flux-system \
--from-file=sops.asc=/dev/stdin
I already have flux git source setup with name 'podinfo' which points to flux-test repo, so I just need push busybox-configmap.yaml to git repo.
Next I'll setup busybox values.yaml with some default environment values which I will overwrite with Configmap values, I'll add these lines to values.yaml:
env:
name1: first
value1: "5"
name2: second
value2: "6"
name3: third
value3: "7"
busybox templates/deployment.yaml setup:
env:
- name: {{ .Values.env.name1 }}
value: {{ .Values.env.value1 | quote }}
- name: {{ .Values.env.name2 }}
value: {{ .Values.env.value2 | quote }}
- name: {{ .Values.env.name3 }}
value: {{ .Values.env.value3 | quote }}
Complete busybox helmrelease config:
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: busybox
spec: {}
status: {}
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: busyboxconfigmap
namespace: flux-system
spec:
decryption:
provider: sops
secretRef:
name: sops-gpg
interval: 5m0s
path: ./apps/kustomize/test1/busybox
prune: true
targetNamespace: flux-system
sourceRef:
kind: GitRepository
name: podinfo
validation: client
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: busybox
namespace: flux-system
spec:
interval: 10m0s
chart:
spec:
chart: ./apps/base/charts/busybox/
sourceRef:
kind: GitRepository
name: podinfo
interval: 1m0s
targetNamespace: busybox
valuesFrom:
- kind: ConfigMap
name: busyboxdata
valuesKey: data.txt
Above config will:
- create new namespace 'busybox'
- decrypt and deploy configmap 'busyboxdata' into flux-system namespace
- deploy my custom helm release 'busybox' into busybox namespace
- get helm deployment overwite values from configmap 'busyboxdata' which will replace values from values.yaml
My complete repo structure:
.
├── apps
│ ├── base
│ │ ├── charts
│ │ │ └── busybox
│ │ │ ├── charts
│ │ │ ├── Chart.yaml
│ │ │ ├── templates
│ │ │ │ ├── deployment.yaml
│ │ │ │ ├── _helpers.tpl
│ │ │ │ ├── hpa.yaml
│ │ │ │ ├── NOTES.txt
│ │ │ │ ├── service.yaml
│ │ │ │ └── tests
│ │ │ │ └── test-connection.yaml
│ │ │ └── values.yaml
│ │ └── podinfo
│ │ ├── deployment.yaml
│ │ ├── hpa.yaml
│ │ ├── kustomization.yaml
│ │ └── service.yaml
│ └── kustomize
│ ├── test1
│ │ ├── busybox
│ │ │ ├── busybox-configmap.yaml
│ │ │ └── data.txt
│ │ └── podinfo
│ │ ├── hpa.yaml
│ │ └── kustomization.yaml
│ └── test2
├── clusters
│ ├── test1
│ │ ├── apps1-kustomization.yaml
│ │ ├── busybox-helm.yaml
│ │ ├── flux-system
│ │ │ ├── gotk-components.yaml
│ │ │ ├── gotk-monitoring.yaml
│ │ │ ├── gotk-sync.yaml
│ │ │ └── kustomization.yaml
│ │ └── test1-source.yaml
│ └── test2
└── README.md
I have completed setup only for cluster 'test1' which has two apps: 1) podinfo with kustomize and 2) busybox with sops and custom helm charts. Similar way I could define more different type of clusters test2, test3 and so on.
For cluster 'test2' I will create complete setup for my Go restapi platform:
- deploy flux
- deloy krew
- deploy sops
- deloy helm
- deloy node labels
- deploy MetalLB loadbalancer
- deploy haproxy ingress
- deploy Prometheus
- deploy haproxy-fix
- deploy application layer: postgres and gorestapi Pods
If things get badly wrong, sometimes fastest way back is to re-install cluster state.
kubeadm reset: clean up files that were created by kubeadm init or join. When executed in control-plane node, wipes out all info from previous cluster and print out join info to new cluster.
kubeadm init: Initailize new cluster state. New config files will be created.
kebeadm join: You have to re-join all worker nodes by executing kudeadm reset + kubeadm join.
In my setup I did reset on all nodes, kubeadm init on control-plane and join on worker nodes. I also had to execute some additional commands in every node:
cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF
sudo modprobe overlay sudo modprobe br_netfilter
All labels are wiped out, so I had to re-label my cluster nodes for haproxy-ingress.
I got this error: "failed to set bridge addr: "cni0" already has an IP address" for some starting Pod. I executed below commands in every node:
sudo ip link set cni0 down
sudo brctl delbr cni0
Finally I rebooted control-plane node and cluster was back in business.