Kubernetes 中使用consul-template渲染配置
當前公司使用consul來實現服務發現,如Prometheue配置中的target和alertmanager註冊都採用了consul服務發現的方式,以此來靈活應對服務的變更。但對於其他服務,是否也有一個通用的方式來使用consul管理配置檔案?本文中描述如何使用consul-template來渲染配置檔案。
使用方式
consul-template
是hashicorp開發的一個模板渲染工具,它採用了Go template語法。可以將其配置為守護程式模式,watch consul服務的變動,並將變動後的服務渲染到配置檔案中。會雖然名字中帶了consul,但它還可以對 Vault和 Nomad 進行渲染。
簡單使用方式如下,首先要建立一個模板in.tpl
,在渲染時透過-template
指定模板(in.tpl)和渲染結果(out.txt):
$ consul-template -consul-addr=<consul-address>:<consul-port> -template "in.tpl:out.txt"
SSL方式
生產環境中的consul通常會啟用ssl和ACL配置,這樣在連線consul的時候需要提供CA證書和token。命令列使用方式如下:
$ consul-template -log-level debug -consul-addr==<consul-address>:<consul-port> -consul-token=<token> -consul-ssl -consul-ssl-verify=false -consul-ssl-ca-cert=<ca.crt> -template "in.tpl:out.txt"
也可以使用
-consul-ssl-ca-path
和-consul-token-file
分別指定CA證書和token檔案的路徑。
獲取ca證書
官方提供了一個名為consul-k8s的工具來獲取consul的CA證書,並提供了容器映象,使用方式如下:
$ consul-k8s-control-plane get-consul-client-ca -output-file=/tmp/tls.crt -server-addr=<consul-address> -server-port=<consul-port>
可以將consul-k8s
配置為一個initcontainer,這樣在consul-template
啟動之前就可以獲取到ca證書:
initContainers:
- command:
- /bin/sh
- '-ec'
- |
consul-k8s-control-plane get-consul-client-ca \
-output-file=/consul/tls/client/ca/tls.crt \
-server-addr=<consul-address> \
-server-port=<consul-port> \
image: docker pull hashicorp/consul-k8s-control-plane:0.36.0
imagePullPolicy: IfNotPresent
name: get-auto-encrypt-client-ca
resources:
limits:
cpu: 50m
memory: 50Mi
requests:
cpu: 50m
memory: 50Mi
volumeMounts:
- mountPath: /consul/tls/client/ca
name: consul-auto-encrypt-ca-cert
volumes:
- emptyDir:
medium: Memory
name: consul-auto-encrypt-ca-cert
獲取token
連線consul所使用的token可以以secret的形式部署在kubernetes叢集中,可以透過vault注入等方式來避免token洩露。
整個處理方式如下圖所示:
配置檔案方式
上面透過命令列的方式(-template "in.tpl:out.txt"
)指定了模版和渲染結果,但這種方式只適用於渲染單個模板,如果需要渲染多個模板,可以採用配置檔案的方式。
配置檔案語法採用的是hcl,包含三部分:服務端(Consul、Vault和Nomad)、Templates和Modes,以及可選欄位。服務端主要用於配置到服務端(Consul、Vault和Nomad)的連線;Templates可以指定多個模板(source)和渲染結果(destination);Modes用於配置consul-template的執行模式,透過once mode可以配置為非守護程式模式,透過exec mode可以啟動額外的子程式。Modes欄位可選,預設是守護程式模式。
配置檔案的例子如下:
consul {
address = "127.0.0.1:8500"
auth {
enabled = true
username = "test"
password = "test"
}
}
log_level = "warn"
template {
contents = "{{key \"hello\"}}"
destination = "out.txt"
exec {
command = "cat out.txt"
}
}
後續就可以透過consul-template -config <config>
的方式執行。
編寫模板
consul-template使用的Go template的語法,除此之外,它還提供了豐富的內建方法,用於支援Consul(文章中搜尋關鍵字Query Consul
)、Vault(文章中搜尋關鍵字Query Vault
)和Nomad(文章中搜尋關鍵字Query Nomad
),以及一些公共函式(如trim、regexMatch、replaceAll等)。
模板語法中比較重要的兩點:
- 在模板文字中,一切動態的內容和判斷程式碼塊均使用
{{
和}}
包括起來,在{{
和}}
之外的文字均會被原封不動地複製到輸出中。 - 為了方便格式化模板原始碼,還額外提供了
{{-
和-}}
兩種語法,可以將程式碼塊前或程式碼塊後的空白字元均移除。空白字元包括空格符、換行符、回車符、水平製表符。
舉例
下面是logstash的output配置,用於將logstash處理的訊息傳送到elasticsearch.hosts
中。如果hosts
中的節點發生變動(如擴縮容),此時就需要聯動修改logstash的配置:
output {
elasticsearch {
hosts => ['dev-logging-elkclient000001.local:9200', 'dev-logging-elkclient000002.local:9200', 'elkclient000003.local:9200']
index => "logstash-infra-%{es_index}"
resurrect_delay => 2
retry_max_interval => 30
sniffing => false
action => "create"
}
}
為了避免上述修改,可以透過consul-template對該配置進行渲染(當然前提是hosts
中的節點都已經註冊到了Consul中)。
{{- $nodes := "" }}
{{ range service "elasticsearch" }} {{- $node := .Node }} {{- $port := .Port }}
{{ if $node | regexMatch "(dev|prd)-logging-elkclient.*" }}
{{ if eq $nodes "" }}
{{$nodes = (printf "'%s:%d'" $node $port)}}
{{else}}
{{$nodes = (printf "%s,'%s:%d'" $nodes $node $port)}}
{{- end -}}
{{- end -}}
{{- end }}
output {
elasticsearch {
hosts => [{{$nodes}}]
index => "logstash-%{es_index}"
resurrect_delay => 2
retry_max_interval => 30
sniffing => false
action => "create"
}
}
- 首先定義一個變數
$nodes
,用於儲存最終的結果 - 遍歷consul的
service
elasticsearch
,獲取Node
欄位(如dev-logging-elkclient000001.local
)和Port
欄位(本例中只有9200
) - 透過內建方法
regexMatch
從elasticsearch
的節點中過濾所需的節點 - 透過
printf
方法拼接字串,並將結果儲存到$nodes
中 - 最後在
output.elasticsearch.hosts
中使用上面的結果$nodes
即可,由於$nodes
是動態輸出,因此需要加上雙大括號{{$nodes}}
參考
Tips
-
有時候一個檔案因為要經過多個服務的渲染而新增了多個模板,例如先使用vault注入secrets,再使用consul注入services。這樣就會導致vault在處理時候會嘗試解析consul的模板,但由於vault缺少連線consul所需的配置,會導致vault一直嘗試連線consul。可以透過將其他服務的模版作為raw string的方式規避該問題,這樣在vault解析模板的時候就會輸出consul的模板:
{{- $consulTemplate := ` {{- $nodes := "" }} {{ range service "elasticsearch" }} {{- $node := .Node }} {{- $port := .Port }} {{ if $node | regexMatch "(dev|prd)-logging-elkclient.*" }} {{ if eq $nodes "" }} {{$nodes = (printf "'%s:%d'" $node $port)}} {{else}} {{$nodes = (printf "%s,'%s:%d'" $nodes $node $port)}} {{- end -}} {{- end -}} {{- end }} output { elasticsearch { hosts => [{{$nodes}}] index => "logstash-%{es_index}" resurrect_delay => 2 retry_max_interval => 30 sniffing => false action => "create" } } ` }} {{ $consulTemplate }}