{"id":17961041,"url":"https://github.com/slok/kahoy-helm-example","last_synced_at":"2025-05-01T12:22:08.288Z","repository":{"id":41487170,"uuid":"306837536","full_name":"slok/kahoy-helm-example","owner":"slok","description":"A production-ready Kahoy deploy example using Helm as the templating engine","archived":false,"fork":false,"pushed_at":"2020-10-26T07:23:32.000Z","size":34,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T21:15:12.416Z","etag":null,"topics":["ci","cicd","deploy","deployment","gitops","helm","k8s","kahoy","kubernetes"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/slok.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2020-10-24T08:18:14.000Z","updated_at":"2023-11-26T07:20:56.000Z","dependencies_parsed_at":"2022-09-21T10:23:33.465Z","dependency_job_id":null,"html_url":"https://github.com/slok/kahoy-helm-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slok%2Fkahoy-helm-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slok%2Fkahoy-helm-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slok%2Fkahoy-helm-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slok%2Fkahoy-helm-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slok","download_url":"https://codeload.github.com/slok/kahoy-helm-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251872380,"owners_count":21657634,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","cicd","deploy","deployment","gitops","helm","k8s","kahoy","kubernetes"],"created_at":"2024-10-29T11:08:07.343Z","updated_at":"2025-05-01T12:22:08.263Z","avatar_url":"https://github.com/slok.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kahoy-helm-example\n\n[![Build Status][deploy-image]][deploy-url] [![Build Status][schedule-sync-all-image]][schedule-sync-all-url] [![Build Status][docker-image-image]][docker-image-url]\n\nThis example shows a production-ready, simple and reliable way of deploying multiple apps on different environments based on Kubernetes.\n\n## Features\n\n- Multiple environments (staging and production).\n- Multiple applications.\n- Each app can have different configuration and version.\n- Scalable to hundreds of apps (Uses a generic Helm chart).\n- Optional configuration inheritance.\n- Waits until deployed/deleted resources are ready.\n- Deployment flow through Git and PRs.\n- Scheduled syncs to fix _manual_ changes.\n- Use Github actions as CI.\n\n## How does it work\n\n- We will have a generic [Helm] chart with multiple options. Each app can configure these options.\n- We will use a generation script that will convert all these options into Kubernetes app manifests.\n- We will use [Kahoy] to sync these manifests on Kubernetes (create, update and delete).\n- We will have a wait script that uses [Kubedog] to wait for resources on the cluster to be ready.\n- We will use Github actions CI to generate, deploy and wait.\n\n### Step 1: Kubernetes manifests generation\n\nWe have a generic helm chart ready to be used to generate the required manifests to deploy a service on Kubernetes.\n\nWe are only using helm for rendering, the generic chart comes with:\n\n- Deployment + service\n- Autoscaling\n- Ingress\n- Monitoring\n- ...\n\nWe have set default values, so applications only need to configure whatever they required. This will create our abstraction layer so users don't need to craft huge YAML manifests to deploy a generic service.\n\n- [`./charts`](charts/): Generic [Helm] chart.\n- [`./_gen`](_gen/): Generated manifests (these are the ones that will be deployed).\n- [`./services`](services/): Our applications, with their version, configuration.\n\n#### Services structure\n\nOur services have this structure `services/{SERVICE}/{ENV}`, e.g:\n\n```text\n├── app1\n│   ├── staging\n│   └── production\n└── app2\n    └── production\n```\n\nThis will generate 3 applications:\n\n- `app1` in `staging`.\n- `app1` in `production`.\n- `app2` in `production`.\n\nTo configure the services, we need `config.yaml` and `version` files to generate the required Kubernetes YAMLs.\n\nThe envs can inherit the app level configuration and version if they don't redefine values, however `config.yaml` file must exist on app and env level (can be empty). e.g:\n\n```text\n├── app1\n│   ├── config.yaml         `app1-root-config`\n│   ├── production\n│   │   ├── config.yaml     `app1-prod-config`\n│   │   └── version         `app1-prod-version`\n│   ├── staging\n│   │   └── config.yaml     `app1-staging-config`\n│   └── version             `app1-root-version`\n└── app2\n    ├── config.yaml         `app2-root-config`\n    └── production\n        ├── config.yaml     `app2-prod-config`\n        └── version         `app2-prod-version`\n```\n\nThis would produce this:\n\n- app1 production: `app1-root-config` + `app1-prod-config` and `app1-prod-version`.\n- app1 staging: `app1-root-config` + `app1-staging-config` and `app1-root-version`.\n- app2 production: `app2-root-config` + `app2-prod-config` and `app2-prod-version`.\n\n#### Generated structure\n\nWe want to deploy the apps by env, so, to give flexibility, we are generating the envs in `_gen/{ENV}/{SERVICE}` structure. e.g:\n\n```text\n./_gen/\n├── production\n│   ├── app1\n│   │   └── resources.yaml\n│   └── app2\n│       └── resources.yaml\n└── staging\n    └── app1\n        └── resources.yaml\n```\n\nNow to deploy to different envs we can use `_gen/production` and `_gen/staging`, or if we add more envs, `_gen/xxxxxx`.\n\n#### How to generate\n\nWith `make generate` Will regenerate everything, if any of the resources has been deleted (e.g an application, and ingress...), these will be removed from the generated files.\n\nAll this generation logic can be checked in [`scripts/generate.sh`](scripts/generate.sh).\n\n### Step 2: Deploy to Kubernetes\n\nWe will use [Kahoy] to deploy to Kubernetes. [Kahoy] is a great tool for raw manifests, it handles the changes. these are the main features we need:\n\n- Understands Kubernetes resources.\n- Has dry-run and diff stages.\n- Can deploys only the changes between 2 states (old and new).\n- Garbage collection.\n- Uses kubectl under the hoods to deploy, no magic.\n\nThis simplifies everything because we don't depend on a specific tool, we are deploying Raw kubernetes manifests in a smart way:\n\nAll the dpeloy commands can be see in [`scripts/deploy.sh`](scripts/deploy.sh).\n\n#### State store on Kubernetes\n\nWe are using [Kahoy Kubernetes state storage][kahoy-kubernetes]. This is how Kahoy knows what needs to deploy/delete. For this Kahoy has a storage ID.\n\nWe will use a different ID per environment. This way if we deploy only Production manifests, Kahoy will not detect the staging manifests as they have dissapear and needs to garbage collect.\n\nCheck [this][storage-id-example] to see where is the ID specificed.\n\n### Step 3: Deployment Feedback\n\nDeploy feedback means the feedback that we get after a deployment, not everyone wants this, but some companies are used to wait until the deployment is ready to mark the deployment as good or bad.\n\n[Kahoy] solves this by giving the user an optional report of what applied. With this report we can know what we need to wait for.\n\nTo wait we will use [Kubedog], Kubedog knows how to wait Kubernetes core workloads, these are `Deployments`, `StatefulSets`, `Jobs` and `Daemonsets`.\n\nSo in a few words, we will take the Kahoy's [report output][kahoy-report], and pass it through [Kubedog] so it will wait until all the resources are ready (e.g replicas of a deployment updated).\n\nWe also can wait for deleted resources, for this, we use `kubetcl wait`.\n\nAll this waiting logic can be checked in [`scripts/wait.sh`](scripts/wait.sh).\n\n## CI\n\nWe have used Github actions for these example but other CI would work too (e.g Gitlab CI).\n\nWe have set 2 workflows:\n\n- PR based\n- Scheduled (cron style).\n\nThe PR based workflow will execute generate the manifests and execute a kubernetes dry-run+diff with only the changed Kubernetes resources. This will be made for Production and staging environments separetly.\n\nWhen merged, it will execute the same as before but after the dry-run+diff, the real deploy, and then will wait for the resources.\n\nOn the other hand the scheduled pipeline contrary to the PR, it will sync all resources, not only the changed ones. This ensures the manually changed resources on the resources handled by Kahoy are overwritten. This will happen every 12 hours.\n\n### Examples\n\n#### Add a new app (create)\n\n- [PR](https://github.com/slok/kahoy-helm-example/pull/6)\n- [Dry-run](https://github.com/slok/kahoy-helm-example/runs/1304427783)\n- [Deploy and wait](https://github.com/slok/kahoy-helm-example/runs/1304436794)\n\n#### Fix ingress (change)\n\n- [PR](https://github.com/slok/kahoy-helm-example/pull/7)\n- [Dry-run](https://github.com/slok/kahoy-helm-example/runs/1304464224)\n- [Deploy and wait](https://github.com/slok/kahoy-helm-example/runs/1304469985)\n\n#### Remove ingress (garbage collection)\n\n- [PR](https://github.com/slok/kahoy-helm-example/pull/8)\n- [Dry-run](https://github.com/slok/kahoy-helm-example/runs/1304477194)\n- [Deploy and wait](https://github.com/slok/kahoy-helm-example/runs/1304482455)\n\n#### Remove app (delete + create)\n\n- [PR](https://github.com/slok/kahoy-helm-example/pull/9)\n- [Dry-run](https://github.com/slok/kahoy-helm-example/runs/1304487157)\n- [Deploy and wait](https://github.com/slok/kahoy-helm-example/runs/1304494947)\n\n#### Scheduled pipeline\n\n- [Production deploy](https://github.com/slok/kahoy-helm-example/runs/1306338587)\n- [Staging deploy](https://github.com/slok/kahoy-helm-example/runs/1306338620)\n\n[deploy-image]: https://github.com/slok/kahoy-helm-example/workflows/deploy/badge.svg\n[deploy-url]: https://github.com/slok/kahoy-helm-example/actions?query=workflow%3Adeploy\n[docker-image-image]: https://github.com/slok/kahoy-helm-example/workflows/docker-image/badge.svg\n[docker-image-url]: https://github.com/slok/kahoy-helm-example/actions?query=workflow%3Adocker-image\n[schedule-sync-all-image]: https://github.com/slok/kahoy-helm-example/workflows/Schedule%20sync%20all/badge.svg\n[schedule-sync-all-url]: https://github.com/slok/kahoy-helm-example/actions?query=workflow%3A%22Schedule+sync+all%22\n[helm]: https://github.com/helm/helm\n[kahoy]: https://github.com/slok/kahoy\n[kubedog]: https://github.com/werf/kubedog\n[kahoy-kubernetes]: https://docs.kahoy.dev/topics/provider/kubernetes/\n[storage-id-example]: https://github.com/slok/kahoy-helm-example/blob/bee08ed0c63e1224544d990bd2683ae66d4ba4b7/scripts/deploy.sh#L23\n[kahoy-report]: https://docs.kahoy.dev/topics/report/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslok%2Fkahoy-helm-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslok%2Fkahoy-helm-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslok%2Fkahoy-helm-example/lists"}