Usage

Registering Clusters

Clusters can be registered via config.exs or directly with K8s.Cluster.Registry.add/2.

Clusters are referenced by name (:default below) when using a K8s.Client. Multiple clusters can be registered via config or at runtime.

Kubernetes API resources are auto-discovered at boot time. This library is currently tested against k8s OpenAPI specs: 1.1x and master.

Registering Clusters at Run Time

The below will register a cluster named :prod using ~/.kube.config to connect. There are many options for loading a config, this will load the user and cluster from the current-context.

conf = K8s.Conf.from_file("~/.kube/config")
K8s.Cluster.Registry.add(:prod, conf)

Registering a cluster using the k8s' ServiceAccount of the pod:

conf = K8s.Conf.from_service_account()
K8s.Cluster.Registry.add(:prod, conf)

Registering Clusters at Compile Time (config.exs)

Adding a cluster named :default using ~/.kube/config. Defaults to current-context of the kube config file.

config :k8s,
  clusters: %{
    default: %{
      conf: "~/.kube/config"
    }
  }

Using an alternate context:

config :k8s,
  clusters: %{
    default: %{
      conf: "~/.kube/config"
      conf_opts: [context: "other-context"]
    }
  }

Setting cluster and user explicitly:

config :k8s,
  clusters: %{
    default: %{
      conf: "~/.kube/config"
      conf_opts: [user: "some-user", cluster: "prod-cluster"]
    }
  }

Using a pod's service account (pod.spec.serviceAccountName):

A cluster name with a blank configuration will default to using the pod's service account.

config :k8s, clusters: %{
  default: %{}
}

Registering Clusters with Environment Variables

Multiple clusters can be registered via environment variables. Keep in mind that under the hood, k8s uses kubeconfig files.

Environment Variable Prefixes:

  • K8S_CLUSTER_CONF_SA_ - boolean enables authentication to the k8s API with the pods spec.serviceAccount.
  • K8S_CLUSTER_CONF_PATH_ - string absolute path to the kube config file.
  • K8S_CLUSTER_CONF_CONTEXT_ string which context to use in the kube config file.

Examples:

Configure access to a cluster named us_central to use the pod's service account:

export K8S_CLUSTER_CONF_SA_us_central=true

Set the path to a kubeconfig file and the context to use for us_east:

export K8S_CLUSTER_CONF_PATH_us_east="east.yaml"
export K8S_CLUSTER_CONF_CONTEXT_us_east="east"

Register multiple clusters:

export K8S_CLUSTER_CONF_SA_us_central=true
export K8S_CLUSTER_CONF_PATH_us_east="east.yaml"
export K8S_CLUSTER_CONF_CONTEXT_us_east="east"
export K8S_CLUSTER_CONF_PATH_us_west="west.yaml"
export K8S_CLUSTER_CONF_CONTEXT_us_west="west"

Running an operation

Many more client examples exist in the K8s.Client docs.

Creating a Deployment from a Map

resource = %{
  "apiVersion" => "apps/v1",
  "kind" => "Deployment",
  "metadata" => %{
    "labels" => %{"app" => "nginx"},
    "name" => "nginx-deployment",
    "namespace" => "default"
  },
  "spec" => %{
    "replicas" => 3,
    "selector" => %{"matchLabels" => %{"app" => "nginx"}},
    "template" => %{
      "metadata" => %{"labels" => %{"app" => "nginx"}},
      "spec" => %{
        "containers" => [
          %{
            "image" => "nginx:1.7.9",
            "name" => "nginx",
            "ports" => [%{"containerPort" => 80}]
          }
        ]
      }
    }
  }
}

operation = K8s.Client.create(resource)
{:ok, response} = K8s.Client.run(operation, :dev)

Creating a Deployment from a YAML file

Given the YAML file priv/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: <%= name %>-deployment
  namespace: <%= namespace %>
  labels:
    app: <%= name %>
spec:
  replicas: 3
  selector:
    matchLabels:
      app: <%= name %>
  template:
    metadata:
      labels:
        app: <%= name %>
    spec:
      containers:
      - name: <%= name %>
        image: <%= image %>
        ports:
        - containerPort: 80
opts = [namespace: "default", name: "nginx", image: "nginx:nginx:1.7.9"]
resource = K8s.Resource.from_file!("priv/deployment.yaml", opts)

operation = K8s.Client.create(resource)
{:ok, deployment} = K8s.Client.run(operation, :dev)

Listing Deployments

In a given namespace:

operation = K8s.Client.list("apps/v1", "Deployment", namespace: "prod")
{:ok, deployments} = K8s.Client.run(operation, :dev)

Across all namespaces:

operation = K8s.Client.list("apps/v1", "Deployment", namespace: :all)
{:ok, deployments} = K8s.Client.run(operation, :dev)

Note: K8s.Client.list will return a map. The list of resources will be under "items".

Using labelSelector with list operations

K8s.Client.list("apps/v1", :deployments)
|> K8s.Selector.label({"app", "nginx"})
|> K8s.Selector.label_in({"environment", ["qa", "prod"]})
|> K8s.Client.run(:default)

Getting a Deployment

operation = K8s.Client.get("apps/v1", :deployment, [namespace: "default", name: "nginx-deployment"])
{:ok, deployment} = K8s.Client.run(operation, :dev)

Watch Operations

operation = K8s.Client.list("apps/v1", :deployment, namespace: :all)
{:ok, reference} = K8s.Client.watch(operation, :dev, stream_to: self())

Kubernetes Watch API added, modified, and deleted events will be streamed as they occur.

Wait on a Resource

This will wait 60 seconds for the field status.succeeded to equal 1. :find and :eval also accept functions to apply to check success.

operation = K8s.Client.get("batch/v1", :job, namespace: "default", name: "database-migrator")
wait_opts = [find: ["status", "succeeded"], eval: 1, timeout: 60]
{:ok, job} = K8s.Client.wait(op, cluster_name, wait_opts)

Batch Operations

Fetching two pods at once.

operation1 = K8s.Client.get("v1", "Pod", namespace: "default", name: "pod-1")
operation2 = K8s.Client.get("v1", "Pod", namespace: "default", name: "pod-2")

# results will be a list of :ok and :error tuples
results = K8s.Client.async([operation1, operation2], :dev)

Note: all operations are fired async and their results are returned. Processing does not halt if an error occurs for one operation.

List operations as a Elixir Streams

operation = K8s.Client.list("v1", "Pod", namespace: :all)

operation
|> K8s.Client.stream()
|> Stream.filter(&my_filter_function?/1)
|> Stream.map(&my_map_function?/1)
|> Enum.into([])

Custom Resource Definitions

Custom resources are discovered via the same mechanism as "standard" k8s resources and can be worked with as such:

Listing the Greetings from the hello operator.

operation = K8s.Client.list("hello-operator.example.com/v1", :greeting, [namespace: "default"])
{:ok, greeting} = K8s.Client.run(operation, :dev)

Multiple Clusters

Copying a workloads between two clusters:

Register a staging cluster:

staging_conf = K8s.Conf.from_file("~/.kube/config")
{:ok, staging} = K8s.Cluster.Registry.add(:staging, staging_conf)

Register a prod cluster:

prod_conf = K8s.Conf.from_service_account() # or from_file/2
{:ok, prod} = K8s.Cluster.Registry.add(:prod, staging_conf)

Get a list of all deployments in the default prod namespace:

operation = K8s.Client.list("apps/v1", :deployment, namespace: "default")
{:ok, deployments} = K8s.Client.run(operation, :prod)

Map the deployments to operations and async create on staging:

deployments
|> Enum.map(fn(deployment) -> K8s.Client.create(deployment) end)
|> K8s.Client.async(:staging)

Adding Authorization Providers

config :k8s, auth_providers: [My.Custom.Provider]

Providers are checked in order, the first to return an authorization struct wins.

Custom providers are processed before default providers.

For protocol and behavior implementation examples check out Certificate, Token, or AuthProvider here.

Performing sub-resource operations

Subresource (eviction|finalize|bindings|binding|approval|scale|status) operations are created in the same way as standard operations using K8s.Operation.build/4, K8s.Operation.build/5, or any K8s.Client function.

Getting a deployment's status:

cluster = :test
operation = K8s.Client.get("apps/v1", "deployments/status", name: "nginx", namespace: "default")
{:ok, scale} = K8s.Client.run(operation, cluster)

Getting a deployment's scale:

cluster = :test
operation = K8s.Client.get("apps/v1", "deployments/scale", [name: "nginx", namespace: "default"])
{:ok, scale} = K8s.Client.run(operation, cluster)

There are two forms for mutating subresources.

Evicting a pod with a Pod map:

cluster = :test
eviction = %{
  "apiVersion" => "policy/v1beta1",
  "kind" => "Eviction",
  "metadata" => %{
    "name" => "nginx",
  }
}

# Here we use K8s.Resource.build/4 but this k8s resource map could be built manually or retrieved from the k8s API
subject = K8s.Resource.build("v1", "Pod", "default", "nginx")
operation = K8s.Client.create(subject, eviction)
{:ok, resp} = K8s.Client.run(operation, cluster)

Evicting a pod by providing details:

cluster = :test
eviction = %{
  "apiVersion" => "policy/v1beta1",
  "kind" => "Eviction",
  "metadata" => %{
    "name" => "nginx",
  }
}

subject = K8s.Client.create("v1", "pods/eviction", [namespace: "default", name: "nginx"], eviction)
operation = K8s.Client.create(subject, eviction)
{:ok, resp} = K8s.Client.run(operation, cluster)