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/