
Go atualmente é um monopolista entre as linguagens de programação que as pessoas escolhem para escrever declarações para o Kubernetes. Existem razões objetivas como:
- Existe uma estrutura poderosa para o desenvolvimento de operadores no Go - Operator SDK .
- O Go escreveu aplicativos invertidos como Docker e Kubernetes. Para escrever seu próprio operador no Go - fale o mesmo idioma com o ecossistema.
- Aplicativos de alto desempenho no Go e ferramentas simples para trabalhar com simultaneidade imediatamente.
NB : A propósito, já descrevemos como escrever seu próprio operador no Go em uma de nossas traduções de autores estrangeiros.Mas e se o aprendizado do Go for impedido por falta de tempo ou, trivialmente, por motivação? O artigo fornece um exemplo de como você pode escrever um operador sólido usando uma das linguagens mais populares que quase todo engenheiro do DevOps conhece -
Python .
Conheça: Copywriter - operador de cópia!
Por exemplo, considere o desenvolvimento de um operador simples projetado para copiar o ConfigMap quando um novo espaço para nome aparecer ou quando uma das duas entidades for alterada: ConfigMap e Secret. Do ponto de vista do aplicativo prático, o operador pode ser útil para a atualização em massa das configurações de aplicativos (atualizando o ConfigMap) ou para atualizar dados secretos - por exemplo, chaves para trabalhar com o Docker Registry (ao adicionar Segredo ao namespace).
Então, o
que um bom operador deve ter :
- A interação com o operador é realizada usando definições de recursos personalizadas (doravante - CRD).
- O operador pode ser personalizado. Para fazer isso, usaremos sinalizadores de linha de comando e variáveis de ambiente.
- A montagem do contêiner do Docker e o gráfico Helm está sendo elaborada para que os usuários possam instalar facilmente (literalmente com um comando) o operador em seu cluster Kubernetes.
CRD
Para que o operador saiba quais recursos e onde procurá-lo, precisamos definir uma regra para ele. Cada regra será representada como um único objeto CRD. Quais campos esse CRD deve ter?
- O tipo de recurso que estamos procurando (ConfigMap ou Secret).
- Uma lista de namespace onde os recursos devem estar localizados.
- Seletor , pelo qual procuraremos recursos no espaço para nome.
Nós descrevemos o CRD:
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: copyrator.flant.com spec: group: flant.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: copyrators singular: copyrator kind: CopyratorRule shortNames: - copyr validation: openAPIV3Schema: type: object properties: ruleType: type: string namespaces: type: array items: type: string selector: type: string
E crie imediatamente uma
regra simples - para pesquisar no espaço para nome com o nome
default
todo o ConfigMap com rótulos como
copyrator: "true"
:
apiVersion: flant.com/v1 kind: CopyratorRule metadata: name: main-rule labels: module: copyrator ruleType: configmap selector: copyrator: "true" namespace: default
Feito! Agora você precisa, de alguma forma, obter informações sobre nossa regra. Preciso fazer uma reserva imediatamente para não gravar solicitações na API do servidor de cluster. Para fazer isso, usaremos a biblioteca Python pronta
kubernetes-client :
import kubernetes from contextlib import suppress CRD_GROUP = 'flant.com' CRD_VERSION = 'v1' CRD_PLURAL = 'copyrators' def load_crd(namespace, name): client = kubernetes.client.ApiClient() custom_api = kubernetes.client.CustomObjectsApi(client) with suppress(kubernetes.client.api_client.ApiException): crd = custom_api.get_namespaced_custom_object( CRD_GROUP, CRD_VERSION, namespace, CRD_PLURAL, name, ) return {x: crd[x] for x in ('ruleType', 'selector', 'namespace')}
Como resultado desse código, obtemos o seguinte:
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}
Excelente: conseguimos obter uma regra para o operador. E o mais importante, fizemos o que é chamado de Kubernetes.
Variáveis de ambiente ou sinalizadores? Nós levamos tudo!
Passamos para a configuração principal do operador. Existem duas abordagens básicas para configurar aplicativos:
- Use opções de linha de comando
- use variáveis de ambiente.
As opções de linha de comando permitem que você leia as configurações de maneira mais flexível, com suporte e validação de tipos de dados. A biblioteca padrão do Python possui um módulo
argparser
, que usaremos. Detalhes e exemplos de seus recursos estão disponíveis na
documentação oficial .
Aqui está como será o exemplo de configuração da leitura de sinalizadores de linha de comando para o nosso caso:
parser = ArgumentParser( description='Copyrator - copy operator.', prog='copyrator' ) parser.add_argument( '--namespace', type=str, default=getenv('NAMESPACE', 'default'), help='Operator Namespace' ) parser.add_argument( '--rule-name', type=str, default=getenv('RULE_NAME', 'main-rule'), help='CRD Name' ) args = parser.parse_args()
Por outro lado, usando variáveis de ambiente no Kubernetes, você pode facilmente transferir informações de serviço sobre o pod para o contêiner. Por exemplo, podemos obter informações sobre o espaço para nome no qual o pod está sendo executado com a seguinte construção:
env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace
Lógica do Operador
Para entender como separar os métodos para trabalhar com o ConfigMap e o Secret, usaremos cartões especiais. Então podemos entender quais métodos precisamos rastrear e criar o objeto:
LIST_TYPES_MAP = { 'configmap': 'list_namespaced_config_map', 'secret': 'list_namespaced_secret', } CREATE_TYPES_MAP = { 'configmap': 'create_namespaced_config_map', 'secret': 'create_namespaced_secret', }
Em seguida, você precisa receber eventos do servidor da API. Nós o implementamos da seguinte maneira:
def handle(specs): kubernetes.config.load_incluster_config() v1 = kubernetes.client.CoreV1Api()
Após receber o evento, prosseguimos para a lógica principal de seu processamento:
A lógica básica está pronta! Agora você precisa compactar tudo isso em um pacote Python.
setup.py
, escrevemos meta-informações sobre o projeto:
from sys import version_info from setuptools import find_packages, setup if version_info[:2] < (3, 5): raise RuntimeError( 'Unsupported python version %s.' % '.'.join(version_info) ) _NAME = 'copyrator' setup( name=_NAME, version='0.0.1', packages=find_packages(), classifiers=[ 'Development Status :: 3 - Alpha', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], author='Flant', author_email='maksim.nabokikh@flant.com', include_package_data=True, install_requires=[ 'kubernetes==9.0.0', ], entry_points={ 'console_scripts': [ '{0} = {0}.cli:main'.format(_NAME), ] } )
Nota : O cliente kubernetes para Python possui seu próprio controle de versão. Você pode aprender mais sobre compatibilidade entre versões do cliente e versões do Kubernetes na matriz de compatibilidade .Agora, nosso projeto fica assim:
copyrator ├── copyrator │ ├── cli.py # │ ├── constant.py # , │ ├── load_crd.py # CRD │ └── operator.py # └── setup.py #
Docker e Elmo
O arquivo Dockerfile será escandalosamente simples: pegue a imagem básica python-alpina e instale nosso pacote. Adiaremos sua otimização para tempos melhores:
FROM python:3.7.3-alpine3.9 ADD . /app RUN pip3 install /app ENTRYPOINT ["copyrator"]
A implantação para o operador também é muito simples:
apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }} spec: selector: matchLabels: name: {{ .Chart.Name }} template: metadata: labels: name: {{ .Chart.Name }} spec: containers: - name: {{ .Chart.Name }} image: privaterepo.yourcompany.com/copyrator:latest imagePullPolicy: Always args: ["--rule-type", "main-rule"] env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace serviceAccountName: {{ .Chart.Name }}-acc
Por fim, você deve criar a função apropriada para o operador com os direitos necessários:
apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Chart.Name }}-acc --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: {{ .Chart.Name }} rules: - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["secrets", "configmaps"] verbs: ["*"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: {{ .Chart.Name }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: {{ .Chart.Name }} subjects: - kind: ServiceAccount name: {{ .Chart.Name }}-acc
Sumário
Portanto, sem medo, censura e aprendizado do Go, conseguimos montar nosso próprio operador para o Kubernetes em Python. Claro, ele ainda tem espaço para crescer: no futuro, ele poderá processar várias regras, trabalhar em vários threads, monitorar independentemente as alterações em seu CRD ...
Para dar uma olhada no código, colocamos em um
repositório público . Se você deseja exemplos de operadores mais sérios implementados usando o Python, pode voltar sua atenção para dois operadores para implantar o mongodb (o
primeiro e o
segundo ).
PS E se você tiver preguiça de lidar com os eventos do Kubernetes ou se estiver mais à vontade usando o Bash, nossos colegas prepararam uma solução pronta na forma de um
operador de shell (
anunciamos em abril).
PPS
Leia também em nosso blog: