# ScratchPad: Identify ghost diff during kapp controller reconcilliation
Kapp controller is a package manager compatible with Gitops philosophy. In each reconcilliation cycle, it monitors current state of the resources on cluster and tries to bring it to the desired state if there is any mismatch. It does so with the help of kapp. kapp tries to do a diff of the current live state of the resources with the desired state. Sometimes, even though there is no change, kapp still thinks some resources have changed and tries to redeploy them as part of the reconcilliation. We call them as `ghost` diffs. As package consumers are aware of the package and package install only, it becomes difficult for them to identify what part of the resource configuration is causing these diff.
In this blog, we will see how to identify the resources causing these ghost diffs and also what part of their configuration is participating in it.
## You will need these to start your journey:
* carvel tool set
* Kubernetes cluster(I'm using minikube)
Since this blog will be using `HorizontalPodAutoscaler`, we have to enable `metrics-server` on minikube.
```console
$ minikube addons enable metrics-server
▪ Using image k8s.gcr.io/metrics-server/metrics-server:v0.4.2
The 'metrics-server' addon is enabled
```
## Install the
For the purpose of this blog, I have already created a package `simple-app-package`. This package is part of the package repository `my-pkg-repo`. If you are interested in how to create package and package repository, I would recommend you to go for this link(https://carvel.dev/kapp-controller/docs/v0.34.0/packaging-tutorial/).
First, we need to install the package repository. To interact with package and package Repository, I will be using `kctrl`
```console
$ kctrl package repository add -r demo-pkg-repo --url docker.io/rohitagg2020/my-pkg-repo:1.0.0
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Waiting for package repository to be added
1:38:45PM: packagerepository/demo-pkg-repo (packaging.carvel.dev/v1alpha1) namespace: default: Reconciling
1:38:50PM: packagerepository/demo-pkg-repo (packaging.carvel.dev/v1alpha1) namespace: default: ReconcileSucceeded
Succeeded
```
```cat > repo.yml << EOF
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageRepository
metadata:
name: simple-package-repository
spec:
fetch:
imgpkgBundle:
image: docker.io/rohitagg2020/my-pkg-repo:1.0.0
EOF
```
```console
$ kapp deploy -a repo -f repo.yml -y
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Changes
Namespace Name Kind Conds. Age Op Op st. Wait to Rs Ri
default simple-package-repository PackageRepository - - create - reconcile - -
Op: 1 create, 0 delete, 0 update, 0 noop, 0 exists
Wait to: 1 reconcile, 0 delete, 0 noop
1:10:24PM: ---- applying 1 changes [0/1 done] ----
1:10:24PM: create packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
1:10:24PM: ---- waiting on 1 changes [0/1 done] ----
1:10:24PM: ongoing: reconcile packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
1:10:24PM: ^ Waiting for generation 1 to be observed
1:10:25PM: ongoing: reconcile packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
1:10:25PM: ^ Reconciling
1:10:30PM: ok: reconcile packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
1:10:30PM: ---- applying complete [1/1 done] ----
1:10:30PM: ---- waiting complete [1/1 done] ----
Succeeded
```
Once the package repository is installed, we can check the list of available packages.
```console
$ kctrl package available list --summary=false
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Available packages in namespace 'default'
Name Version Released at
simple-app.corp.com 1.0.0 0001-01-01 00:00:00 +0000 UTC
Succeeded
$ kubectl get packages
NAME PACKAGEMETADATA NAME VERSION AGE
simple-app.corp.com.1.0.0 simple-app.corp.com 1.0.0 5m11s
```
Let's install the package.
```console
$ kctrl package install -i pkg-demo -p simple-app.corp.com --version 1.0.0
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Creating service account 'pkg-demo-default-sa'
Creating cluster admin role 'pkg-demo-default-cluster-role'
Creating cluster role binding 'pkg-demo-default-cluster-rolebinding'
Creating package install resource
Waiting for PackageInstall reconciliation for 'pkg-demo'
1:40:20PM: packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default: Reconciling
1:40:30PM: packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default: ReconcileSucceeded
Succeeded
$ kapp deploy -a pkg-demo -f pkginstall.yml -y
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Changes
Namespace Name Kind Conds. Age Op Op st. Wait to Rs Ri
default pkg-demo PackageInstall - - create - reconcile - -
^ pkg-demo-values Secret - - create - reconcile - -
Op: 2 create, 0 delete, 0 update, 0 noop, 0 exists
Wait to: 2 reconcile, 0 delete, 0 noop
1:10:56PM: ---- applying 1 changes [0/2 done] ----
1:10:57PM: create secret/pkg-demo-values (v1) namespace: default
1:10:57PM: ---- waiting on 1 changes [0/2 done] ----
1:10:57PM: ok: reconcile secret/pkg-demo-values (v1) namespace: default
1:10:57PM: ---- applying 1 changes [1/2 done] ----
1:10:57PM: create packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default
1:10:57PM: ---- waiting on 1 changes [1/2 done] ----
1:10:57PM: ongoing: reconcile packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default
1:10:57PM: ^ Waiting for generation 1 to be observed
1:10:58PM: ongoing: reconcile packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default
1:10:58PM: ^ Reconciling
1:11:08PM: ok: reconcile packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default
1:11:08PM: ---- applying complete [2/2 done] ----
1:11:08PM: ---- waiting complete [2/2 done] ----
Succeeded
```
After the deploy has finished, kapp-controller will have installed the package in the cluster. We can verify this by checking the pods to see that we have a workload pod running. The output should show two running pods which is part of simple-app:
```console
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
simple-app-8648457765-8jtpq 1/1 Running 0 56s
simple-app-8648457765-p5lzp 1/1 Running 0 56s
```
In our package, we have set `syncPeriod` to 10 min. This means after every 10 min, kapp controller will try to reconcile the package. And if there are any ghost diff's generated, we will get to know by looking at the configmaps. To identify the configmaps related to an installed package, we have to look at the configmap with Installed Package Prefix. Lets check the configmaps.
```console
$ kubectl get configmaps | grep pkg-demo
pkg-demo-ctrl 1 1m40s
pkg-demo-ctrl-change-ndr7w 1 1m40s
```
Lets do the same after 10 min of package installation, so that reconcilliation cycle would have run once.
```console
$ kubectl get configmaps | grep pkg-demo
pkg-demo-ctrl 1 12m
pkg-demo-ctrl-change-ndr7w 1 12m
pkg-demo-ctrl-change-t7zgc 1 56s
```
We can see that one more configmap is generated. If I check the configmap content, I can see that there has been one update, but I dont know which resource got updated and what is causing this update.
```console
$ kubectl get configmap pkg-demo-ctrl-change-t7zgc -oyaml
apiVersion: v1
data:
spec: '{"startedAt":"2022-04-14T08:21:28.264641525Z","finishedAt":"2022-04-14T08:21:32.42972746Z","successful":true,"description":"update:
Op: 0 create, 0 delete, 1 update, 0 noop, 0 exists / Wait to: 1 reconcile, 0 delete,
0 noop","namespaces":["default"]}'
kind: ConfigMap
```
As a package consumer, I can see that there are ghost diff's appearing. To identify what is causing them, we will make a copy of the package. We will modify the deploy section of the package. It will help us to get the configuration applied by `kapp`. Let's start:
```console
$ kubectl get pkg simple-app.corp.com.1.0.0 -oyaml > copy-simple-app-package.yaml
```
Open copy-simple-app-package.yaml. Remove labels starting with `kapp`. Add below snippet to the kapp section. Setting the `diff-changes` to true will enable the kapp to show changes.
```
- kapp:
rawOptions:
- --diff-changes=true
```
I would recommend not to tinker with the original package. Hence, lets change the pacakge version at all places. Lets change it from 1.0.0 to 2.0.0. Specifically, change the `spec.Version` and `metadata.labels.name`. Now, apply this package in the cluster so that it will be available for install.
```console
$ kubectl apply -f copy-simple-app-package.yaml
package.data.packaging.carvel.dev/simple-app.corp.com.2.0.0 created
```
Now, if we will see list of available packages, we can see our copied package as well.
```console
$ kctrl package available list --summary=false
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Available packages in namespace 'default'
Name Version Released at
simple-app.corp.com 1.0.0 0001-01-01 00:00:00 +0000 UTC
simple-app.corp.com 2.0.0 0001-01-01 00:00:00 +0000 UTC
Succeeded
```
Let's uninstall the previous package and deploy locally created package.
```console
$ kctrl package installed delete -i pkg-demo -y
Delete package install 'pkg-demo' from namespace 'default'
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Deleting package install 'pkg-demo' from namespace 'default'
Waiting for deletion of package install 'pkg-demo' from namespace 'default'
2:08:42PM: packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default: Deleting
2:08:43PM: packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default: DeletionSucceeded
Deleting 'ClusterRole': pkg-demo-default-cluster-role
Deleting 'ClusterRoleBinding': pkg-demo-default-cluster-rolebinding
Deleting 'ServiceAccount': pkg-demo-default-sa
Succeeded
$ kctrl package install -i pkg-demo -p simple-app.corp.com --version 2.0.0
Target cluster 'https://192.168.64.82:8443' (nodes: minikube)
Creating service account 'pkg-demo-default-sa'
Creating cluster admin role 'pkg-demo-default-cluster-role'
Creating cluster role binding 'pkg-demo-default-cluster-rolebinding'
Creating package install resource
Waiting for PackageInstall reconciliation for 'pkg-demo'
2:12:19PM: packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default: Reconciling
2:12:28PM: packageinstall/pkg-demo (packaging.carvel.dev/v1alpha1) namespace: default: ReconcileSucceeded
Succeeded
```
After the package is deployed successfully, lets see how the initial configuration of the resources deployed by package looks like. We can get that by describing App(link to the app section) linked to the package. Similar to configmap, package creates `App` with same name as its own name.As the output is long, I have added only small snippet.
```console
$ kubectl describe app pkg-demo
@@ create deployment/simple-app (apps/v1) namespace: default @@
0 + apiVersion: apps/v1
1 + kind: Deployment
2 + metadata:
3 + annotations:
4 + kbld.k14s.io/images: |
5 + - origins:
6 + - preresolved:
7 + url: index.docker.io/dkalinin/k8s-simple-app@sha256:4c8b96d4fffdfae29258d94a22ae4ad1fe36139d47288b8960d9958d1e63a9d0
8 + url: index.docker.io/dkalinin/k8s-simple-app@sha256:4c8b96d4fffdfae29258d94a22ae4ad1fe36139d47288b8960d9958d1e63a9d0
9 + labels:
10 + kapp.k14s.io/app: "1649925743708215040"
11 + kapp.k14s.io/association: v1.22a4cbb25c518f776737777e8407b8d9
12 + name: simple-app
13 + namespace: default
14 + spec:
15 + progressDeadlineSeconds: 600
16 + replicas: 2
17 + revisionHistoryLimit:
```
Let the reconcilliation loop run once. After the reconcilliation loop is run, we will see that another configmap has been generated. Now, if we will run `app describe` again, we will see the exact diff.
```console
```
This is how, as a package consumer, you can discover the reason for phantom diff's and take appropriate action. In this case, one can add the rebase rule(https://carvel.dev/kapp/docs/v0.46.0/hpa-deployment-rebase/#docs) to remove the phantom diffs.