github.com/vshn/k8ify@v1.1.2-0.20240502214202-6c9ed3ef0bf4/README.md (about) 1 # k8ify 2 3 `k8ify` converts [**Compose**][Compose] files into **Kubernetes** manifests. 4 5 This project adheres to [Semantic Versioning][SemVer] and tries to not break functionality between major versions. 6 7 8 ## Goal & Purpose 9 10 The purpose of this project is to allow developers to generate state-of-the-art Kubernetes manifests without the need to have a degree in Kubernetes-Manifestology. 11 12 Just by describing the components of their application they should be able to get a set of Kubernetes manifests that adhere to best practices. 13 14 The spiritual prototype for k8ify is [Kompose][] and k8ify tries to do things similarly to Kompose. Unfortunately Kompose does not provide the flexibility we need, hence the custom implementation. 15 16 We chose the [Compose][] format as many developers already use Docker Compose for local development and are familiar with it. 17 18 19 ### Non-Goals 20 21 Out of scope of this project are: 22 23 24 #### Builds 25 26 Building their applications (and container images thereof) is something most developers are very proficient in and don't really need any help with. Furthermore the build and test processes are usually very custom to the application. 27 28 29 #### Deployments 30 31 The idea is that the "build" stage of a deployment pipeline generates the manifests and outputs a diff by comparing the manifests to the state in the target cluster (e.g. using `kubectl diff`), and the "deploy" stage then applies the manifests. 32 This results in flexibility to support various modes of deployment, be it plain `kubectl apply` in the next step of the CI/CD pipeline or a GitOps solution like ArgoCD or FluxCD. 33 34 35 ## Mode of Operation 36 37 `k8ify` takes Compose files in the current working directory and converts them to Kubernetes manfests. The manifests are written to the `manifests` directory. 38 39 40 ### Command Line Arguments 41 42 `k8ify` supports the following command line arguments: 43 44 - Argument #1: `environment`. Optional. 45 - Argument #2: `ref`. Optional. 46 - `--modified-image [IMAGE]`: IMAGE has changed. Optional, repeatable. 47 - `--shell-env-file [FILENAME]`: Load additional shell environment variables from file. Optional, repeatable. 48 49 #### `environment` 50 51 `k8ify` supports different environments (e.g. "prod", "test" and "dev") by merging Compose files. A setup could look like this: 52 53 - `compose.yml` - The global default, the base used by all environments. 54 - `compose-prod.yml` - Additional information about the `prod` environment. Used by `k8ify` when asked to generate manifests for the `prod` environment. 55 - `compose-test.yml` - Additional information about the `test` environment. Used by `k8ify` when asked to generate manifests for the `test` environment. 56 - `compose-dev.yml` - Additional information about the developer's local `dev` environment. Never used by `k8ify` but used by developer for running everything locally. 57 58 `k8ify` will choose the correct Compose files and merge them based on the selected environment. 59 60 61 #### `ref` - Multiple deployments in the same environment 62 63 `k8ify` supports multiple deployments in the same environment, e.g. to deploy different branches of an application into the same `test` environment. It does so by adding a `-$ref` suffix to the name of all generated resources. 64 65 Each Compose service is, by default, deployed for each `ref`. If you want to deploy a service only once per environment (e.g. a single shared database for all deployments) you can do so by adding the `k8ify.singleton: true` label to the service. 66 67 A resulting deployment might look like this: 68 69 - deployment/service `mongodb` (singleton) with secret `mongodb-env` 70 - deployment/service `myapp-testbranch1` with secret `myapp-testbranch1-env` 71 - deployment/service `myapp-testbranch2` with secret `myapp-testbranch2-env` 72 73 #### `--modified-image [IMAGE]` - Handling image rebuilding with the same image tag 74 75 A build pipeline usually builds at least one image, tags it with a version number or branch name and pushes it to a registry. If the tag is new, the Deployments/ReplicaSets using this image get updated with the new version number. This will cause K8s to restart the corresponding Pods in order for them to use the new image. 76 77 However, if the image tag stays the same (which is often the case for test branches) there is a problem: The Deployment/ReplicaSet does not need to change at all, and if there is no change K8s does not roll out the new image. 78 79 To work around this problem k8ify can introduce a dummy change to the Deployment/ReplicaSet to force the roll-out. In order to identify which Deployments/ReplicaSets need this dummy change, you can tell k8ify which images have been rebuilt and k8ify will automatically find the relevant Deployments/ReplicaSets. 80 81 This parameter is generally set by the CI/CD pipeline, because the pipeline knows which images it has generated in earlier steps. The image should be specified as `$SERVICE:$TAG` or `$NAMESPACE/$SERVICE:$TAG`, depending on how specific you need to be. You can repeat this parameter for any number of images. 82 83 #### `--shell-env-file [FILENAME]` - Load additional shell environment variables from file 84 85 k8ify relies on the shell environment to fill placeholders in the Compose files. This argument can be used to load additional variables. The files have the usual "KEY=VALUE" format and they support quoted values. 86 87 A use case could be to protect your secrets. Instead of loading them into the shell environment you could put them into a file and use this argument to load said file. 88 89 90 ### Labels 91 92 `k8ify` supports configuring services and volumes by using Compose labels. All labels are optional. 93 94 #### General 95 96 Service Labels 97 98 | Label | Effect | 99 | ------ | ------- | 100 | `k8ify.singleton: true` | Compose service is only deployed once per environment instead of once per `$ref` per environment | 101 | `k8ify.expose: $host` | The first port is exposed to the internet via a HTTPS ingress with the host name set to `$host` | 102 | `k8ify.expose.$port: $host` | The port `$port` is exposed to the internet via a HTTPS ingress with the host name set to `$host` | 103 | `k8ify.converter: $script` | Call `$script` to convert this service into a K8s object, expecting YAML on `$script`'s stdout. Used for plugging additional functionality into k8ify. The first argument sent to `$script` is the name of the resource, after that all the parameters follow (next row) | 104 | `k8ify.converter.$key: $value` | Call `$script` with parameter `--$key $value` | 105 | `k8ify.serviceAccountName: $name` | Set this service's pod(s) spec.serviceAccountName to `$name`, which tells the pod(s) to use ServiceAccount `$name` for accessing the K8s API. This does not set up the ServiceAcccount itself. | 106 | `k8ify.partOf: $name` | This Compose service will be combined with another Compose service (resulting in a deployment or statefulSet with multiple containers). Useful e.g. for sidecars or closely coupled services like nginx & php-fpm. | 107 | `k8ify.annotations.$key: $value` | Add annotation(s) to all resources generated by k8ify | 108 | `k8ify.$kind.annotations.$key: $value` | Add annotation(s) to specific resource types generated by k8ify. $kind uses the default case used by k8s and is always singular (e.g. "StatefulSet") | 109 | `k8ify.exposePlain.$port: true` | Set up a k8s Service which exposes this port directly instead of using the cluster-wide reverse proxy/load balancer, useful for non-HTTP applications (for HTTP always use `k8ify.expose`). Allocated public IP is visible in the k8s Service's `.status` field. | 110 | `k8ify.exposePlain.$port.type: ClusterIP\|LoadBalancer\|ExternalName\|NodePort` | Set the k8s Service type (default `LoadBalancer`) | 111 | `k8ify.exposePlain.$port.externalTrafficPolicy: Cluster\|Local` | Set the k8s Service traffic policy (default `Local`). `Local` makes the client IP visible to the application but may provide worse load balancing than `Cluster`. | 112 | `k8ify.exposePlain.$port.healthCheckNodePort: $port` | Set the k8s Service health check port number. | 113 114 Volume Labels 115 116 | Label | Effect | 117 | ------ | ------- | 118 | `k8ify.size: 10G` | Requested volume size. Defaults to `1G`. | 119 | `k8ify.singleton: true` | Volume is only created once per environment instead of once per `$ref` per environment | 120 | `k8ify.shared: true` | Instead of `ReadWriteOnce`, create a `ReadWriteMany` volume; Services with multiple replicas will all share the same volume | 121 | `k8ify.storageClass: ssd` | Specify the storage class, e.g. 'hdd' or 'ssd'. Available values depend on the target system. | 122 123 #### Health Checks 124 125 For each Compose service k8ify will set up a basic TCP based health check (liveness and startup) by default. 126 For all services providing HTTP endpoints you should provide at least a basic health check path and point `k8ify.liveness` to it. 127 This replaces the TCP based health check by a more specific HTTP(S) check. 128 129 | Label | Effect | 130 | ------ | ------- | 131 | `k8ify.liveness` | Configure a container liveness check. If the check fails the container will be restarted. | 132 | `k8ify.liveness: $path` | Configure the path for a HTTP GET based liveness check. Default is "", which disables the HTTP GET check and uses a simple TCP connection check instead. | 133 | `k8ify.liveness.path: $path` | See previous | 134 | `k8ify.liveness.enabled: true` | Enable or disable the liveness check. Default is true. | 135 | `k8ify.liveness.scheme: 'HTTP'` | Switch to HTTPS for HTTP GET based liveness check. Default is HTTP. | 136 | `k8ify.liveness.periodSeconds: 30` | Configure the periodicity of the check. Default is 30. | 137 | `k8ify.liveness.timeoutSeconds: 60` | Configure the timeout of the check. Default is 60. | 138 | `k8ify.liveness.initialDelaySeconds: 0` | Delay before the first check is executed. Default is 0. | 139 | `k8ify.liveness.successThreshold: 1` | Number of times the check needs to succeed in order to signal that everything is fine. Default is 1. | 140 | `k8ify.liveness.failureThreshold: 3` | Number of times the check needs to fail in order to signal a failure. Default is 3. | 141 | `k8ify.startup` | Configure a container startup check. This puts the liveness check on hold during container startup in order to prevent the liveness check from killing it. | 142 | `k8ify.startup.*` | All the settings work the same as for `k8ify.liveness`. **The values are copied over from `k8ify.liveness` by default** with the following exceptions: | 143 | `k8ify.startup.periodSeconds: 10` | In order to have quick startup the default is lowered to **10**. | 144 | `k8ify.startup.failureThreshold: 30` | In order to give the application a total of 300 seconds to start up, the default is raised to **30**. | 145 | `k8ify.readiness` | This check decides if traffic should be sent to this instance or not. In contrast to the liveness check a failing readiness check will not restart the pod, just mark it as unavailable and not send traffic to it. | 146 | `k8ify.readiness.*` | All the sub-values work the same as for `k8ify.liveness` incl. defaults. No values are copied over. However the readiness check is disabled by default. | 147 | `k8ify.readiness.enabled: false` | Enable or disable the readiness check. Default is false. | 148 149 #### Target Cluster Configuration 150 151 There are some cases in which the output of k8ify needs to be different based on the target cluster's configuration. To make this work some properties of the target cluster can be configured via the `x-targetCfg` root key in the Compose file. 152 153 | Key | Effect | 154 | ---- | ------- | 155 | `appsDomain: $domain` | A cluster may have a wildcard certificate for apps to use. If you configure this option and expose a service using `$domain`, the resulting Ingress uses this wildcard certificate (instead of e.g. Let's Encrypt). | 156 | `maxExposeLength: $length` | k8ify does a length check on the exposed domain names, because if they're too long the Ingress will not work. Default is 63. | 157 | `encryptedVolumeScheme: $provider` | The implementation of encrypted volumes is provider specific. Use this to enable support for a provider. See [Provider](./docs/provider.md) for more information. | 158 159 160 ## Conversion 161 162 The conversion process is documented in depth in [Conversion](./docs/conversion.md). 163 164 165 ## Storage 166 167 Storage support is documented in depth in [Storage](./docs/storage.md). 168 169 170 ## Testing 171 172 In order to validate that `k8ify` does what we expect it to do, we use the concept of "golden tests": a predefined set of inputs (Compose files) and outputs (Kubernetes manifests) are added to the repository. During the testing process we run `k8ify` against each of the inputs, and verify that the outputs match the expected outputs. 173 174 To set up a golden test named `$NAME`, you need to create two things in the `tests/golden/` directory: 175 176 1. A file called `$NAME.yml`, and 177 2. A directory called `$NAME` containing Compose files. 178 179 The structure of the YAML file should look like this: 180 181 ```yaml 182 environments: 183 prod: {} 184 test: 185 refs: 186 - foo 187 - bar 188 vars: 189 SOME_ENV_VAR: "true" 190 ANOTHER_ENV_VAR: "42" 191 ``` 192 193 Note that both the `refs` and `vars` fields are optional, but allow you to control the tests: 194 195 - `refs` will make the test run `k8ify` for each of the provided values. If no `refs` is defined, `k8ify` will be run once for this environment with an empty `ref` value. 196 - `vars` can contain environment variables that are ADDED to the ones that are already set within the testing environment. If your compoose file makes use of any environment variables, make sure to add them here for reproducibility. 197 198 To actually run the tests run `go test` in the root of the repository. 199 200 201 ## License 202 203 This project is licensed under the [BSD 3-Clause License](LICENSE) 204 205 [Compose]: https://github.com/compose-spec/compose-spec/blob/master/spec.md 206 [Kompose]: https://kompose.io/ 207 [SemVer]: https://semver.org/