Essa documentação utiliza princípios de programação letrada. Consequentemente, alguns snippets que são descritos abaixo então contidos em scripts extraídos da documentação. O nome do script que contém o snippet respectivo está em um comentário na primeira linha do snippet.
Todos os scripts e comandos devem ser executados como usuário raiz. Recomenda-se que se utilize uma máquina exterior ao cluster para fazer a instalação.
O login de usuário root via SSH deve estar habilitado também.
A clonagem das VMs é feita através do ProxMox manualmente. Para clonar uma VM utilize o template chamado “rocky9vm”; clique com o botão direito do mouse na VM e clique em “clone”. Ajuste as configurações como quiser, mas recomenda-se a utilização de modo “Linked Clone”. Uma VM gerada com essa configuração requer menos espaço em disco, mas não pode executar sem acesso à VM template base utilizida, i.e., “rocky9vm”. Após a clonagem você terá uma nova VM para ser utilizada.
Para termos a conveniência de logarmos na máquina sem introduzir senha toda vez, devemos criar chaves assimétricas para acesso sem senha. Esse passo deve ser feito manualmente.
# A variável HOSTS deve conter os IPs dos hosts da instalação do cluster
# separados por um espaço.
# Exemplo: HOSTS=(10.20.1.123 10.20.1.124)
HOSTS=()
# Gera uma chave RSA
# Explicação para as bandeiras para o programa ssh-keygen
# -q: modo silencioso
# -t: especifica o tipo de chave a ser criada
# -b: especifica o número de bits na chave a ser criada
# -f: especifica o nome do arquivo do arquivo da chave
# -P: especifica a senha antiga
ssh-keygen -q -t rsa -b 4096 -f ~/.ssh/id_rsa -P ""
# Copia a chave pública correspondente para os hospedeiros remotos
for HOST in "${HOSTS[@]}"; do ssh-copy-id $USER@$HOST; done
Para executar e instalar o Ansible, precisamos do Python e do Pip no host de onde partirá a instalação do cluster Kubernete. O seguinte snippet instala Python e Pip em sistemas baseados em RPM. Caso o sistema ponto de partida não seja baseado em RPM, deve-se utilizar os comandos cabíveis.
# ./deps-install.sh
sudo dnf install python39 python3-pip -y
pip3 install --upgrade pip
Caso a instalação esteja partindo de uma máquina exterior ao cluster, pode-se utilizar o seguinte script integralmente; mas se a instalação estiver partindo de um dos nós do cluster, deve-se omitir a instalação do ambiente virtual Python.
O script abaixo instala as dependências Python do Kubespray no diretório ./kubespray-venv na raíz desse projeto. Segundo a documentação do Python, esses ambientes virtuais servem para resolver problemas de incompatibilidade entre versões de um mesmo software requerido em versões diversas. Em alguns dos scripts/snippets que seguem, utilizaremos essas dependências por conveniência.
Caso seu sistema não possua os utilitários “wget” e “tar”, instale-os:
dnf install -y wget tar
# ./kubespray-install.sh
if [ ! -d ./kubespray-2.22.0 ]; then
wget https://github.com/kubernetes-sigs/kubespray/archive/refs/tags/v2.22.0.tar.gz
tar -xf v2.22.0.tar.gz
rm v2.22.0.tar.gz
fi
EXTERNAL=""
read -p "A instalação do cluster parte de uma máquina externa ao cluster? (y/N)" EXTERNAL
if [ $EXTERNAL = "y" ]; then
pip install virtualenv
VENVDIR=kubespray-venv
KUBESPRAYDIR=kubespray-2.22.0
ANSIBLE_VERSION=2.12
virtualenv --python=$(which python3) $VENVDIR
source $VENVDIR/bin/activate
cd $KUBESPRAYDIR
pip install -U -r requirements-$ANSIBLE_VERSION.txt
elif [ $EXTERNAL = "N" ]; then
cd kubespray-2.22.0
pip install -U -r requirements.txt
else
echo "Entrada inválida"
fi
Caso julgue necessário, atualize os sistemas operacionais. Esse script depende da existência dos arquivos “inventory.ini” e “update-systems-playbook.yaml” que são providos nesse repositório.
Esse é playbook Ansible que especifica a tarefa para atualizar os pacotes do sistema operacional alvo para as versões mais recentes da distribuição. Não precisa ser modificado.
- name: Atualiza sistema
hosts: server*
become: true
tasks:
- name: Atualiza sistema
package:
name: '*'
state: latest
Esse é o inventário contendo configuração para acesso aos hosts. Esse arquivo deve ser atualizado com os IPs dos hosts do cluster manualmente.
server1 ansible_host=10.20.1.113
server2 ansible_host=10.20.1.115
Esse é o mini-roteiro a ser utilizado para aplicar o YAML anterior.
# ./update-systems.sh
(
# Se o diretório ./kubespray-venv existe, assume que
# a instalação parte de uma máquina exterior ao cluster
# e carrega as variáveis do ambiente virtual para o sub-shell
if [ -d ./kubespray-venv ]; then
source ./kubespray-venv/bin/activate
fi
ansible-playbook -i inventory.ini update-systems-playbook.yaml --become --become-user=root
)
Caso os firewalls não tenham sido desabilitados, desabilitá-los para simplificar a instalação. É recomendável que após a instalação, faça-se uma configuração adequada do firewall.
Esse é o playbook Ansible utilizado para instruir o Ansible para executar os comandos para parar e desabilitar o serviço de firewall nas máquinas alvo.
Esse playbook utiliza o inventário mencionado na seção anterior, portanto ele deve estar atualizado com os IPs das máquianas alvo.
- name: Remove firewall e habilitar login de root via SSH
hosts: server*
become: true
tasks:
- name: Remove firewall
shell: |
systemctl stop firewalld.service
systemctl disable firewalld.service
Esse é o snippet para aplicar a configuração.
# ./remove-firewall.sh
(
# Se o diretório ./kubespray-venv existe, assuma que
# a instalação parte de uma máquina exterior ao cluster
# e carregue as variáveis do ambiente virtual no sub-shell
if [ -d ./kubespray-venv ]; then
source ./kubespray-venv/bin/activate
fi
ansible-playbook -i inventory.ini remove-firewall-playbook.yaml --become --become-user=root
)
A instalação do Kubernetes pode ser feita seguindo o seguinte snippet. Mas há algumas ressalvas. A instalação utilizando ambientes virtuais do Python quando a instalação é feita a partir de uma máquina que será um nó do cluster apresenta erros na busca de dependências do python. Mas como caso a máquina deva ser parte do cluster esse roteiro instrui para não utilizar ambientes virtuais do Python para instalar as dependências do Kubespray, esse problema já foi contornado.
Os seguintes passos, que são os mais importantes, devem ser executados manualmente:
# Na raíz do projeto kubespray.
cd kubespray-2.22.0
cp -rfp inventory/sample inventory/mycluster
# IPS é um vetor contendo os IPs dos hosts do cluster.
declare -a IPS=()
# A seguinte linha deve ser executada somente se a máquina de onde parte
# a instalação for externa ao cluster.
source ../kubespray-venv/bin/activate
# Esse script gera o inventário automaticamente com configuração padrão.
CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
# Nesse ponto pode-se revisar e modificar as variáveis em
# inventory/mycluster/group_vars/all/all.yml e
# inventory/mycluster/group_vars/k8s_cluster/k8s_cluster.yml.
# Para limpar um cluster velho, executar como root:
ansible-playbook -i inventory/mycluster/hosts.yaml --become --become-user=root reset.yml
# Para fazer uma nova instalação do kubernetes, executar como root:
ansible-playbook -i inventory/mycluster/hosts.yaml --become --become-user=root cluster.yml
Mais detalhes são documentados no repositório oficial do Kubespray.
Para mais detalhes sobre conceitos do Kubernetes, vide a documentação oficial.
Os arquivos de manifesto YAML, que utilizaremos a seguir, são extensos e seus campos difíceis de se entender pelos seus nomes. Para fazer consultas rápidas e singelas sobre os campos dos manifestos, pode-se utilizar o seguinte comando em um nó master:
kubectl explain <COMPONENT>
Por exemplo:
kubectl explain deployment
kubectl explain pod
kubectl explain pod.spec
kubectl explain pod.spec.tolerations
Na medida do possível, quando parecer cabível, deixarei uma breve explicação da terminologia.
Terminologia:
- Node
- Máquinas físicas ou virtuais que executam Pods.
- Pod
- Um Pod é um grupo de um ou mais contêineres, com armazenamento e recursos de rede compartilhados, e uma especificação de como executar os contêineres.
- Deployment
- Componente que fornece atualizações declarativas para Pods e ReplicaSets. O propósito de um ReplicaSet é manter um conjunto estável de réplicas de Pods executando em um dado momento. O ReplicaSet cumpre o seu propósito criando e deletando Pods quando necessário para atingir o número desejado de Pods. O Deployment cria ReplicaSets que criam Pods replicados.
- Service
- É um método para expor uma aplicação de rede que está sendo executado como um ou mais Pods no cluster. É necessário porque apesar de um IP ser atribuído a um Pod pelos plugins de rede nativos do Kubernetes, Pods são componentes efêmeros, podendo ser destruídos e reconstruídos por Deployments, o que pode modificar seus IPs. Services servem para atrelar um IP fixo de acesso aos serviços nos Pods.
- Toleration e Taint
- “Tolerations” e “Taints” são termos relacionados ao conceito de afinidade de nó, que é uma propriedade de Pods que os “atrai” para um conjunto de nós, seja como preferência ou exclusividade. Taints são propriedades de Pods que os repelem de um conjunto de nós. Tolerations especificam condições de tolerância para a permanência de um Pod em um nó. Pode-se especificar condições de memória, processamento ou rede. Quando essas condições de tolerância são verificadas, o Pod possuindo a tolerância é reagendado para outro nó.
Os passos utilizados para instalação do Wordpress e MySQL consistem na aplicação de um deployment e de um service para cada componente. Cada deployment possuirá também uma configuração para utilizar um servidor NFS como armazenamento persistente, com o intuito de preservar a configuração das aplicações e dos dados do banco de dados entre possíveis deployments (ex.: quando seu nó é desligado ou cai). Também possuirá uma configuração que especifica uma toleration, nesse caso, um espaço de tempo que um container permanecerá atrelado a um nó enquanto uma taint for verificada, por exemplo, quando a taint not-ready estiver verificada quando o nó estiver fora do ar.
Para mais detalhes sobre o que esses termos significam, vide a documentação referenciada na seção anterior ou utilize os comando
kubectl explain <COMPONENT>
# Exemplo
kubectl explain pod.spec.tolerations
em um nó master para visualizar uma explicação sobre algum componente.
Nesse guia utilizaremos um servidor NFS exterior ao cluster Kubernetes. Dedique uma VM para hospedar esse servidor. Para levantar o serviço NFS no host onde o servidor NFS será hospedado, execute, como raiz:
# Como raiz.
dnf install nfs-utils -y
mkdir /var/nfs/general -p
touch /etc/exports
# Colocar IPs dos workers no vetor HOSTS.
# Exemplo: HOSTS=(10.20.1.113 10.20.1.118)
# Exportamos a variável para podermos utilizar essa variável em outros momentos, caso cabível.
export HOSTS=()
# O seguinte laço estabelece o ponto de montagem com permissões e configurações para o IP respectivo.
# rw: permissões de leitura e escrita
# no_subtree_check: desativa checagens que o servidor faz para ter certeza de que o cliente está acessando
# um arquivo/diretório dentro do diretório exportado. Melhora performance.
# no_root_squash: permite que o cliente root leia e escreva arquivos como usuário root
for i in "${HOSTS[@]}"; do echo "/var/nfs/general $i(rw,no_subtree_check,no_root_squash)" >> /etc/exports;done
systemctl enable nfs-server
systemctl start nfs-server
# Esse comando deve ser executado toda vez que o arquivo /etc/exports
# for modificado.
exportfs -ra
# Os hosts clientes também precisam do pacote nfs-utils, caso não estejam instalados
# então instalamos ele:
for i in "${HOSTS[@]}"; do ssh $USER@$i "dnf install nfs-utils -y";done
Caso o servidor NFS já exista, deve-se executar apenas os seguintes comandos no servidor:
# Modificar manualemente o arquivo /etc/exports
# ou então executar o seguinte snippet.
HOSTS=() # Colocar IPs dos workers no vetor HOSTS.
for i in "${HOSTS[@]}"; do echo "/var/nfs/general $i(rw,no_subtree_check,no_root_squash)" >> /etc/exports;done
exportfs -ra
Os comandos acima especificam o diretório a ser montado nos clientes, os IPS dos clientes e configurações por IP.
Deve-se também criar pastas específicas de cada aplicação no diretório var/nfs/general (ex.: /var/nfs/general/mysql-igor) e deixá-las com permissão 777 para evitar erros de permissão e também com usuário e grupo nobody. Os comandos são esses, por exemplo:
chmod -R 777 /var/nfs/general/mysql-igor
chown -R nobody:nobody /var/nfs/general/mysql-igor
Copie os arquivos de configuração mysql-dep.yml, mysql-serv.yml, wordpress-dep.yml e wordpress-serv.yml para um master do cluster utilizando o comando, na raíz do projeto:
scp wordpress/*.yml root@<HOST-IP>
Onde HOST-IP é o IP de um dos control_planes do cluster.
Para fazer troubleshooting, visualizar logs e informações sobre as ações do kubernetes pode-se utilizar esses comandos:
# Lista deployments
kubectl get deployments -o wide
# Lista pods
kubectl get pods -o wide
# Lista serviços
kubectl get svc -o wide
# Visualiza detalhes sobre um recurso ou grupo de recursos específico
kubectl describe deployments
kubectl describe deployment <DEPLOYMENT_NAME>
# Visualiza logs emitidos por um pod
kubectl logs --follow <POD_NAME>
# Para ver os detalhes de todos os comandos possívels
kubectl --help | less
Para mais detalhes sobre as possibilidades de comandos, vide o cheat sheet oficial.
Logado em um dos master nodes (control_planes) modificar o seguinte arquivo de configuração para servir suas necessidades, como o caminho para o diretório dos arquivos da aplicação no servidor NFS.
No seguinte arquivo de configuração instruímos o Deployment para gerar 1 réplica de Pod identificado pela Label ‘app=mysql’. O campo ‘spec’ mais aninhado especifica a imagem a ser utilizada pelo contêiner, as variáveis de ambiente para o contêiner, a porta do serviço do contêiner e os pontos de montagem de volume. O campo ‘volumes’ especifica o tipo de volume que utilizaremos, nesse caso, um servidor NFS. O campo ‘tolerations’ especifica condições para o Pod ser reagendado para outro nó. Nesse caso, quando o nó apresenta as condições ‘not-ready’ ou ‘unreachable’, o Pod deve ser reagendado, observando uma tolerância de 30 segundos para essas condições.
Para mais detalhes sobre o significado desses campos de manifesto, vide o comando
kubectl explain <COMPONENT>
# Exemplos
kubectl explain deployment
kubectl explain pod
kubectl explain pod.spec.tolerations
# Arquivo: ./wordpress/mysql-dep.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:latest
env:
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
volumeMounts:
- name: nfs-volume
mountPath: /var/lib/mysql
volumes:
- name: nfs-volume
nfs:
server: 10.20.1.111
path: /var/nfs/general/mysql-igor
readOnly: no
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 30
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 30
Depois execute o seguinte comando para levantar o deployment do MySQL.
kubectl apply -f mysql-dep.yml
O seguinte arquivo configura o serviço para o MySQL. Caso queira, pode modificar a porta de acesso externo serviço do Pod modificando o campo “targetPort”.
A seguinte configuração especifica um Service para expor os recursos do servidor MySQL para outros Pods no cluster. A identificação do Pod é feito pelo campo ‘selector’, que aponta para o objeto com label ‘app=mysql’. O campo ‘ports’ especifica a porta de entrada para o serviço do Pod. O ‘port’ é a porta utilizada pelos clientes, como outros Pods no cluster, e ‘targetPort’ é a porta alvo, a porta onde o Pod exposto está de fato escutando.
# Arquivo: ./wordpress/mysql-serv.yml
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql
ports:
- protocol: TCP
port: 3306
targetPort: 3306
Utilize o seguinte comando para aplicar a configuração do serviço MySQL.
kubectl apply -f mysql-serv.yml
Novamente, revise o seguinte arquivo de configuração do Deployment para o Wordpress e modifique os campos que forem necessários, como o para os arquivos específicos do Wordpress no servidor NFS.
# Arquivo: ./wordpress/wordpress-dep.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-deployment
spec:
replicas: 1
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:latest
env:
- name: WORDPRESS_DB_HOST
value: mysql-service
- name: WORDPRESS_DB_USER
value: root
- name: WORDPRESS_DB_PASSWORD
value: password
- name: WORDPRESS_DB_NAME
value: wordpress
ports:
- containerPort: 80
volumeMounts:
- name: nfs-volume
mountPath: /var/www/html
volumes:
- name: nfs-volume
nfs:
server: 10.20.1.111
path: /var/nfs/general/wordpress-igor
readOnly: no
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 30
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 30
Utilize o seguinte comando para aplicar o deployment do Wordpress.
kubectl apply -f wordpress-dep.yml
Revise o arquivo de configuração do serviço Wordpress e modifique os campos que achar necessário.
O campo ‘nodePort’ no campo ‘ports’ especifica a porta pela qual clientes fora do cluster podem acessar o serviço exposto pelo Pod do Wordpress.
# Arquivo: ./wordpress/wordpress-serv.yml
apiVersion: v1
kind: Service
metadata:
name: wordpress-service
spec:
selector:
app: wordpress
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30036
O seguinte comando aplica a configuração do serviço Wordpress.
kubectl apply -f wordpress-serv.yml
Para que o Wordpress funcione precisamos criar, manualmente, um banco de dados chamado ‘wordpress’. Para isso, logamos no container do MySQL e utilizamos o utilitário ‘mysql’ para emitir o comando de criação do banco de dados.
A partir de control_plane logar no container:
# Para resgatar o nome do Pod do MySQL
kubectl get pods
# Para logar no contêiner no Pod
kubectl exec -it <MYSQL_POD_NAME> -- bash
mysql -u root -p
# no prompt do shell do mysql:
create database wordpress;
exit
exit
Agora pode visitar qualquer endereço pertencente ao cluster Kubernetes utilizando a porta especificada no Deployment para o Wordpress para visualizar a tela principal da interface Web do Wordpress.
Revise o arquivo de configuração para o serviço do Gitlab e edite o que achar necessário.
# Arquivo: ./gitlab/gitlab-serv
apiVersion: v1
kind: Service
metadata:
name: gitlab-service
spec:
selector:
app: gitlab
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30036
Execute o seguinte comando para aplicar a configuração:
kubectl apply -f gitlab-serv.yml
Revise o arquivo de configuração do deployment para o Gitlab e edite os campos necessários, como os caminhos nos volumes “gitlab-data”, “gitlab-logs” e “gitlab-config” para servir a sua configuração. Vale ressaltar que os caminhos para esses volumes devem ser diferentes. A variável de ambiente GITLAB_OMNIBUS_CONFIG deve ter como valor o IP no campo “cluster-ip” da saída do comando
kubectl get svc -o wide
para o serviço do Gitlab que foi aplicado na seção anterior. Esse endereço será utilizado pelo GitLab Runner para clonar seus repositórios durante uma pipeline CI/CD.
# Arquivo: ./gitlab/gitlab-dep.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitlab-deployment
spec:
replicas: 1
selector:
matchLabels:
app: gitlab
template:
metadata:
labels:
app: gitlab
spec:
containers:
- name: gitlab
image: gitlab/gitlab-ce:latest
env:
- name: GITLAB_OMNIBUS_CONFIG
value: |
external_url 'http://localhost'
# O endereco external_url deve ser o IP apontado pelo campo cluster-ip na saída do comando kubectl get svc -o wide
ports:
- containerPort: 80
volumeMounts:
- name: gitlab-data
mountPath: /var/opt/gitlab
- name: gitlab-logs
mountPath: /var/log/gitlab
- name: gitlab-config
mountPath: /etc/gitlab
volumes:
- name: gitlab-data
nfs:
server: 10.20.9.111
path: /var/nfs/general/gitlab-igor/data
readOnly: no
- name: gitlab-logs
nfs:
server: 10.20.9.111
path: /var/nfs/general/gitlab-igor/logs
readOnly: no
- name: gitlab-config
nfs:
server: 10.20.9.111
path: /var/nfs/general/gitlab-igor/config
readOnly: no
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 30
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 30
Execute o seguinte comando para aplicar a configuração do deployment:
kubectl apply -f gitlab-dep.yml
Para configurar uma senha para o usuário raíz:
kubectl exec -it <GITLAB_POD_NAME> -- /bin/bash -c "gitlab-rake 'gitlab:password:reset[root]'"
kubectl exec -it <GITLAB_POD_NAME> -- /bin/bash -c "gitlab-ctl reconfigure"
Antes de de fato instalarmos o Runner do Gitlab, precisamos registrá-lo no Gitlab. Para isso, instalamos o Runner num container Docker apenas para registrá-lo no Gitlab. Depois descartamos o container e instalamos outro Runner utilizando kubectl e um manifesto e utilizamos a configuração do outro Runner, modificada, para configurar o novo Runner.
# ./install-docker.sh
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf upgrade --refresh -y
dnf install docker-ce docker-ce-cli containerd.io
systemctl start docker
docker run -it --entrypoint /bin/bash gitlab/gitlab-runner:latest
Crie um repositório teste na sua instância do Gitlab. Acesse o repositório teste e na tela do repositório vá em Settings -> CI/CD -> Runners e siga as instruções para registrar um novo runner. O runner no nosso caso deve utilizar a plataforma Linux e deve ser configurado para executar trabalhados sem tag acionando o checkbox “Run untagged jobs”.
Ao adicionar o Runner você pode encontrar um erro de conexão. Isso porque o Gitlab utiliza o URL armazenado na variável de ambiente GITLAB_OMNIBUS_CONFIG, que aponta para o IP fixo de cluster do Gitlab, que não é o IP que utilizamos para expor o serviço do Gitlab para fora do cluster, i.e., o IP de algum dos nós do cluster. Modificar o IP armazenado nessa variável de ambiente para apontar para algum nó do cluster resulta em erro ao tentar acessar a interface Web do Gitlab.
Como workaround temporário, você pode simplesmente colocar na URL da página de erro o IP e a porta adequados para acessar o Gitlab, preservando os outros elementos da URL. Depois disso, você será redirecionado para uma página contendo o comando que deve ser utilizado para registrar o Runner.
Volte para o shell logado no container Docker e execute:
gitlab-runner register --url <CAMINHO_PARA_O_CLUSTER> --token <TOKEN_GERADO>
O CAMINHO_PARA_O_CLUSTER é o IP para qualquer nó do cluster (ex.: http://10.20.9.116:30036). Você receberá um prompt para entregar algumas informações. O nome do Runner fica a seu critério e o executor deve ser “kubernetes”. Você pode visualizar o arquivo de configuração, gerado automaticamente, do Runner com o comando:
more /etc/gitlab-runner/config.toml
Salve esse arquivo. Nós vamos utilizar esse arquivo para configurar o nosso container Kubernetes do Gitlab Runner. No meu caso o arquivo é estruturado assim:
concurrent = 1
check_interval = 0
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "runner"
url = "http://10.20.9.116:30036"
id = 1
token = "glrt-qJDS_pTGimZC8YtaoBPw"
token_obtained_at = 2023-05-31T16:40:36Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.docker]
tls_verify = false
image = "busybox:latest"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size =0
Agora podemos deslogar do container Docker e derrubar o serviço Docker e proceder para os próximos passos. Podemos também parar o serviço Docker:
systemctl stop docker
Para configurar o Gitlab Runner, precisamos adicionar um ServiceAccount, um Role e um RoleBinding respectivo ao Runner no cluster para gerenciar as permissões do Runner.
Novamente, para conseguir mais informações singelas sobre esses termos ou componentes execute o comando
kubectl explain <COMPONENT>
# Exemplos
kubectl explain serviceaccount
kubectl explain role
kubectl explain rolebinding
kubectl explain configmap
O seguinte arquivo provê essa configuração:
# Arquivo: ./gitlab/gitlab-runner-authentication.yml
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-admin
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: gitlab-admin
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: gitlab-admin
subjects:
- kind: ServiceAccount
name: gitlab-admin
roleRef:
kind: Role
name: gitlab-admin
apiGroup: rbac.authorization.k8s.io
Aplique essa configuração com o comando:
kubectl apply -f gitlab-runner-authentication.yml
Depois de termos um ServiceAccount, Role e RoleBinding configurados precisamos de um ConfigMap para persistir a configuração do runner. O arquivo de configuração do ConfigMap deve especificar o arquivo de configuração do runner que roubamos do container Docker com algumas modificações para adaptá-lo ao ambiente Kubernetes. No meu caso, ficou assim:
# Arquivo: ./gitlab/gitlab-runner-config.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: gitlab-runner-config
data:
config.toml: |-
concurrent = 4
[[runners]]
name = "runner"
url = "http://10.20.9.116:30036"
id = 1
token = "glrt-qJDS_pTGimZC8YtaoBPw"
token_obtained_at = 2023-05-31T16:40:36Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "kubernetes"
[runners.kubernetes]
poll_timeout = 600
cpu_request = "1"
service_cpu_request = "200m"
Após a aplicação dessa configuração, aplicamos a configuração do Deployment:
# Arquivo: ./gitlab/gitlab-runner-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitlab-runner
spec:
replicas: 1
selector:
matchLabels:
name: gitlab-runner
template:
metadata:
labels:
name: gitlab-runner
spec:
serviceAccountName: gitlab-admin
containers:
- args:
- run
image: gitlab/gitlab-runner:latest
imagePullPolicy: Always
name: gitlab-runner
resources:
requests:
cpu: "100m"
limits:
cpu: "100m"
volumeMounts:
- name: config
mountPath: /etc/gitlab-runner/config.toml
readOnly: true
subPath: config.toml
volumes:
- name: config
configMap:
name: gitlab-runner-config
restartPolicy: Always
Você pode verificar se a instalação do Runner obteve sucesso indo para a tela Settings -> CI/CD -> Runners e checando se há um runner verde na seção “Assigned project runners”.
Vide referência.
Pipelines de CI/CD (Continuous Integration / Continuous Delivery) consistem em uma série de etapas a serem realizadas para a disponibilização de uma nova versão de um software. Elas são uma prática que tem como objetivo acelerar a disponibilização de softwares, adotando a abordagem de DevOps ou de engenharia de confiabilidade de sites (SRE). O pipeline de CI/CD inclui o **monitoramento** e **automação** para melhorar o processo de desenvolvimento de aplicações principalmente nos estágios de integração e teste, mas também na entrega e na imnplantação.
Como solução de **monitoramente** utilizaremos o **Grafana**. Grafana é uma plataforma de fonte aberta interativa de visualização de dados, desenvolvieda pela Grafana Labs, que permite aos usuários ver dados por meio de tabelas e gráficos unificados em um painel ou vários, para facilitar a interpretação e a compreensão. Com o Grafana pode-se consultar e definir alertas sobre informações e métricas de qualquer lugar que os dados esteja, sejam ambientes tradicionais de servidor, clusters do Kubernetes, etc. Grafana foi construído com base nos princípios open source e na crença de que os dados devem ser acessíveis em toda a organização, não apenas para um pequeno grupo de pessoas. Isso promove uma cultura em que os dados podem ser **facilmente encontrados e usados** por qualquer pessoa que precise deles, capacitando as equipes a serem mais abertas, inovadoras e colaborativas.
**Prometheus** é um conjunto de ferramentas de fonte aberta para monitoramento de sistemas. Ele coleta e armazena suas métricas como dados de séries temporais, i.e., informações de métricas são aramazenados com o timestamp em que foi gravado.
Em nossa solução, utilizaremos o Prometheus para coletar as métricas do Gitlab e o Grafana para tramar gráficos para visualização.
Para que o Prometheus possa rastrear as métricas do Gitlab precisamos primeiro expor os serviços exportadores de métricas no Gitlab. Para isso, editamos os arquivo ‘/etc/gitlab/gitlab.rb’ no contêiner do Gitlab. Para facilitar esse processo podemos ao invés disso editar o arquivo, no servidor NFS, ‘/var/nfs/general/gitlab/config/gitalb.rb’, que para você pode ser um caminho diferente a depender de onde você especificou o ponto de montagem no arquivo de Deployment do Gitlab.
Para configurar o Gitlab para expor esses serviços você pode copiar o arquivo “gitlab.rb” presente na raíz desse repositório para a pasta “config” que fica no seu diretório de configuração do Gitlab no seu servidor NFS. Por exemplo, /var/nfs/general/gitlab-igor/config.
Se quiser editar o arquivo manualmente, você deve abrir o arquivo /var/nfs/general/<SUA-PASTA-GITLAB>/config/gitlab.rb com um editor de texto, buscar as seguintes linhas, descomentar e editá-las para ficar assim:
gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '::1/128', '0.0.0.0/0']
registry['debug_addr'] = "0.0.0.0:5001"
gitlab_workhorse['prometheus_listen_addr'] = "0.0.0.0:9229"
sidekiq['listen_address'] = "0.0.0.0"
gitlab_rails['gitlab_kas_enabled'] = true
gitlab_rails['gitlab_kas_external_url'] = 'ws://gitlab.example.com/-/kubernetes-agent/'
gitlab_rails['gitlab_kas_internal_url'] = 'grpc://localhost:8153'
gitlab_rails['gitlab_kas_external_k8s_proxy_url'] = 'https://gitlab.example.com/-/kubernetes-agent/k8s-proxy/'
gitlab_kas_external_url "ws://gitlab.example.com/-/kubernetes-agent/"
gitlab_kas['enable'] = true
gitlab_kas['private_api_listen_address'] = 'localhost:8155'
gitlab_kas['env'] = {
'SSL_CERT_DIR' => "/opt/gitlab/embedded/ssl/certs/",
'OWN_PRIVATE_API_URL' => 'grpc://localhost:8155'
}
gitlab_rails['prometheus_address'] = '127.0.0.1:9090'
node_exporter['enable'] = true
node_exporter['listen_address'] = '0.0.0.0:9100'
redis_exporter['listen_address'] = '0.0.0.0:9121'
postgres_exporter['listen_address'] = '0.0.0.0:9187'
pgbouncer_exporter['listen_address'] = '0.0.0.0:9188'
gitlab_exporter['listen_address'] = '0.0.0.0'
gitlab_exporter['listen_port'] = '9168'
gitaly['configuration'] = {
prometheus_listen_addr: '0.0.0.0:9236',
}
Depois disso você deve reconfigurar e reiniciar o contêiner do Gitlab. Para isso, num nó control-plane (master) execute:
# Para pegar pegar o nome do pod do Gitlab:
kubectl get pod
# Para reconfigurar e reiniciar:
kubectl exec -it <GITLAB_POD_NAME> -- /bin/bash -c 'gitlab-ctl reconfigure ; gitlab-ctl restart'
Agora você deve criar os serviços para expor esses endpoinst para o cluster Kubernetes.
As portas utilizadas para o campo “targetPort” ficam à sua discrição. Configuração para expor serviços internos do Gitlab para o cluster:
# Arquivo: ./gitlab/services-integration-prometheus/integration-service.yaml
apiVersion: v1
kind: Service
metadata:
name: integration-service
spec:
selector:
app: gitlab
type: NodePort
ports:
- name: node-exporter-metrics
protocol: TCP
port: 9100
targetPort: 9100
nodePort: 30086
- name: gitlab-workhorse-metrics
protocol: TCP
port: 9229
targetPort: 9229
nodePort: 30087
- name: gitlab-exporter-metrics
protocol: TCP
port: 9168
targetPort: 9168
nodePort: 30088
- name: registry-metrics
protocol: TCP
port: 5001
targetPort: 5001
nodePort: 30089
- name: sidekiq-metrics
protocol: TCP
port: 8082
targetPort: 8082
nodePort: 30090
- name: redis-exporter-metrics
protocol: TCP
port: 9121
targetPort: 9121
nodePort: 30091
- name: postgres-metrics
protocol: TCP
port: 9187
targetPort: 9187
nodePort: 30092
- name: gitaly-metrics
protocol: TCP
port: 9236
targetPort: 9236
nodePort: 30093
- name: pgbouncer-metrics
protocol: TCP
port: 9188
targetPort: 9188
nodePort: 30094
Aplicação da configuração:
kubectl apply -f integration-service.yaml
O seguinte arquivo cria o ClusterRole e ClusterRoleBinding para configurar o mecanismo de autorização para o Prometheus acessar recursos protegidos do Kubernetes.
Novamente, para informações singelas sobre os termos utilizados, execute o comando
kubectl explain <COMPONENT>
# Exemplo
kubectl explain clusterrole
kubectl explain clusterrolebinding
Essa configuração dá para o Prometheus permissões para acessar recursos da API do Kubernetes.
# Arquivo: ./prometheus/alternative/kubernetes-prometheus/clusterRole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole # contem regras que representam um conjunto de permissoes
metadata:
name: prometheus
rules:
- apiGroups: [""] # "" indica o grupo de API base
resources: # especifica os recursos suplicados
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups:
- extensions
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding # concede as permissoes definidas em um papel para um usuario ou conjunto de usuarios
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: default
namespace: monitoring
Aplicação da configuração:
kubectl apply -f clusterRole.yaml
Crie o namespace ‘monitoring’:
kubectl create namespace monitoring
O seguinte arquivo especifica a configuração base do Prometheus e os ‘targets’, isto é, as endpoints aonde o Prometheus buscará as métricas. Atenção: você deve modificar o IP alvo para cada “job_name” que rastreia os serviços de exposição de métricas do Gitlab para apontar para um nó do cluster Kubernetes e deve modificar a porta a ser acessada para ser a porta especificada no arquivo de configuração ./gitlab/services-integration-prometheus/integration-service.yaml que foi utilizado para expor os serviços de exposição de métricas do Gitlab.
# Arquivo: ./prometheus/alternative/kubernetes-prometheus/config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-server-conf
labels:
name: prometheus-server-conf
namespace: monitoring
data:
prometheus.rules: |-
groups:
- name: devopscube demo alert
rules:
- alert: High Pod Memory
expr: sum(container_memory_usage_bytes) > 1
for: 1m
labels:
severity: slack
annotations:
summary: High Memory Usage
prometheus.yml: |-
global:
scrape_interval: 5s
evaluation_interval: 5s
rule_files:
- /etc/prometheus/prometheus.rules
alerting:
alertmanagers:
- scheme: http
static_configs:
- targets:
- "alertmanager.monitoring.svc:9093"
scrape_configs:
- job_name: 'node-exporter'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_endpoints_name]
regex: 'node-exporter'
action: keep
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
- job_name: 'kubernetes-nodes'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
- job_name: 'kubernetes-cadvisor'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
- job_name: redis
static_configs:
- targets:
- 10.20.9.116:30091
- job_name: postgres
static_configs:
- targets:
- 10.20.9.116:30092
- job_name: node
static_configs:
- targets:
- 10.20.9.116:30086
- job_name: gitlab-workhorse
static_configs:
- targets:
- 10.20.9.116:30087
- job_name: gitlab-rails
metrics_path: "/-/metrics"
scheme: http
static_configs:
- targets:
- 10.20.9.116:30036
- job_name: gitlab-sidekiq
static_configs:
- targets:
- 10.20.9.116:30090
- job_name: gitlab_exporter_database
metrics_path: "/database"
static_configs:
- targets:
- 10.20.9.116:30088
- job_name: gitaly
static_configs:
- targets:
- 10.20.9.116:30093
Aplicação da configuração:
kubectl apply -f config-map.yaml
O seguinte arquivo especifica o Deployment do Prometheus.
# Arquivo: ./prometheus/alternative/kubernetes-prometheus/prometheus-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-deployment
namespace: monitoring
labels:
app: prometheus-server
spec:
replicas: 1
selector:
matchLabels:
app: prometheus-server
template:
metadata:
labels:
app: prometheus-server
spec:
containers:
- name: prometheus
image: prom/prometheus
args:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus/"
ports:
- containerPort: 9090
volumeMounts:
- name: prometheus-config-volume
mountPath: /etc/prometheus/
- name: prometheus-storage-volume
mountPath: /prometheus/
volumes:
- name: prometheus-config-volume
configMap:
defaultMode: 420
name: prometheus-server-conf
- name: prometheus-storage-volume
emptyDir: {}
Aplicação da configuração:
kubectl apply -f prometheus-deployment.yaml
O seguinte arquivo especifica o Service para acesso aos recursos do Prometheus.
# Arquivo: ./prometheus/alternative/kubernetes-prometheus/prometheus-service.yaml
apiVersion: v1
kind: Service
metadata:
name: prometheus-service
namespace: monitoring
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '9090'
spec:
selector:
app: prometheus-server
type: NodePort
ports:
- port: 8080
targetPort: 9090
nodePort: 30000
Aplicação da configuração:
kubectl apply -f prometheus-service.yaml
Configuração do Deployment e do Service do Grafana:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: grafana
name: grafana
spec:
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
securityContext:
fsGroup: 472
supplementalGroups:
- 0
containers:
- name: grafana
image: grafana/grafana:9.1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
name: http-grafana
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /robots.txt
port: 3000
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 3000
timeoutSeconds: 1
resources:
requests:
cpu: 250m
memory: 750Mi
volumeMounts:
- mountPath: /var/lib/grafana
name: grafana-data
volumes:
- name: grafana-data
nfs:
server: 10.20.9.111
path: /var/nfs/general/grafana-igor
readOnly: no
---
apiVersion: v1
kind: Service
metadata:
name: grafana
spec:
ports:
- port: 3000
protocol: TCP
targetPort: http-grafana
nodePort: 30001
selector:
app: grafana
sessionAffinity: None
type: NodePort
Utilize o seguinte comando para aplicar a configuração.
kubectl apply -f grafana.yaml
Para fazer login na sua instância do Grafana, utilize como credênciais ‘admin’ para usuário e ‘admin’ para senha.
- Acesse a interface Web da sua instância do grafana utilizando um
navegador de Internet.
- Procure pelo símbolo de configuração e clique em ‘Data sources’.
- Clique em ‘Add data source’.
- Escolha o banco de dados Prometheus.
- Preencha como nome do Data Source ‘GitLab Omnibus’.
- Forneça a URL para a instância do Prometheus (Como utilizamos exposição via nodePort o host pode ser qualquer host do cluster).
- Clique ‘Save & test’.
Na tela principal do Grafana, vá para a página de gerenciamento de Dashboards.
- Clique em ‘New’.
- Clique me ‘Import’.
- Clique em ‘Upload JSON file’.
- Navegue até o diretório lab-proxmox/gitlab/dashboards/grafana-dashboards-master/omnibus/
- Escolha o Dashboard que você deseja visualizar. Por exemplo, gitaly.json.
O dashboard mais útil para propósitos de experimentos com o cluster Kubernetes é o localizado em lab-proxmox-doc/dashboards/kubernetes-cluster-monitoring-via-prometheus_rev3.json. Para visualizar esse dashboard você precisa criar uma nova Data Source Prometheus chamada DS_PROMETHEUS e associar ao dashboard.
Para testar uma pipeline CI/CD você pode clonar esse repositório e subí-lo para sua instância, por exemplo, colocá-lo no repositório utilizado para associar um Runner em uma das seções anteriores.
Ao fazer o primeiro push, a pipeline CI/CD iniciará automaticamente. O log da pipeline pode ser visualizada pelo caminho CI/CD -> Pipelines. No campo “Stages” você verá as Jobs. Clique no Job cujo log você quer visualizar.
No dashboard monitor do cluster Kubernetes você pode visualizar informações como o uso de CPU por nó, por contêiner, por processo, etc. O mesmo a respeito de uso de memória e de rede.
Para deploy do Prometheus pode-se utilizar a seguinte configuração. Entretanto, ela é complexa e difícil de configurar.
kubectl create namespace darwin
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack --namespace darwin --version 46.8.0
# Arquivo: ./prometheus/prometheus-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: darwin
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
scrape_configs:
- job_name: 'darwin-service'
scrape_interval: 5s
static_configs:
- targets: ['darwin-service:8080']
- job_name: nginx
static_configs:
- targets:
- 1.1.1.1:8060
- job_name: redis
static_configs:
- targets:
- 1.1.1.1:9121
- job_name: postgres
static_configs:
- targets:
- 1.1.1.1:9187
- job_name: node
static_configs:
- targets:
- 1.1.1.1:9100
- job_name: gitlab-workhorse
static_configs:
- targets:
- 1.1.1.1:9229
- job_name: gitlab-rails
metrics_path: "/-/metrics"
scheme: https
static_configs:
- targets:
- 1.1.1.1
- job_name: gitlab-sidekiq
static_configs:
- targets:
- 1.1.1.1:8082
- job_name: gitlab_exporter_database
metrics_path: "/database"
static_configs:
- targets:
- 1.1.1.1:9168
- job_name: gitlab_exporter_sidekiq
metrics_path: "/sidekiq"
static_configs:
- targets:
- 1.1.1.1:9168
- job_name: gitaly
static_configs:
- targets:
- 1.1.1.1:9236
# Arquivo: ./prometheus/darwin-prometheus-service.yaml
apiVersion: v1
kind: Service
metadata:
name: darwin-prometheus-service
namespace: darwin
spec:
type: NodePort
selector:
app.kubernetes.io/name: prometheus
ports:
- name: web
port: 9090
targetPort: 9090
nodePort: 30000