Usage
Registering Clusters
Clusters can be registered via config.exs
or directly with K8s.Cluster.register/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.10, 1.11, 1.12, 1.13, 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.register(:prod, conf)
Registering a cluster using the k8s' ServiceAccount of the pod:
conf = K8s.Conf.from_service_account()
K8s.Cluster.register(: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 podsspec.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"
.
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 Greeting
s 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")
staging = K8s.Cluster.register(:staging, staging_conf)
Register a prod cluster:
prod_conf = K8s.Conf.from_service_account() # or from_file/2
prod = K8s.Cluster.register(: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 "unsupported" sub-resource operations
Currently sub-resource (eviction|finalize|bindings|binding|approval|scale|status) operations aren't officially supported by the K8s.Client
API and currently need to be constructed using HTTPoison directly. K8s.Cluster.url_for/2
can still be used to generate the base URL for the request.
Below is an example evict/1
function that will evict a pod from a node on the cluster registered as :default
.
def evict(%{"metadata" => %{"name" => name, "namespace" => ns}}) do
operation = K8s.Operation.build(:get, "v1", :pod, namespace: ns, name: name)
with {:ok, base_url} <- K8s.Cluster.url_for(operation, :default),
{:ok, cluster_connection_config} <- K8s.Cluster.conf(:default),
{:ok, request_options} <- K8s.Conf.RequestOptions.generate(cluster_connection_config),
{:ok, body} <- eviction_body(ns, name) do
eviction_url = "#{base_url}/eviction"
headers = request_options.headers ++ [{"Accept", "application/json"}, {"Content-Type", "application/json"}]
options = [ssl: request_options.ssl_options]
HTTPoison.post(eviction_url, body, headers, options)
end
end
defp eviction_body(ns, name) do
manifest = %{
apiVersion: "policy/v1beta1",
kind: "Eviction",
metadata: %{
name: name,
namespace: ns
}
}
Jason.encode(manifest)
end