Projeto 12 — Os Services Kubernetes · Nível intermédio → avançado · Duração estimada: 3 a 5 h
Todo o código da aplicação é-lhe fornecido em anexo neste documento. O seu trabalho: escrever, determinar e completar os Services que faltam — ou seja, fazer comunicar um sistema que, no estado atual, está totalmente mudo.
Uma equipa implantou uma pequena plataforma de comércio eletrónico no Kubernetes. As imagens estão construídas, os Deployments e o StatefulSet estão a correr, todos os Pods estão Running.
E no entanto, nada funciona.
O portal mostra um painel inteiramente vermelho: não alcança nenhum componente. A base de dados é inatingível. A cache é invisível. Nenhuma página é acessível a partir do navegador.
A razão é simples: a pessoa que deveria escrever os Services partiu sem os entregar.
Lembrete fundamental que este projeto o vai fazer viver: Pods a correr não constituem uma aplicação. Sem Services, são ilhéus isolados, sem endereço estável nem nome, incapazes de se encontrarem uns aos outros.
A sua missão: restabelecer todas as comunicações, unicamente ao escrever os Services certos.
Conceitos essenciais antes de começar
Esta secção é um mini-manual autónomo: contém todo o vocabulário necessário às missões. Leia-a uma vez, volte a ela quando uma palavra lhe escapar.
1. O problema que um Service resolve
Um Pod é mortal: o Kubernetes pode apagá-lo, deslocá-lo, recriá-lo — e o seu novo IP será diferente. Por isso nunca se liga a um Pod pelo seu IP.
Um Service é um objeto estável — nome, IP, porta — que segue os Pods para onde forem. O mecanismo é simples:
O que faz a ligação entre um Service e «os seus» Pods é o seletor de labels:
yaml
spec: selector: app: api-produits # tout Pod portant CE label est atteint
A lista dos Pods que correspondem forma o objeto Endpoints — é a sua radiografia do Service.
powershell
kubectl get endpoints api-produits# api-produits 10.244.0.3:8000,10.244.0.4:8000,10.244.0.5:8000
Se Endpoints estiver vazio, o seletor não corresponde a nenhum Pod: é quase sempre um erro de digitação num label.
2. Os cinco tipos de Services (os únicos de que precisa)
Um ClusterIP sem IP virtual: devolve a lista dos IP dos Pods, mais um nome DNS por Pod
Não
bd-interne
ExternalName
Alias DNS para um nome externo. Nenhum Pod, nenhum seletor.
Não
paiement-externe
Ponto importante: um Service que «não funciona» quase nunca é um problema de tipo. É quase sempre seletor ou porta.
3. DNS interno: as regras que dão a ilusão de magia
No cluster, o CoreDNS fabrica automaticamente nomes segundo regras fixas:
Você chama
O CoreDNS resolve para
api-produits
o Service api-produits do mesmo namespace
api-produits.default
o Service api-produits do namespace default
api-produits.default.svc.cluster.local
forma longa e plenamente qualificada
Corolário capital: o nome do Service é o nome que a aplicação chama. Um Service chamado notificationnão responde a http://notifications. Esta armadilha está no coração da missão 6.
4. StatefulSet e Service headless: o tandem
Um Deployment trata as suas réplicas como gémeas intercambiáveis (web-abc123-x7k9, web-abc123-p2m1…). Perfeito para web sem estado.
Um StatefulSet produz, pelo contrário, Pods numerados e estáveis: bd-0, bd-1, bd-2. Cada Pod guarda a sua identidade através dos reinícios — indispensável para uma base de dados em que se deve designar precisamente a primária.
Mas um StatefulSet não basta sozinho: exige estar associado a um Service headless cujo nome indica no campo serviceName.
yaml
kind: StatefulSetspec: serviceName: bd-interne # <-- pointe vers un Service headless du même nom replicas: 3
Este Service headless dá então um nome DNS por Pod:
bd-0.bd-interne -> IP du Pod bd-0 UNIQUEMENTbd-1.bd-interne -> IP du Pod bd-1 UNIQUEMENTbd-interne -> IPs des trois Pods (liste)
Sem a palavra None em spec.clusterIP, nenhum destes nomes existe.
yaml
spec: clusterIP: None # transforme le Service en "headless"
Retenha isto: para alcançar bd-0.bd-interne, são precisas duas condições simultâneas — um StatefulSet cujo serviceName: bd-interne, e um Service headless chamado bd-interne. Se uma faltar, o nome individual não existe.
5. Portas nomeadas e Services multiporta
Quando um Service expõe várias portas, cada entrada torna-se obrigatoriamente nomeada:
Utilidade: o seu código guarda o mesmo nome interno (paiement-externe) quer se trate de um serviço no cluster, de um serviço SaaS externo, ou de uma mudança de API. Muda-se o Service, não o código.
7. As três perguntas que desbloqueiam 90 % das avarias
Cada vez que um Service não funciona, faça estas três perguntas nesta ordem:
É exatamente o método da missão 6.
O que saberá fazer no fim
Distinguir os cinco tipos de Services e saber quando usar cada um.
Usar kubectl get svc, kubectl describe svc, kubectl get endpoints como três ferramentas complementares.
Acoplar corretamente um StatefulSet com um Service headless.
Escrever um Service multiporta limpo, com targetPort referenciado por nome.
Diagnosticar as três avarias mais frequentes em empresa (label errado, porta errada, nome errado).
A arquitetura a pôr em serviço
Sete componentes já estão a correr. Nenhum é alcançável.
Componente
Porta(s) do contentor
Label dos Pods
Controlador
portail
5000
app: portail
Deployment (1 réplica)
api-produits
8000
app: api-produits
Deployment (3 réplicas)
api-commandes
8000
app: api-commandes
Deployment (2 réplicas)
cache
6379
app: cache
Deployment (1 réplica)
notifications
7000
app: notifications
Deployment (2 réplicas)
metriques
8080 (nomeado web) e 9090 (nomeado prom)
app: metriques
Deployment (2 réplicas)
bd
5432
app: bd
StatefulSet (3 réplicas)
Organização dos ficheiros
Crie exatamente esta árvore, ao recopiar o conteúdo dos anexos. Cada anexo indica o caminho exato do ficheiro a criar.
projet12-mission-services/├── 00-ENONCE.md <- este documento│├── apps/ <- O CODIGO (ANEXO A) — NAO MODIFICAR│ ├── micro/│ │ ├── app.py│ │ ├── requirements.txt│ │ └── Dockerfile│ ├── metriques/│ │ ├── app.py│ │ ├── requirements.txt│ │ └── Dockerfile│ └── portail/│ ├── app.py│ ├── requirements.txt│ └── Dockerfile│├── k8s/│ ├── 01-deployments.yaml <- FORNECIDO (ANEXO B) — NAO MODIFICAR│ ├── 02-statefulset-bd.yaml <- FORNECIDO (ANEXO B) — NAO MODIFICAR│ ││ └── services/ <- A SUA VEZ│ ├── 01-api-produits.yaml <- esqueleto a completar (ANEXO C)│ ├── 02-portail.yaml <- esqueleto a completar (ANEXO C)│ ├── 03-bd-interne.yaml <- esqueleto a completar (ANEXO C)│ ├── 04-metriques.yaml <- esqueleto a completar (ANEXO C)│ ├── 05-paiement-externe.yaml <- esqueleto a completar (ANEXO C)│ ││ └── 06-casses/ <- FORNECIDOS mas DEFEITUOSOS (ANEXO D)│ ├── casse-1.yaml│ ├── casse-2.yaml│ └── casse-3.yaml│├── outils/│ └── valider.ps1 <- FORNECIDO (ANEXO E)│└── RAPPORT.md <- A REDIGIR por si
Apenas três imagens são necessárias: micro:1.0 serve cinco componentes diferentes (o comportamento muda por variáveis de ambiente), metriques:1.0 expõe duas portas, e portail:1.0 mostra o painel.
O painel: o seu indicador de progresso
O portal interroga em contínuo cada componente e mostra um cartão por ligação, atualizado a cada 3 segundos:
Cartão
Significado
Onde procurar o erro
VERMELHO
O nome DNS não existe
O Service não foi criado, ou o seu nome está errado
LARANJA
O nome está resolvido, mas ninguém responde
O Service existe, mas o seu seletor ou a sua porta está errado
VERDE
Comunicação estabelecida
O seu Service está correto
Objetivo final: os 8 cartões a verde, e o contador a mostrar 8 / 8.
Esta distinção vermelho/laranja não é decorativa: diz-lhe de que lado procurar. Vermelho = o Service não existe (nada a depurar, é preciso escrevê-lo). Laranja = o Service existe mas não encontra os seus Pods ou bate na porta errada.
Proibição absoluta de modificar a pasta apps/, bem como 01-deployments.yaml e 02-statefulset-bd.yaml.
(Toda a dificuldade consiste em adaptar-se ao existente: é exatamente a situação de um posto de trabalho real.)
Só cria e só modifica ficheiros situados emk8s/services/.
Nenhum endereço IP em concreto. Tudo deve assentar nos nomes DNS e nos seletores de labels.
Deve determinar você mesmo o tipo de cada Service: nada lhe diz se se trata de um ClusterIP, de um NodePort, de um LoadBalancer, de um serviço headless ou de um ExternalName. É o coração da avaliação.
Os nomes dos Services são impostos: o código da aplicação chama-os tal qual. Um nome errado dá um cartão vermelho.
Trabalha no Kubernetes integrado no Docker Desktop (Settings → Kubernetes → Enable Kubernetes).
Preparação
Pré-requisitos — a verificar uma só vez
O Docker Desktop está arrancado (ícone verde na barra do sistema).
O Kubernetes está ativado no Docker Desktop: Settings → Kubernetes → Enable Kubernetes → Apply & Restart. Sem esta caixa marcada, nenhum comando kubectl funcionará.
O Docker Desktop dispõe de pelo menos 4 Go de RAM: Settings → Resources → Memory ≥ 4 GB. Este projeto lança 14 Pods; com 2 Go a máquina sufoca e Pods ficam em Pending.
Tem Internet (para a missão 5 e para descarregar a imagem busybox).
Sequência de arranque
powershell
# 0) Se placer sur le bon cluster (indispensable si minikube ou kind a déjà servi)kubectl config use-context docker-desktopkubectl get nodes # doit afficher docker-desktop Ready# 1) Construire les trois imagesdocker build -t micro:1.0 ./apps/microdocker build -t metriques:1.0 ./apps/metriquesdocker build -t portail:1.0 ./apps/portail# 2) Déployer la base fournie (des Pods, et AUCUN Service)kubectl apply -f k8s/01-deployments.yamlkubectl apply -f k8s/02-statefulset-bd.yaml# 3) Attendre que TOUS les Pods soient Ready (environ 30 s)kubectl wait --for=condition=ready pod --all --timeout=180s# 4) Constater la situation de départkubectl get pods # 14 Pods, tous Runningkubectl get svc # seulement "kubernetes" : aucun de vos Services
Neste estádio: todos os Pods correm e nada comunica. É o ponto de partida normal.
Dois avisos técnicos a conhecer desde já — não é um erro seu:
Warning Endpoints is deprecated in v1.33+ — o Kubernetes mostra sistematicamente esta mensagem em cada kubectl get endpoints. O comando continua a funcionar perfeitamente, ignore o warning. A nova API equivalente é kubectl get endpointslices, mas todos os comandos deste TP usam de propósito endpoints, mais legível para aprender.
O painel pode demorar 10 a 15 segundos a aparecer na primeira vez: o portal testa 8 ligações de rede em cada apresentação, cada uma com um tempo de espera de 1,5 s. Quando ainda nada funciona, espera cada prazo antes de mostrar vermelho ou laranja. Uma vez os Services corretos, o tempo de resposta cai para algumas centenas de milissegundos.
Pergunta a colocar a si mesmo imediatamente: como vai sequer ver o painel, já que nenhuma porta de entrada existe ainda?
Boia de salvamento:kubectl port-forward funciona sem nenhum Service, diretamente num Pod.
powershell
kubectl port-forward deploy/portail 5000:5000
Depois abra http://localhost:5000. Verá o painel todo vermelho, com o cartão «Acesso externo» a laranja (normal: não passou pela porta 30500).
As missões
Missão 1 — Fazer o portal falar com a API produtos (15 pontos)
O portal chama http://api-produits na porta 80. Os Pods da API escutam na porta 8000 e levam o label app: api-produits.
Ficheiro a completar:k8s/services/01-api-produits.yaml
A determinar: o tipo de Service, o seletor, bem como port e targetPort.
Validação:
powershell
kubectl apply -f k8s/services/01-api-produits.yamlkubectl get svc api-produitskubectl get endpoints api-produits # doit lister 3 adresses IP
O cartão API Produtos passa a verde.
Missão 2 — Abrir a porta de entrada (15 pontos)
O painel deve ser acessível a partir do seu navegador no endereço exato http://localhost:30500. Os Pods do portal escutam na porta 5000.
Ficheiro a completar:k8s/services/02-portail.yaml
A determinar: que tipo de Service expõe uma aplicação fora do cluster numa porta fixa da máquina? Qual é a gama de portas autorizada para este campo?
Validação:
powershell
kubectl get svc portail # PORT(S) doit afficher 80:30500/TCPstart http://localhost:30500
O cartão Acesso externo passa a verde.
Pergunta a tratar no relatório: outro tipo de Service também teria tornado o portal acessível a partir do navegador no Docker Desktop. Qual? E que diferença faria em produção na cloud?
Missão 3 — Dar uma identidade a cada base de dados (20 pontos)
O StatefulSet bd fornece 3 réplicas. O portal deve alcançar precisamente a primeira (a primária), no endereço:
bd-0.bd-interne
Os Pods da base levam o label app: bd e escutam na porta 5432.
Ficheiro a completar:k8s/services/03-bd-interne.yaml
A determinar: que tipo de Service dá um nome DNS individual a cada Pod, em vez de um único IP virtual? Que campo é preciso escrever, e com que valor particular?
Validação:
powershell
# 1) Depuis un Pod utilitaire, joindre directement bd-0 :kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- wget -qO- http://bd-0.bd-interne:5432/ping# doit repondre : {"pod":"bd-0","port":5432,"service":"base-de-donnees"}# 2) Verifier les entrees DNS avec le nom pleinement qualifie# (busybox nslookup n'applique PAS les search domains, il faut donner le FQDN) :kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup bd-0.bd-interne.default.svc.cluster.local# doit renvoyer UNE seule adresse (celle du Pod bd-0)kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup bd-interne.default.svc.cluster.local# doit renvoyer TROIS adresses (une par Pod du StatefulSet)
Armadilha a não falhar
examine o campo serviceName do StatefulSet fornecido. O nome do seu Service deve corresponder-lhe exatamente, senão os nomes individuais dos Pods nunca serão criados.
Armadilha técnica (busybox)
nslookup nom-court não funciona a partir de um Pod busybox porque o seu resolvedor não usa os search domains de /etc/resolv.conf. A partir de um Pod de aplicação verdadeiro (como portail), em contrapartida, bd-0.bd-interneresolve perfeitamente. Use portanto wget para testar a cadeia de aplicação real, e o FQDN para levantar qualquer ambiguidade DNS.
Missão 4 — Expor duas portas no mesmo Service (15 pontos)
O componente metriques escuta em duas portas:
Uso
Porta do contentor
Nome da porta declarado no Deployment
Interface web
8080
web
Métricas
9090
prom
O portal chama http://metriques (porta 80) ehttp://metriques:9090/metrics.
Ficheiro a completar:k8s/services/04-metriques.yaml
A determinar: como declarar várias portas num Service? Que restrição se torna então obrigatória para cada entrada? E como fazer targetPort apontar para uma porta do contentor pelo seu nome em vez de pelo seu número, para que o Service permaneça válido mesmo se o número mudar?
Validação:
powershell
kubectl describe svc metriques # les DEUX ports doivent apparaître
Missão 5 — Dar um nome interno a um serviço externo (10 pontos)
O portal deve alcançar um serviço de pagamento alojado fora do cluster, mas o código chama um nome interno: paiement-externe. Este nome deve apontar para example.com.
Ficheiro a completar:k8s/services/05-paiement-externe.yaml
A determinar: que tipo de Service cria um simples alias DNS para um nome externo, sem seletor e sem nenhum Pod?
Validação:
powershell
kubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup paiement-externe.default.svc.cluster.local# doit afficher :# paiement-externe.default.svc.cluster.local canonical name = example.com# Name: example.com# Address: <IP publique>
Esta missão exige que o cluster possa resolver nomes públicos. Se não tiver nenhum acesso à Internet, substitua o alvo por api-produits.default.svc.cluster.local e assinale-o no seu relatório.
Missão 6 — O inquérito: reparar três Services defeituosos (20 pontos)
A pasta k8s/services/06-casses/ contém três Services já escritos… que não funcionam. Cada um comporta um só erro, e são as três faltas mais frequentes em empresa.
powershell
kubectl apply -f k8s/services/06-casses/
Ficheiro
Sintoma observado
casse-1.yaml
O Service existe, mas kubectl get endpoints devolve <none>
casse-2.yaml
Os Endpoints estão bem preenchidos, mas qualquer ligação é recusada
casse-3.yaml
O Service parece perfeito, mas o portal nunca o alcança
Para cada um dos três casos, o seu relatório deve conter:
o comando de diagnóstico que o pôs na pista ;
a causa exata da avaria ;
a correção aplicada ;
a prova de que a ligação funciona (cartão verde + saída de comando).
Método aconselhado: proceda como um investigador. kubectl describe svc, kubectl get endpoints, kubectl get pods --show-labels, e depois compare linha a linha o Service e os Pods. A diferença entre «Endpoints vazios» e «ligação recusada» já lhe indica de que lado procurar.
Missão 7 — Bónus: o grande salto (5 pontos)
À escolha, um só basta:
a) Fazer com que o mesmo cliente seja sempre servido pelo mesmo Pod da API produtos. (Indício: um campo do Service permite uma «aderência» fundada no IP do cliente.)
b) Criar um Service sem seletor a apontar para um endereço IP externo fixo, ao escrever você mesmo os seus Endpoints.
c) Escrever um Service do tipo LoadBalancer para o portal, e depois explicar o que se torna EXTERNAL-IP no Docker Desktop, e o que se tornaria na AWS.
Validação automática
Um script dá-lhe a pontuação a qualquer momento:
powershell
.\outils\valider.ps1
Se o PowerShell recusar executar o script com uma mensagem do tipo l'exécution de scripts est désactivée sur ce système, contorne a restrição só para este comando:
O script não dá nenhuma solução: indica apenas o que falha e onde olhar.
Entregáveis
A pasta k8s/services/ completa: os seus 5 Services escritos e os 3 Services reparados.
Um RAPPORT.md contendo:
para cada Service: o tipo escolhido e uma justificação em duas frases («por que este e não outro») ;
o inquérito completo da missão 6 (comando → causa → correção → prova) ;
uma captura do painel a mostrar 8 / 8 ;
uma captura de kubectl get svc a mostrar todos os seus Services e os seus tipos ;
as suas respostas às questões de reflexão.
A saída final de .\outils\valider.ps1.
Questões de reflexão
Por que a aplicação absolutamente não podia funcionar sem Services, quando todos os Pods estavam Running?
Qual é a diferença concreta entre um cartão vermelho e um cartão laranja? O que lhe ensina cada um sobre o sítio onde está o erro?
Por que o Service da base de dados deve ser headless, quando um Service ordinário basta para a API produtos?
O que contém exatamente a lista dos Endpoints, e quem a atualiza? O que acontece quando um Pod fica NotReady?
Apaga um Pod da API produtos; o Kubernetes recria um com um endereço IP diferente. Por que o portal continua a funcionar sem a menor alteração?
Em produção, exporia dez aplicações com dez Services do tipo LoadBalancer? Justifique, e proponha uma alternativa.
Cotação
Elemento
Pontos
Missão 1 — Service interno e descoberta DNS
15
Missão 2 — Exposição externa na porta 30500
15
Missão 3 — Service headless e identidades estáveis
20
Missão 4 — Multiporta e portas nomeadas
15
Missão 5 — Alias para um serviço externo
10
Missão 6 — Diagnóstico e reparação (3 avarias)
20
Qualidade do relatório e justificação das escolhas
5
Bónus — Missão 7
+5
Total
100 (+5)
Penalizações: −10 por alteração de um ficheiro proibido (apps/, 01-deployments.yaml, 02-statefulset-bd.yaml) ; −5 por endereço IP escrito em concreto.
Critérios de sucesso
Critério
Esperado
Painel
8 / 8 cartões verdes
Tipos de Services
Cada um adaptado ao seu uso e justificado
DNS individual
bd-0.bd-interne resolvido para um só Pod
Multiporta
As duas portas visíveis, targetPort referenciado por nome
Inquérito
As 3 avarias identificadas, explicadas e corrigidas
Resiliência
Depois de apagar um Pod, o portal continua a funcionar
Nenhum IP em concreto
Unicamente nomes DNS e seletores de labels
Caixa de ferramentas
Nenhuma solução aqui — apenas pistas.
powershell
kubectl get svc # types, IP, portskubectl describe svc <nom> # détails + Endpointskubectl get endpoints <nom> # QUI se trouve derrière le Service ?kubectl get pods --show-labels # les labels réels des Podskubectl get pods -l app=<valeur> # tester un sélecteurkubectl port-forward deploy/portail 5000:5000 # accéder à un Pod SANS Servicekubectl run test --rm -i --restart=Never --image=busybox:1.36 -- wget -qO- http://<nom>/pingkubectl run test --rm -i --restart=Never --image=busybox:1.36 -- nslookup <nom>.default.svc.cluster.localkubectl logs -l app=portail --tail=30 # ce que le portail n'arrive pas à joindrekubectl delete svc <nom> # repartir de zéro sur un Service
Armadilha a conhecer com kubectl run test: se encadear vários comandos depressa, o Pod anterior nem sempre é apagado a tempo e obterá:
Error from server (AlreadyExists): pods "test" already exists
Duas soluções: mudar o nome (test1, test2…) em cada comando, ou limpar antes:
powershell
kubectl delete pod test --ignore-not-found; kubectl run test --rm -i --restart=Never ...
As três perguntas que desbloqueiam 90 % das situações:
O Service existe, com o nome certo? (senão → cartão vermelho: não há nada a depurar, é preciso escrevê-lo)
Os Endpoints estão preenchidos? (vazios → o seletor não corresponde a nenhum label de Pod)
O targetPort corresponde à porta realmente escutada pelo contentor? (senão → ligação recusada)
ANEXO A — As aplicações
Não modifique nenhum destes ficheiros. Recopie-os tal qual nos caminhos indicados.
A.1 — O microserviço genérico
Esta única aplicação serve cinco componentes (api-produits, api-commandes, cache, notifications, bd). O seu nome e a sua porta são fixados por variáveis de ambiente.
Ficheiro: apps/micro/app.py
python
"""Micro-servico generico de demonstracao.A mesma imagem serve varios componentes: o nome e a porta de escuta saofornecidos por variaveis de ambiente (APP_NAME, PORT).Cada resposta contem o nome do Pod, o que torna visivel a distribuicaode carga realizada por um Service."""import osimport socketfrom flask import Flask, jsonifyapp = Flask(__name__)NOM = os.environ.get("APP_NAME", "micro")PORT = int(os.environ.get("PORT", "8000"))@app.route("/")@app.route("/ping")def ping(): return jsonify(service=NOM, pod=socket.gethostname(), port=PORT)@app.route("/health")def health(): return "OK", 200if __name__ == "__main__": app.run(host="0.0.0.0", port=PORT)
# ---------------------------------------------------------------------------# A BASE DE DADOS (3 replicas) — FORNECIDO, NAO MODIFICAR## ATENCAO: o campo serviceName abaixo impõe o NOME do Service que# tera de escrever para que bd-0, bd-1 e bd-2 obtenham cada um um nome DNS.# ---------------------------------------------------------------------------apiVersion: apps/v1kind: StatefulSetmetadata: name: bdspec: serviceName: bd-interne # <-- leia bem esta linha replicas: 3 selector: matchLabels: app: bd template: metadata: labels: app: bd spec: containers: - name: micro image: micro:1.0 imagePullPolicy: IfNotPresent env: - { name: APP_NAME, value: "base-de-donnees" } - { name: PORT, value: "5432" } ports: - containerPort: 5432 readinessProbe: httpGet: { path: /health, port: 5432 } initialDelaySeconds: 3 periodSeconds: 5
ANEXO C — Os esqueletos de Services a completar
Recopie estes cinco ficheiros e depois substitua cada TODO pelo valor certo.
As linhas precedidas de # ? são perguntas a decidir: cabe-lhe a si decidir se deve acrescentar, modificar ou apagar a linha em causa.
Ficheiro: k8s/services/01-api-produits.yaml
yaml
# MISSION 1 — Tornar a API produtos alcancavel a partir do portal.## O portal chama : http://api-produits (logo a porta 80)# Os Pods escutam em : 8000# Os Pods levam o label : app: api-produits## ? Que tipo de Service para uma comunicacao INTERNA ao cluster ?apiVersion: v1kind: Servicemetadata: name: api-produits # nome IMPOSTO : nao mudarspec: type: TODO selector: TODO: TODO ports: - port: TODO # a porta pela qual os clientes chamam targetPort: TODO # a porta realmente escutada pelo contentor
Ficheiro: k8s/services/02-portail.yaml
yaml
# MISSION 2 — Tornar o painel acessivel a partir do navegador,# no endereco exato : http://localhost:30500## Os Pods escutam em : 5000# Os Pods levam o label : app: portail## ? Que tipo de Service abre uma porta na MAQUINA ?# ? Qual e a gama autorizada para esta porta ?# ? Que campo suplementar e preciso acrescentar para impor a porta 30500 ?apiVersion: v1kind: Servicemetadata: name: portail # nome IMPOSTO : nao mudarspec: type: TODO selector: TODO: TODO ports: - port: TODO targetPort: TODO # ? falta aqui uma linha
Ficheiro: k8s/services/03-bd-interne.yaml
yaml
# MISSION 3 — Dar um nome DNS INDIVIDUAL a cada replica da base,# para poder alcancar precisamente : bd-0.bd-interne## Os Pods escutam em : 5432# Os Pods levam o label : app: bd## ? Que tipo de Service NAO possui IP virtual unico ?# ? Que campo, com que valor muito particular, produz este efeito ?# ? O nome abaixo deve corresponder a que campo do StatefulSet ?apiVersion: v1kind: Servicemetadata: name: bd-interne # nome IMPOSTO : nao mudarspec: # ? falta aqui uma linha essencial selector: TODO: TODO ports: - port: TODO targetPort: TODO
Ficheiro: k8s/services/04-metriques.yaml
yaml
# MISSION 4 — Expor DUAS portas num unico e mesmo Service.## O portal chama : http://metriques (porta 80)# e : http://metriques:9090/metrics## Os Pods escutam em : 8080 (porta nomeada "web") e 9090 (porta nomeada "prom")# Os Pods levam o label : app: metriques## ? Que restricao se torna OBRIGATORIA assim que um Service expoe varias portas ?# ? Como fazer targetPort apontar para uma porta do contentor PELO SEU NOME ?apiVersion: v1kind: Servicemetadata: name: metriques # nome IMPOSTO : nao mudarspec: type: TODO selector: TODO: TODO ports: - TODO: TODO # ? falta um campo obrigatorio em cada entrada port: TODO targetPort: TODO - TODO: TODO port: TODO targetPort: TODO
Ficheiro: k8s/services/05-paiement-externe.yaml
yaml
# MISSION 5 — Fazer um nome INTERNO apontar para um servico EXTERNO.## O portal usa o nome : paiement-externe# Este nome deve apontar para : example.com## ? Que tipo de Service cria um simples alias DNS (CNAME) ?# ? Este tipo possui seletor ? portas ? Pods ?apiVersion: v1kind: Servicemetadata: name: paiement-externe # nome IMPOSTO : nao mudarspec: type: TODO TODO: TODO # ? o campo que indica o alvo externo
ANEXO D — Os três Services defeituosos
Recopie estes três ficheiros tal qual, aplique-os e depois diagnostique e corrija.
Cada um contém exatamente um erro. Não reescreva o ficheiro do zero: encontre a falta.
Ficheiro: k8s/services/06-casses/casse-1.yaml
yaml
# PANNE 1# Sintoma : o Service existe, mas "kubectl get endpoints api-commandes"# devolve <none>. O portal mostra um cartao LARANJA.apiVersion: v1kind: Servicemetadata: name: api-commandesspec: type: ClusterIP selector: app: api-commande ports: - port: 80 targetPort: 8000
Ficheiro: k8s/services/06-casses/casse-2.yaml
yaml
# PANNE 2# Sintoma : "kubectl get endpoints cache" mostra mesmo um endereco IP,# mas qualquer ligacao falha. O portal mostra um cartao LARANJA.apiVersion: v1kind: Servicemetadata: name: cachespec: type: ClusterIP selector: app: cache ports: - port: 80 targetPort: 6380
Ficheiro: k8s/services/06-casses/casse-3.yaml
yaml
# PANNE 3# Sintoma : este Service parece perfeito (tipo correto, seletor correto,# Endpoints preenchidos, portas coerentes)... e no entanto o portal# mostra um cartao VERMELHO e NUNCA o alcanca.apiVersion: v1kind: Servicemetadata: name: notificationspec: type: ClusterIP selector: app: notifications ports: - port: 80 targetPort: 7000
ANEXO E — O script de validação
Ficheiro: outils/valider.ps1
powershell
# ---------------------------------------------------------------------------# Script de validacao — da uma pontuacao, NUNCA a solucao.# Utilizacao : .\outils\valider.ps1# ---------------------------------------------------------------------------$total = 0function Existe($nom) { kubectl get svc $nom -o name 2>$null | Out-Null return $LASTEXITCODE -eq 0}function Afficher($libelle, $points, $max, $note) { $etat = if ($points -eq $max) { "[OK] " } else { "[ECHEC] " } $ligne = "{0} {1} {2}/{3}" -f $etat, $libelle.PadRight(34, '.'), $points, $max if ($note) { $ligne += " -> $note" } Write-Host $ligne}Write-Host ""Write-Host "=== VALIDATION — Mission : retablir les communications ===" -ForegroundColor CyanWrite-Host ""# --- Mission 1 : api-produits ---------------------------------------------$p = 0; $note = ""if (-not (Existe "api-produits")) { $note = "Service api-produits introuvable" }else { $eps = (kubectl get endpoints api-produits -o jsonpath="{.subsets[*].addresses[*].ip}" 2>$null) $tp = (kubectl get svc api-produits -o jsonpath="{.spec.ports[0].targetPort}" 2>$null) if (-not $eps) { $note = "Endpoints vides : le selecteur ne correspond a aucun Pod" } elseif ("$tp" -ne "8000") { $note = "targetPort ne correspond pas au port ecoute" } else { $p = 15 }}Afficher "Mission 1 - api-produits" $p 15 $note; $total += $p# --- Mission 2 : portail ---------------------------------------------------$p = 0; $note = ""if (-not (Existe "portail")) { $note = "Service portail introuvable" }else { $type = (kubectl get svc portail -o jsonpath="{.spec.type}" 2>$null) $np = (kubectl get svc portail -o jsonpath="{.spec.ports[0].nodePort}" 2>$null) if ("$np" -ne "30500") { $note = "le port expose sur la machine doit etre 30500 (actuel : '$np')" } elseif ($type -notin @("NodePort", "LoadBalancer")) { $note = "type inadapte a un acces externe" } else { $p = 15 }}Afficher "Mission 2 - portail" $p 15 $note; $total += $p# --- Mission 3 : bd-interne (headless) -------------------------------------$p = 0; $note = ""if (-not (Existe "bd-interne")) { $note = "Service bd-interne introuvable (verifiez serviceName du StatefulSet)" }else { $cip = (kubectl get svc bd-interne -o jsonpath="{.spec.clusterIP}" 2>$null) $eps = (kubectl get endpoints bd-interne -o jsonpath="{.subsets[*].addresses[*].ip}" 2>$null) if ("$cip" -ne "None") { $note = "ce Service ne doit PAS avoir d'IP virtuelle" } elseif (-not $eps) { $note = "Endpoints vides : verifiez le selecteur" } else { $p = 20 }}Afficher "Mission 3 - bd-interne" $p 20 $note; $total += $p# --- Mission 4 : metriques (multi-port) ------------------------------------$p = 0; $note = ""if (-not (Existe "metriques")) { $note = "Service metriques introuvable" }else { $ports = (kubectl get svc metriques -o jsonpath="{.spec.ports[*].port}" 2>$null) $noms = (kubectl get svc metriques -o jsonpath="{.spec.ports[*].name}" 2>$null) $cible = (kubectl get svc metriques -o jsonpath="{.spec.ports[*].targetPort}" 2>$null) $liste = ($ports -split '\s+') | Where-Object { $_ } if ($liste.Count -lt 2) { $note = "il manque un port : deux sont attendus (80 et 9090)" } elseif (-not $noms) { $note = "chaque port doit porter un nom lorsqu'il y en a plusieurs" } elseif ($cible -match '^\s*\d+(\s+\d+)*\s*$') { $note = "targetPort doit referencer les ports PAR LEUR NOM" } else { $p = 15 }}Afficher "Mission 4 - metriques" $p 15 $note; $total += $p# --- Mission 5 : paiement-externe -------------------------------------------$p = 0; $note = ""if (-not (Existe "paiement-externe")) { $note = "Service paiement-externe introuvable" }else { $type = (kubectl get svc paiement-externe -o jsonpath="{.spec.type}" 2>$null) $cible = (kubectl get svc paiement-externe -o jsonpath="{.spec.externalName}" 2>$null) if ("$type" -ne "ExternalName") { $note = "ce n'est pas le type attendu pour un alias DNS" } elseif (-not $cible) { $note = "la cible externe n'est pas renseignee" } else { $p = 10 }}Afficher "Mission 5 - paiement-externe" $p 10 $note; $total += $p# --- Mission 6 : les trois reparations --------------------------------------$p = 0; $notes = @()foreach ($cas in @( @{ nom = "api-commandes"; port = "8000" }, @{ nom = "cache"; port = "6379" }, @{ nom = "notifications"; port = "7000" })) { if (-not (Existe $cas.nom)) { $notes += "$($cas.nom) : Service introuvable"; continue } $eps = (kubectl get endpoints $cas.nom -o jsonpath="{.subsets[*].addresses[*].ip}" 2>$null) $tp = (kubectl get svc $cas.nom -o jsonpath="{.spec.ports[0].targetPort}" 2>$null) if (-not $eps) { $notes += "$($cas.nom) : Endpoints vides" } elseif ("$tp" -ne $cas.port) { $notes += "$($cas.nom) : aucun Pod ne repond sur ce port" } else { $p += 7 }}if ($p -gt 20) { $p = 20 }Afficher "Mission 6 - reparations" $p 20 ($notes -join " | "); $total += $pWrite-Host ""$couleur = if ($total -ge 90) { "Green" } elseif ($total -ge 50) { "Yellow" } else { "Red" }Write-Host ("SCORE AUTOMATIQUE : {0} / 95" -f $total) -ForegroundColor $couleurWrite-Host " (+5 pour la qualite du rapport, +5 de bonus : evalues manuellement)" -ForegroundColor DarkGrayWrite-Host ""Write-Host "Rappel : le tableau de bord doit afficher 8 / 8 sur http://localhost:30500" -ForegroundColor DarkGrayWrite-Host ""
Curso criado pelo Dr. Haythem REHOUMA — Desenvolvimento e implantação de soluções de dados