Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Add `kubernetes_use_cached_resources` option to Kubernetes strategy
- Add `kubernetes_field_selector` option to `Cluster.Strategy.Kubernetes` to enable filtering by pod status

## 3.4.1

Expand Down
39 changes: 32 additions & 7 deletions lib/strategy/kubernetes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule Cluster.Strategy.Kubernetes do
This clustering strategy works by fetching information of endpoints or pods, which are filtered by
given Kubernetes namespace and label.

> #### Note {: .info}
>
> This strategy requires a service account with the ability to list endpoints or pods. If you want
> to avoid that, you could use one of the DNS-based strategies instead.
>
Expand All @@ -22,6 +24,7 @@ defmodule Cluster.Strategy.Kubernetes do
+ `<basename>` would be the value configured by `:kubernetes_node_basename` option.
+ `<ip_or_domain>` would be the value which is controlled by following options:
- `:kubernetes_namespace`
- `:kubernetes_field_selector`
- `:kubernetes_selector`
- `:kubernetes_service_name`
- `:kubernetes_ip_lookup_mode`
Expand All @@ -37,13 +40,32 @@ defmodule Cluster.Strategy.Kubernetes do

## Getting `<ip_or_domain>`

### `:kubernetes_namespace` and `:kubernetes_selector` option
This strategy uses the Kubernetes API to fetch information about endpoints or pods. The
following options configure the API request and how the responses are used.

### `:kubernetes_namespace` option

This option is used to filter endpoints or pods by namespace. It is required in cases when the
namespace is not determined by the service account. If not provided, it defaults to the
namespace of the service account from `\#{config[:kubernetes_service_account_path]}/namespace`.

### `:kubernetes_selector` option

This option is a **label selector** used to filter endpoints or pods by label. It is
**required** and should be provided in the format of a label selector, such as
`"app.kubernetes.io/name=my-app"`. For more information on label selectors, see the
[Kubernetes Documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#list-and-watch-filtering).

### `:kubernetes_field_selector` option

These two options configure how to filter required endpoints or pods.
This option is a **field selector** used to filter endpoints or pods by specific fields. It is
optional and can be used to filter pods by their status, such as `"status.phase=Running"`.
If not provided, no filters are applied. For more information on field selectors, see the
[Kubernetes Documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/).

### `:kubernetes_ip_lookup_mode` option

These option configures where to lookup the required IP.
This option configures where to lookup the required IP.

Available values:

Expand Down Expand Up @@ -226,6 +248,7 @@ defmodule Cluster.Strategy.Kubernetes do
mode: :ip,
kubernetes_node_basename: "myapp",
kubernetes_selector: "app=myapp",
kubernetes_field_selector: "status.phase=Running",
kubernetes_namespace: "my_namespace",
polling_interval: 10_000
]
Expand Down Expand Up @@ -365,7 +388,8 @@ defmodule Cluster.Strategy.Kubernetes do
app_name = Keyword.fetch!(config, :kubernetes_node_basename)
cluster_name = Keyword.get(config, :kubernetes_cluster_name, "cluster")
service_name = Keyword.get(config, :kubernetes_service_name)
selector = Keyword.fetch!(config, :kubernetes_selector)
field_selector = Keyword.get(config, :kubernetes_field_selector)
label_selector = Keyword.fetch!(config, :kubernetes_selector)
ip_lookup_mode = Keyword.get(config, :kubernetes_ip_lookup_mode, :endpoints)

use_cache = Keyword.get(config, :kubernetes_use_cached_resources, false)
Expand All @@ -388,10 +412,11 @@ defmodule Cluster.Strategy.Kubernetes do
end

cond do
app_name != nil and selector != nil ->
app_name != nil and label_selector != nil ->
query_params =
[]
|> apply_param(:labelSelector, selector)
|> apply_param(:fieldSelector, field_selector)
|> apply_param(:labelSelector, label_selector)
|> apply_param(:resourceVersion, resource_version)
|> URI.encode_query(:rfc3986)

Expand Down Expand Up @@ -442,7 +467,7 @@ defmodule Cluster.Strategy.Kubernetes do

[]

selector == nil ->
label_selector == nil ->
warn(
topology,
"kubernetes strategy is selected, but :kubernetes_selector is not configured!"
Expand Down
34 changes: 33 additions & 1 deletion test/fixtures/vcr_cassettes/kubernetes_pods.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,38 @@
"type": "ok"
}
},
{
"request": {
"body": "",
"headers": {
"authorization": "***"
},
"method": "get",
"options": {
"httpc_options": [],
"http_options": {
"ssl": "[verify: :verify_none]"
}
},
"request_body": "",
"url": "https://cluster.localhost./api/v1/namespaces/__libcluster_test/pods?labelSelector=app=test_selector&fieldSelector=status.phase%3DRunning"
},
"response": {
"binary": false,
"body": "{\"kind\":\"PodList\",\"apiVersion\":\"v1\",\"metadata\":{\"selfLink\":\"SELFLINK_PLACEHOLDER\",\"resourceVersion\":\"17042410\"},\"items\":[{\"metadata\":{\"name\":\"development-development\",\"namespace\":\"airatel-service-localization\",\"selfLink\":\"SELFLINK_PLACEHOLDER\",\"uid\":\"7e3faf1e-0294-11e8-bcad-42010a9c01cc\",\"resourceVersion\":\"17037787\",\"creationTimestamp\":\"2018-01-26T12:29:03Z\",\"labels\":{\"app\":\"development\",\"chart\":\"CHART_PLACEHOLDER\"}},\"spec\": { \"hostname\": \"my-hostname-0\" },\"status\":{\"podIP\": \"10.48.33.137\"}}]}\n",
"headers": {
"date": "Fri, 26 Jan 2018 13:18:46 GMT",
"content-length": "877",
"content-type": "application/json"
},
"status_code": [
"HTTP/1.1",
200,
"OK"
],
"type": "ok"
}
},
{
"request": {
"body": "",
Expand Down Expand Up @@ -63,4 +95,4 @@
"type": "ok"
}
}
]
]
28 changes: 28 additions & 0 deletions test/kubernetes_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -312,5 +312,33 @@ defmodule Cluster.Strategy.KubernetesTest do
end)
end
end

test "works with pods and field selector" do
use_cassette "kubernetes_pods", custom: true do
capture_log(fn ->
start_supervised!({Kubernetes,
[
%Cluster.Strategy.State{
topology: :name,
config: [
kubernetes_node_basename: "test_basename",
kubernetes_selector: "app=test_selector",
# If you want to run the test freshly, you'll need to create a DNS Entry
kubernetes_master: "cluster.localhost.",
kubernetes_ip_lookup_mode: :pods,
kubernetes_field_selector: "status.phase=Running",
kubernetes_service_account_path:
Path.join([__DIR__, "fixtures", "kubernetes", "service_account"])
],
connect: {Nodes, :connect, [self()]},
disconnect: {Nodes, :disconnect, [self()]},
list_nodes: {Nodes, :list_nodes, [[]]}
}
]})

assert_receive {:connect, :"[email protected]"}, 5_000
end)
end
end
end
end