Nota : este não é um artigo de guia completo, mas um lembrete / dica para quem já usa o ConfigMap no Kubernetes ou está apenas preparando seu aplicativo para trabalhar nele.
Histórico: do rsync ao ... Kubernetes
O que aconteceu antes? Na era da “administração clássica”, na versão mais simples, o arquivo de configuração foi colocado ao lado dos aplicativos (ou no repositório, se você preferir). É simples: fazemos entrega elementar (CD) para o nosso código junto com a configuração. Mesmo uma implementação condicional de rsync pode ser chamada de rudimentos de um CD.Quando a infraestrutura cresceu, diferentes configurações foram necessárias para diferentes ambientes (dev / stage / production). O aplicativo foi treinado para entender qual configuração usar, passando-os como argumentos para iniciar ou variáveis de ambiente definidas no ambiente. Um CD ainda mais complicado é o advento de um Chef / Marionete / Ansible tão útil. As funções aparecem nos servidores e os ambientes deixam de ser descritos em lugares diferentes - chegamos ao IaC (Infraestrutura como código).O que se seguiu? Se fosse possível ver as vantagens críticas do Kubernetes e chegar a um acordo com a necessidade de modificar aplicativos para funcionarem nesse ambiente, ocorreu a migração. No caminho, esperava muitas nuances e diferenças na construção da arquitetura, mas quando consegui lidar com a parte principal, consegui o tão esperado aplicativo em execução no K8s.Uma vez aqui, ainda podemos usar as configurações preparadas no repositório ao lado do aplicativo ou passando ENV para o contêiner. No entanto, além desses métodos, o ConfigMaps também está disponível . Essa primitiva do K8s permite que você use modelos Go em configurações, ou seja, renderize-as como páginas HTML e recarregue o aplicativo ao alterar a configuração sem reiniciar. Com o ConfigMaps, não é mais necessário manter mais de 3 configurações para diferentes ambientes e acompanhar a relevância de cada uma.Uma introdução geral ao ConfigMaps pode ser encontrada, por exemplo, aqui . E neste artigo, focarei em alguns recursos do trabalho com eles.ConfigMaps simples
Como eram as configurações no Kubernetes? O que eles obtiveram dos modelos em uso? Por exemplo, aqui está um ConfigMap comum para um aplicativo implantado a partir de um gráfico Helm:apiVersion: v1
kind: ConfigMap
metadata:
name: app
data:
config.json: |
{
"welcome": {{ pluck .Values.global.env .Values.welcome | quote }},
"name": {{ pluck .Values.global.env .Values.name | quote }}
}
Aqui, os valores substituídos .Values.welcome
e .Values.name
serão retirados do arquivo values.yaml
. Por que exatamente de values.yaml
? Como o mecanismo do modelo Go funciona? Já falamos sobre esses detalhes em mais detalhes aqui .A chamada pluck
ajuda a selecionar a linha necessária no mapa:$ cat .helm/values.yaml
welcome:
production: "Hello"
test: "Hey"
name:
production: "Bob"
test: "Mike"
Além disso, você pode pegar linhas específicas e fragmentos inteiros da configuração.Por exemplo, o ConfigMap pode ser assim:data:
config.json: |
{{ pluck .Values.global.env .Values.data | first | toJson | indent 4 }}
... e em values.yaml
- o seguinte conteúdo:data:
production:
welcome: "Hello"
name: "Bob"
O envolvido aqui global.env
é o nome do ambiente. Substituindo esse valor durante a implantação, é possível renderizar o ConfigMaps com conteúdo diferente. first
necessário aqui porque pluck
retorna uma lista, cujo primeiro elemento contém o valor desejado.Quando há muitas configurações
Um ConfigMap pode conter vários arquivos de configuração:data:
config.json: |
{
"welcome": {{ pluck .Values.global.env .Values.welcome | first | quote }},
"name": {{ pluck .Values.global.env .Values.name | first | quote }}
}
database.yml: |
host: 127.0.0.1
db: app
user: app
password: app
Você pode até montar cada configuração separadamente: volumeMounts:
- name: app-conf
mountPath: /app/configfiles/config.json
subPath: config.json
- name: app-conf
mountPath: /app/configfiles/database.yml
subPath: database.yml
... ou selecione todas as configurações de uma só vez com o diretório: volumeMounts:
- name: app-conf
mountPath: /app/configfiles
Se você alterar a descrição do recurso Deployment durante a implantação, o Kubernetes criará um novo ReplicaSet, diminuindo o antigo para 0 e aumentando o novo para o número especificado de réplicas. (Isso é verdade para a estratégia de implantação RollingUpdate
.)Essas ações levarão à recriação do pod com uma nova descrição. Por exemplo: havia uma imagem image:my-registry.example.com:v1
, mas tornou-se - image:my-registry.example.com:v2
. E não importa o que exatamente mudamos na descrição de nossa implantação: o principal é que isso fez com que a replicaSet (e, como resultado, o pod) fosse recriada. Nesse caso, a nova versão do arquivo de configuração na nova versão do aplicativo é montada automaticamente e não haverá problemas.Resposta à mudança do ConfigMap
Em caso de alterações no ConfigMap, quatro cenários de eventos podem ser seguidos. Considere-os:- : ConfigMap, subPath.
: . - : ConfigMap, pod.
: pod . - : ConfigMap Deployment -.
: , ConfigMap’, Deployment, pod , — . - : ConfigMap, .
: pod’ / pod’.
Vamos analisar com mais detalhes.Cenário 1
Nós única corrigido configMap? O aplicativo não será reiniciado. No caso de montagem por, subPath
não haverá alterações até que o pod seja reiniciado manualmente.Tudo é simples: o Kubernetes monta nosso ConfigMap em um pod de uma versão específica do recurso. Como é montado subPath
, não há mais "influência" na configuração.Cenário 2
Não é possível atualizar o arquivo sem recriar o pod? Ok, temos 6 réplicas em Implantação, para que possamos revezar manualmente fazendo tudo delete pod
. Então, ao criar novos pods, eles "capturam" a nova versão do ConfigMap.Cenário 3
Cansado de executar essas operações manualmente? Uma solução para esse problema é descrita em dicas e truques do Helm :kind: Deployment
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]
Portanto, o spec.template
hash da configuração da anotação renderizada é simplesmente gravado no modelo pod ( ).As anotações são campos arbitrários de valores-chave nos quais você pode armazenar seus valores. Se você os registrar no modelo do spec.template
pod futuro, esses campos cairão no ReplicaSet e no próprio pod. O Kubernetes notará que o modelo do pod foi alterado (desde a configuração do sha256) e será iniciado RollingUpdate
, no qual nada muda, exceto esta anotação.Como resultado, salvamos a mesma versão do aplicativo e da descrição da Implantação e basicamente acionamos a recriação do pod pela máquina - semelhante à forma como faríamos manualmente kubectl delete
, mas já "corretamente": automaticamente e com RollingUpdate
.Cenário 4
Talvez o aplicativo já saiba como monitorar alterações na configuração e recarregar automaticamente? Aqui está um recurso importante do ConfigMaps ...No Kubernetes, se a configuração estiver montada subPath
, ela não será atualizada até que o pod seja reiniciado (consulte os três primeiros cenários discutidos acima) . Mas se você montar o ConfigMap como um diretório, sem subPath
, dentro do contêiner, haverá um diretório com uma configuração atualizada sem reiniciar o pod.Existem outros recursos que são úteis para lembrar:- Um arquivo de configuração atualizado dentro do contêiner é atualizado com algum atraso. Isso ocorre porque o arquivo não está montado exatamente, mas o objeto Kubernetes.
- O arquivo dentro é um link simbólico. Exemplo com
subPath
:
$ kubectl -n production exec go-conf-example-6b4cb86569-22vqv -- ls -lha /app/configfiles
total 20K
drwxr-xr-x 1 root root 4.0K Mar 3 19:34 .
drwxr-xr-x 1 app app 4.0K Mar 3 19:34 ..
-rw-r--r-- 1 root root 42 Mar 3 19:34 config.json
-rw-r--r-- 1 root root 47 Mar 3 19:34 database.yml
E o que acontecerá sem subPath
quando montado pelo diretório?
$ kubectl -n production exec go-conf-example-67c768c6fc-ccpwl -- ls -lha /app/configfiles
total 12K
drwxrwxrwx 3 root root 4.0K Mar 3 19:40 .
drwxr-xr-x 1 app app 4.0K Mar 3 19:34 ..
drwxr-xr-x 2 root root 4.0K Mar 3 19:40 ..2020_03_03_16_40_36.675612011
lrwxrwxrwx 1 root root 31 Mar 3 19:40 ..data -> ..2020_03_03_16_40_36.675612011
lrwxrwxrwx 1 root root 18 Mar 3 19:40 config.json -> ..data/config.json
lrwxrwxrwx 1 root root 19 Mar 3 19:40 database.yml -> ..data/database.yml
Atualize a configuração (via deploy ou kubectl edit
), aguarde 2 minutos (tempo de armazenamento em cache do apiserver) - e pronto:
$ kubectl -n production exec go-conf-example-67c768c6fc-ccpwl -- ls -lha --color /app/configfiles
total 12K
drwxrwxrwx 3 root root 4.0K Mar 3 19:44 .
drwxr-xr-x 1 app app 4.0K Mar 3 19:34 ..
drwxr-xr-x 2 root root 4.0K Mar 3 19:44 ..2020_03_03_16_44_38.763148336
lrwxrwxrwx 1 root root 31 Mar 3 19:44 ..data -> ..2020_03_03_16_44_38.763148336
lrwxrwxrwx 1 root root 18 Mar 3 19:40 config.json -> ..data/config.json
lrwxrwxrwx 1 root root 19 Mar 3 19:40 database.yml -> ..data/database.yml
Observe o carimbo de data / hora alterado no diretório criado pelo Kubernetes.
Alterar rastreamento
E finalmente - um exemplo simples de como você pode monitorar as alterações na configuração.Usaremos esse aplicativo Gopackage main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/fsnotify/fsnotify"
)
type Config struct {
Welcome string `json:"welcome"`
Name string `json:"name"`
}
var (
globalConfig *Config
)
func LoadConfig(path string) (*Config, error) {
configFile, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Unable to read configuration file %s", path)
}
config := new(Config)
decoder := json.NewDecoder(configFile)
err = decoder.Decode(&config)
if err != nil {
return nil, fmt.Errorf("Unable to parse configuration file %s", path)
}
return config, nil
}
func ConfigWatcher() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
}
globalConfig, _ = LoadConfig("./configfiles/config.json")
log.Println("config:", globalConfig)
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
err = watcher.Add("./configfiles/config.json")
if err != nil {
log.Fatal(err)
}
<-done
}
func main() {
log.Println("Start")
globalConfig, _ = LoadConfig("./configfiles/config.json")
go ConfigWatcher()
for {
log.Println("config:", globalConfig)
time.Sleep(30 * time.Second)
}
}
... adicionando-o com essa configuração:$ cat configfiles/config.json
{
"welcome": "Hello",
"name": "Alice"
}
Se executado, o log será:2020/03/03 22:18:22 config: &{Hello Alice}
2020/03/03 22:18:52 config: &{Hello Alice}
E agora vamos instalar este aplicativo no Kubernetes, montando a configuração do ConfigMap no pod, em vez do arquivo da imagem. Um exemplo de gráfico Helm foi preparado no GitHub :helm install -n habr-configmap --namespace habr-configmap ./habr-configmap --set 'name.production=Alice' --set 'global.env=production'
E mude apenas o ConfigMap:- production: "Alice"
+ production: "Bob"
Atualize o gráfico Helm no cluster, por exemplo, assim:helm upgrade habr-configmap ./habr-configmap --set 'name.production=Bob' --set 'global.env=production'
O que vai acontecer?Você pode ver como uma situação semelhante - rastreando as alterações do ConfigMap - é resolvida em um projeto mais adulto (e simplesmente "real"), aqui .Importante! Também é útil lembrar que tudo o que foi dito acima também é válido para o Secret'ov no Kubernetes ( kind: Secret
): não é à toa que eles são tão semelhantes ao ConfigMap ...Bônus! Soluções de Terceiros
Se você está interessado no tópico de rastreamento de alterações nas configurações, já existem utilitários prontos para isso:- jimmidyson / configmap-reload - Envia uma solicitação HTTP se o arquivo foi alterado. O desenvolvedor também planeja ensinar o SIGHUP a enviar, mas a falta de confirmações a partir de outubro de 2019 deixa esses planos em questão;
- stakater / Reloader - monitora o ConfigMap / Secrets e executa a atualização sem interrupção (como o autor o chama) nos recursos associados a eles.
Será conveniente iniciar esses aplicativos com um contêiner lateral para aplicativos existentes. No entanto, se você conhece os recursos do Kubernetes / ConfigMap e configs para editar não "ao vivo" (através edit
), mas apenas como parte da implantação ... os recursos desses utilitários podem parecer supérfluos, ou seja, duplicar funções básicas.Conclusão
Com o advento do ConfigMap no Kubernetes, as configurações foram movidas para a próxima rodada de desenvolvimento: o uso de um mecanismo de modelo trouxe flexibilidade comparável à renderização de páginas HTML. Felizmente, essas complicações não substituíram as soluções existentes, mas se tornaram seu complemento . Portanto, para administradores (ou melhor, até desenvolvedores) que consideram novos recursos redundantes, bons arquivos antigos ainda estão disponíveis.Para aqueles que já usam o ConfigMap'ami ou apenas os observam, o artigo fornece uma breve visão geral de sua essência e nuances de uso. Se você tiver suas próprias dicas e truques sobre o assunto - ficarei feliz em ver nos comentários.PS
Leia também no nosso blog: