Home

Awesome

sbt-kubeyml

Codacy Badge Maven Central Scala Steward badge

Sbt kubeyml logo

An sbt plugin to generate typesafe kubernetes deployment plans for scala projects

Deployment plugin

Add the plugin to your plugins.sbt

addSbtPlugin("org.vaslabs.kube" % "sbt-kubeyml" % "0.4.1")

Add the plugin in your project and enable it

enablePlugins(KubeDeploymentPlugin)

The plugin depends on DockerPlugin from sbt-native-packager

enablePlugins(DockerPlugin)

Try to run

kubeyml:gen

Properties

sbt keydescriptiondefault
namespaceThe kubernetes namespace of the deploymentDefault value is project name
applicationThe name of the deploymentDefault value is project name
dockerImageThe docker image to deploy in a single containerDefault is the picked from sbt-native-packager
portsList of container ports optionally tagged with namedockerExposedPorts from docker plugin
livenessProbeHealthcheck probeHttpProbe(HttpGet("/health", 8080, List.empty), 0 seconds, 1 second, 10 seconds, 3, 1)
readinessProbeProbe to check when deployment is ready to receive trafficlivenessProbe
annotationsMap[String, String] for spec template annotations (e.g. aws roles)empty
replicasthe number of replicas to be deployed2
imagePullPolicyImage pull policy for kubernetes, set to IfNotPresent or AlwaysIfNotPresent
commandCommand for the containerempty
argsarguments for the commandempty Seq
envsMap of environment variables, raw, field path or secret are supportedempty
resourceRequestsResource requests (cpu in the form of m, memory in the form of MiBResource(Cpu(100), Memory(256))
resourceLimitsResource limits (cpu in the form of m, memory in the form of MiBResource(Cpu(1000), Memory(512))
targetThe directory to output the deployment.ymltarget in ThisProject / kubeyml
deploymentThe key to access the whole Deployment definition, exposed for further customisationInstance with above defaults
persistentVolumesPersistent volumes that should be used by deployment podsempty Seq

Recipes

Single namespace, two types of deployments with secret and dependency

import kubeyml.deployment._
import kubeyml.deployment.api._
import kubeyml.deployment.plugin.Keys._

lazy val deploymentName = sys.env.getOrElse("DEPLOYMENT_NAME", "myservice-test")
lazy val secretsName = sys.env.getOrElse("SECRETS_NAME", "myservice-test-secrets")
lazy val serviceDependencyConnection = sys.env.getOrElse("MY_DEPENDENCY", "https://localhost:8080")

lazy val deploymentSettings = Seq(
  kube / namespace := "my-namespace", //default is ThisProject / name 
  kube / application := deploymentName, //default is ThisProject / name
  kube / command := Some("webserver"),
  kube / args := Seq("-c","/path/to/config"),
  kube / envs := Map(
    EnvName("JAVA_OPTS") -> EnvRawValue("-Xms256M -Xmx2048M"),
    EnvName("MY_DEPENDENCY_SERVICE") -> EnvRawValue(serviceDependencyConnection),
    EnvName("MY_SECRET_TOKEN") -> EnvSecretValue(name = secretsName, key = "my-token")
  ),
  kube / resourceLimits := Resource(Cpu.fromCores(2), Memory(2048+512)),
  kube / resourceRequests := Resource(Cpu(500), Memory(512)),
  //if you want you can use something like the below to modify any part of the deployment by hand
  kube / deployment := (kube / deployment).value.pullDockerImage(IfNotPresent)
)

Gitlab CI/CD usage (followup from previous)

stages:
  - publish-image
  - deploy

.publish-template:
  stage: publish-image
  script:
      - sbt docker:publish
      - sbt kubeyml:gen
  artifacts:
      untracked: false
      paths:
        - target/kubeyml/deployment.yml

.deploy-template:
  stage: deploy
  image: docker-image-that-has-your-kubectl-config
  script:
     - kubectl apply -f target/kubeyml/deployment.yml

publish-test:
  before_script:
      export MY_DEPENDENCY=${MY_TEST_DEPENDENCY}
  extends: .publish-template

deploy-test:
  extends: .deploy-template
  dependencies:
     - publish-test

publish-prod:
  before_script:
    - export MY_DEPENDENCY=${MY_PROD_DEPENDENCY}
    - export SECRETS_NAME=${MY_PROD_SECRET_NAME}
    - export DEPLOYMENT_NAME=my-service-prod
  extends: .publish-template

deploy-prod:
  extends: .deploy-template
  dependencies:
   - publish-prod

Service plugin

This plugin depends on the deployment plugin and every property is derived from that.

There's some room for customisation.

enablePlugins(KubeServicePlugin)

Then your gitlab publish template will look like (example extended from above)

.publish-template:
  stage: publish-image
  script:
      - sbt docker:publish
      - sbt kubeyml:gen
  artifacts:
      untracked: false
      paths:
        - target/kubeyml/deployment.yml
        - target/kubeyml/service.yml

And deploy with

.deploy-template:
  stage: deploy
  image: docker-image-that-has-your-kubectl-config
  script:
     - kubectl apply -f target/kubeyml/deployment.yml     
     - kubectl apply -f target/kubeyml/service.yml

Properties

sbt keydescriptiondefault
portMappingsPort mappings against the deployment (service to pod)Derived from deployment
serviceKey configuration for modifying the service propertiesDerived from deployment

Ingress Plugin

This plugin depends on the service plugin. It provides some safety nets to make sure the service name and service ports are mapped properly to the ingress configuration.

To extend on the above, you can configure ingress generation with the following steps.

  1. Enable the plugin
enablePlugins(KubeIngressPlugin)
  1. Set an ingress name and a hostname
lazy val ingressEnvName = sys.env.getOrElse("HELLO_INGRESS_NAME", "helloworld-ingress-test")

lazy val hostName = sys.env.getOrElse("YOUR_HOST_NAME", "your-hostname.yourdomain.smth")
  1. Configure the plugin
  import kubeyml.protocol.{NonEmptyString, Host}
  import kubeyml.deployment.plugin.Keys._
  import kubeyml.ingress.api._

  import kubeyml.ingress.plugin.Keys._
  import kubeyml.service.plugin.Keys._
  import kubeyml.ingress.{HttpRule, ServiceMapping, Path => IngressPath}
  
  val ingressSettings = Seq(
      (kube / ingressName) := ingressEnvName,
      (kube / ingressRules) := List(
        HttpRule(Host(hostName), List(
          IngressPath(ServiceMapping((kube / service).value.name, 8085), "/hello-world")
        ))
      ),
      (kube / ingressAnnotations) := Map(
        Annotate.nginxIngress(), // this adds kubernetes.io/ingress.class: nginx
        Annotate.nginxRewriteTarget("/hello-world"), //this adds nginx.ingress.kubernetes.io/rewrite-target: /hello-world
        NonEmptyString("your-own-annotation-key") -> "value"
      )
    )

The command to generate the ingress is the same kubeyml:gen which will generate 3 yml files.

Gitlab CI extension

Potentially the CI configuration evolves to

.publish-template:
  stage: publish-image
  script:
      - sbt docker:publish
      - sbt kubeyml:gen
  artifacts:
      untracked: false
      paths:
        - target/kubeyml/deployment.yml
        - target/kubeyml/service.yml
        - target/kubeyml/ingress.yml
.deploy-template:
  stage: deploy
  image: docker-image-that-has-your-kubectl-config
  script:
     - kubectl apply -f target/kubeyml/deployment.yml
     - kubectl apply -f target/kubeyml/service.yml
     - kubectl apply -f target/kubeyml/ingress.yml

Properties

sbt keydescriptiondefault
ingressRulesA list of Rules (currently only supports HttpRuleN/A
ingressNameThe name of the ingressThe application name from deployment
ingressKey configuration for modifying the ingress propertiesSome values are derived from service