# Kube Tutorial Em contrução! ## Subindo os primeiros os Pods. [Ref](https://kubernetes.io/docs/concepts/workloads/pods) ### MongoDB Primeiramente vamos criar um POD do MongoDB, para isso criamos o arquivo `mongo.yaml` com o conteúdo abaixo: ```yaml= apiVersion: v1 kind: Pod metadata: name: mongo spec: containers: - name: mongo image: mongo:4.4.6 ports: - containerPort: 27017 env: - name: MONGO_INITDB_ROOT_USERNAME value: <username> - name: MONGO_INITDB_ROOT_PASSWORD value: <password> - name: MONGO_INITDB_DATABASE value: <database> ``` Em `kind` definimos qual componente do kubernetes vamos utilizar, nesse caso o `Pod` que representa nosso container, também definimos um nome `metadata.name` que poderia ser qualquer coisa. Dentro de `spec.containers` as configurações de nosso container, temos `name` que será o nome do nosso container, `image` que é o ID da imagem que irá baixar do [docker-hub](https://hub.docker.com/layers/library/mongo/4.4.6/images/sha256-d9ddf3df31941d30ee486ad5d2337283f0a02f68dbc5092319e24d3a541dbddd?context=explore), `ports.containerPort` será a porta que vamos expor nosso container, e em env podemos definir a variáveis de ambientes que irão carregar junto ao container. Então rodamos o comando abaixo para criar nosso Pod. ```bashrc= kubectl apply -f mongo.yaml ``` Aguardamos o status do Pod ser `Running` ```bashrc= kubectl get pods -w ``` Antes de testar nosso Pod precisamos ter acesso a ele, por padrão um Pod é isolado de qualquer comunicação externa ao nosso cluster mas podemos criar um acesso ao Pod utilizando um recurso chamado `port-forward` que irá criar uma ponte entre nosso Pod e nosso `localhost`. Para isso basta rodar: ```bashrc= kubectl port-forward mongo 27017 ```` Após isso podemos instalar o [monogsh](https://www.mongodb.com/docs/mongodb-shell/install/) E rodar o comando abaixo para testar a conexão: ```bashrc= mongosh "mongodb://localhost:27017" ``` Repare que executamos o mongosh utilizando `localhost` mas funcionou pois nosso port-forward irá encaminhar todas chamadas `localhost:27017` direto para nosso Pod, com isso temos acesso ao mongo ![](https://i.imgur.com/sEOtczj.png) ### API Após testar o mongodb, podemos criar um Pod para nossa API. Vamos criar o arquivo `api.yaml` assim: ```yaml= apiVersion: v1 kind: Pod metadata: name: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 ports: - containerPort: 8080 ``` As configurações são bem parecidas, mudamos apenas os `name`, `image` e a `containerPort` pois por padrão utilizamos `8080` para projetos Java e nesse caso nossa aplicação não precisa de envs diferente do mongo. Sendo assim criamos da mesma forma: ```bashrc= kubectl apply -f api.yaml ``` Após o pod subir podemos ver os logs do nosso Pod usando o comando abaixo: ```bashrc= kubectl logs -f api ``` Repare que o seguinte erro irá aparecer: ``` com.mongodb.MongoSocketOpenException: Exception opening socket at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:70) ~[mongodb-driver-core-4.1.2.jar!/:na] at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:143) ~[mongodb-driver-core-4.1.2.jar!/:na] at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.lookupServerDescription(DefaultServerMonitor.java:188) ~[mongodb-driver-core-4.1.2.jar!/:na] at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:144) ~[mongodb-driver-core-4.1.2.jar!/:na] at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na] Caused by: java.net.ConnectException: Connection refused (Connection refused) at java.base/java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:na] at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:412) ~[na:na] at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:255) ~[na:na] at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:237) ~[na:na] at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:na] at java.base/java.net.Socket.connect(Socket.java:609) ~[na:na] at com.mongodb.internal.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:78) ~[mongodb-driver-core-4.1.2.jar!/:na] at com.mongodb.internal.connection.SocketStream.initializeSocket(SocketStream.java:79) ~[mongodb-driver-core-4.1.2.jar!/:na] at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:65) ~[mongodb-driver-core-4.1.2.jar!/:na] ... 4 common frames omitted ``` Isso acontece pois por padrão nossa aplicação aponta para `127.0.0.1:27017` para conectar ao mongo mas cada Pod possuí seu proprio IP e não estão rodando dentro do mesmo container. Até podemos tentar apontar diretamente para o IP do Pod, para descobrir o IP basta rodar o comando abaixo e ver a tag `IP:` ```bashrc= kubectl describe pod mongo ``` Mas não temos a garantia que será sempre o mesmo IP, Pods podem ser destruídos e recriados por diversos motivos e assim mudarem seu IP. ![](https://i.imgur.com/Pu1N6X8.png) ### Comunicando os Pods com Service Vamos utilizar um outro componente do Kubernetes chamado `Service`, ele é uma maneira de expor nossa aplicação atravez de nossos de Pods. Vamos então criar um `service` para o Pod do `mongo` chamado `mongo-svc` no arquivo `mongo-svc.yaml`: ```yaml= apiVersion: v1 kind: Service metadata: name: mongo-svc spec: selector: app: mongo type: ClusterIP ports: - protocol: TCP port: 27017 targetPort: 27017 ``` Repare em `kind=Service`, agora não é mais um `Pod` e além disso temos outras configurações novas. Em `spec.type` dizemos qual o tipo de nosso serviço, o `ClusterIP` é o mais básico deles e e expoê acesso ao nosso serviço apenas dentro de nosso cluster atraves de rede privada. Já em `spec.ports` definimos em `port` por qual porta nosso serviço irá receber a comunicação e em `targetPort` para qual porta de nosso Pod será encaminhado. Além disso precisamos dizer quais Pods nosso serviço expor, podemos fazer isso com `labels`. No service configuramos um seletor em `spec.selector.app=mongo` para que encontre todos os Pods com a label `mongo`. Então agora precisamos editar nosso `mongo.yaml` colocando a label em `metadata.labels.name=mongo` ficando assim: ```yaml= apiVersion: v1 kind: Pod metadata: name: mongo labels: app: mongo spec: containers: - name: mongo image: mongo:4.4.6 ports: - containerPort: 27017 env: - name: MONGO_INITDB_ROOT_USERNAME value: ecommerce - name: MONGO_INITDB_ROOT_PASSWORD value: password - name: MONGO_INITDB_DATABASE value: ecommerce ``` Podemos executar os comandos para criar ou atualizar nossos Pods. ```bashrc= kubectl apply -f mongo.yaml kubectl apply -f mongo-svc.yaml ``` Podemos conferir rodando um describe: ```bashrc= kubectl describe service mongo-svc ``` Em nossa sáida vamos encontrar a tag `Endpoints` que vai ser `x.x.x.x:27017` exatamente o IP de nosso Pod do mongo. Agora o endpoint `mongo-svc:27017` já é visivel por outros Pods em nosso cluster e irá encaminhar as chamadas para nosso Pod, então vamos mudar o host padrão da nossa Api de `127.0.0.1` para `mongo-svc` mantendo a porta padrão `27017`. Para isso basta adicionar a variável ambiente `SPRING_DATA_MONGODB_HOST=mongo-svc` na configuração do nosso pod pois nossa aplicação já está preparada para ler ela [aqui](https://github.com/thiago-nascimento-ti/ecommerce-clean-architecture/blob/c67afb0aaec9e46eb5c114538b675fce231d016f/ecommerce-api/src/infrastructure/src/main/resources/application.properties#L3) ```yaml= apiVersion: v1 kind: Pod metadata: name: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_DATA_MONGODB_HOST value: mongo-svc - name: SPRING_DATA_MONGODB_PORT value: "27017" ``` Agora podemos testar, vamos fazer um `port-forward` para o Pod da `api` e fazer uma chamada para o endpoint que retorna todos os produtos. ```bashrc= kubectl port-forward api 8080 curl http://localhost:8080/api/v1/products ``` Agora nossa estrutura está assim: ![](https://i.imgur.com/cltHQX6.png) ### Persistindo nossos dados com StatefulSet Ainda temos um problema, nosso mongo foi criado usando o componente `Pod`, esse componente é `stateless` oque significa que não armazena dados. Sempre que ele reinicia tudo é recriado do zero, perdendo qualquer informação contida nele. Para simular isso, vamos parar o Pod `mongo`, iniciar novamente e fazer uma busca dos produtos. ```bash= kubectl delete pod mongo kubectl apply -f mongo.yaml curl http://localhost:8080/api/v1/products ``` Veja que nosso retorno será uma listagem vazia. Quem criou os produtos foi a aplicação `api` que foi desenvolvido para criar os produtos padrões caso não existam sempre ao inciar. Mas como reiniciamos somente o `mongo` tudo foi apagado e qualquer produto novo também seria perdido. Para evitar isso, podemos utilizar um componente chamado [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset) que por padrão persiste os dados em um volume fora do pod, podendo assim reiniciar ele sem problemas. Antes de criar nosso `StatefulSet`, vamos apagar o Pod `mongo` pois quem irá gerenciar o Pod `mongo` agora será o `StatefulSet`. ```bashrc= kubectl delete pod mongo ```` Agora criar o arquivo `mongo-sts.yaml` com o `Kind` agora sendo `StatefulSet`. Dentro de `template` colocamos as configurações de nosso `mongo.yaml` exceto por algumas tags como `apiVersion`, `kind` e `metadata.name`. Também temos a configuração `terminationGracePeriodSeconds` que determina quantos segundos deve esperar antes destruir nosso Pod em caso de restart. Além disso temos o `volumeMounts.name` que determina em qual volume devemos salvar nossos dados e com `volumeMounts.mountPath` determinamos onde devemos montar nosso volue, no caso do mongo precisamos apenas salvar o conteúdo de `/data/db`. O volume é um diretório persistente que fica em outra maquina e apenas criamos um atalho virtual. Para criar de fato nosso volume precisamos definir em `volumeClaimTemplates` aqui damos um nome que deve ser o mesmo em `volumeMounts.name`, também definimos tipo de acesso em `spec.accessModes`, o tamanho do nosso volume em `spec.resources.requests.storage` e por último onde deve ficar salvo em `spec.storageClassName`, cada cloud tem seu padrão e deve ser consultado na documentação, no Google Cloud podemos usar `standard` por padrão. ```yaml= apiVersion: apps/v1 kind: StatefulSet metadata: name: mongo-sts spec: selector: matchLabels: app: mongo serviceName: mongo-svc template: metadata: labels: app: mongo spec: terminationGracePeriodSeconds: 10 containers: - name: mongo image: mongo:4.4.6 ports: - containerPort: 27017 env: - name: MONGO_INITDB_ROOT_USERNAME value: ecommerce - name: MONGO_INITDB_ROOT_PASSWORD value: password - name: MONGO_INITDB_DATABASE value: ecommerce volumeMounts: - name: mongodb-persistent-storage mountPath: /data/db volumeClaimTemplates: - metadata: name: mongodb-persistent-storage spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "standard" resources: requests: storage: 1Gi ``` Então basta criar o `StatefulSet` ```bashrc= kubectl apply -f mongo-sts.yaml ``` Agora temos a seguinte estrutura ![](https://i.imgur.com/INljcWV.png) ### Expondo nossa api para o mundo Até agora nossa `api` está acessível apenas na rede privada fazendo um port-forward, mas em um mundo real o client não acesso a rede do nosso cluster, então precisamos dar acesso atrávez de um IP publico. Para isso vamos criar um `Service` do tipo `NodePort` no arquivo `api-svc.yaml` que diferente do tipo `ClusterIP`, o `NodePort` expoê nosso servico atravez do IP publico do cluster, sendo acessível por clients externo ao cluster. ```yaml= apiVersion: v1 kind: Service metadata: name: api-svc spec: selector: app: api type: NodePort ports: - protocol: TCP port: 8080 targetPort: 8080 nodePort: 30080 name: http ``` Repare que nos já colocamos o seletor `api` em nosso serviço, então para ele encontrar nosso pod, precisamos colocar a label `api` em nosso pod também, vamos alterar o arquivo `api.yaml` ```yaml= apiVersion: v1 kind: Pod metadata: name: api labels: app: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_DATA_MONGODB_HOST value: mongo-svc - name: SPRING_DATA_MONGODB_PORT value: "27017" ``` E aplicar as modificações ```bashrc= kubectl apply -f api.yaml kubectl apply -f api-svc.yaml ``` Aqui estamos criando um redirect entre `api-svc:<port>` para `api:<targetPort>` mas que serve apenas dentro do cluster, entre nossos pods, para chamadas externas precisamos usar `<clusterIP>:<nodePort>` Podemos chamar o comando abaixo para verificar o IP de nosso cluster e verificar a saída `EXTERNAL-IP` ```bashrc= kubectl get nodes --output wide ``` Além disso precisamos criar uma regra de firewall para acessar a porta de nosso serviço Podemos verificar se já não existe alguma regra para a porta `30080` assim: ```bashrc= gcloud compute firewall-rules list ``` E caso não possuír, criamos: ```bashrc= gcloud compute firewall-rules create api-svc-node-port --allow tcp:30080 ``` Agora nossa api está acessível e podemos testar assim: ```bashrc= curl http://<EXTERNAL-IP>:30080/api/v1/products ``` ![](https://i.imgur.com/q5LyCC1.png) ### Escalando nossa applicação Agora que nossa `api` já está acessível publicamente, podemos pensar em como escalar nossa aplicação, lógico que em nosso cenário fictício não teria essa necessidade, mas no dia a dia com a aplicações recebendo muitas requisições pode ser necessário balencear a carga entre duas ou mais instâncias. Antes de falarmos sobre autoscale vamos entender um pouco melhor sobre os recursos solicitados e utilizados por um `Pod` e como escalar de forma vertical. Até agora nos não definimos quanto recurso nosso `Pod` utilizaria, por padrão ele vai solicitando conforme necessário, podemos observar com o comando `top` ```bashrc= kubectl top pod api ``` Aqui podemos observar que nossos Pods estão utilizando 3m, que significa `miliCPUs`, sendo `1000m = 1 CPU`. ![](https://i.imgur.com/s1x9UNX.png) Por default nossos Nodes foram criados com capacidade de `2 CPUs` por Node, como temos 3 Nodes nesse pool, conseguimos utilizar até `6 CPUs` para nossos Pods ```bashrc= kubectl get nodes kubectl describe node <node-name> ``` ![](https://i.imgur.com/JK47gWY.png) Como não informamos o limite de recursos que nosso `Pod` deve utilizar, ele vai escalando esses recursos conforme o necessário e disponível em nossos nodes, isso é caracterizado com uma escalabilidade vertical. Embora seja uma forma valida de escalar a capacidade de nossa aplicação, em alguns cenários somente escalar `CPU` ou `memória` não serão o suficiênte para aumentar a capacidade de nossos serviços, pois outras limitações podem existir, como no caso do `Java` onde temos o limite de Threads por aplicação. Pensando nessas limitações podemos limitar o quanto um Pod deve escalar seus recursos, para isso podemos adicionar a tag `resources` Dentro da tag definimos `requests` e `limits` que determinam quanto recurso o `Pod` deve requisitar inicialmente, e qual vai ser o limite maxímo de CPU e memória. ```yaml= apiVersion: v1 kind: Pod metadata: name: api labels: app: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 resources: requests: memory: "128Mi" cpu: 1m limits: memory: "512Mi" cpu: 0.5 ports: - containerPort: 8080 env: - name: SPRING_DATA_MONGODB_HOST value: mongo-svc - name: SPRING_DATA_MONGODB_PORT value: "27017" ``` Vamos aplicar: ```bashrc= kubectl delete pod api kubectl apply -f api.yaml ``` O limite do CPU define qual a fatia de tempo de CPU nosso container pode utilizar, durante os ciclos de execução é verificado se já excedeu o limite e sendo o caso a execução aguardará. Já se exceder a solicitação de mémoria nosso `Pod` pode ser reiniciado. ### Escalando horizontalmente com ReplicaSet. Diferente do tópico acima, a ideia aqui é escalar horizontalmente, ou seja criar novos `Pod` para dividirem a demanda em vez de adicionar mais recursos para um único `Pod` Para isso vamos utilizar o componente chamado `ReplicaSet` que é responsável por gerênciar as replicas de nossos pods. Criaremos então um arquivo chamado `api-rps.yaml` com o seguinte conteúdo. ```yaml= apiVersion: apps/v1 kind: ReplicaSet metadata: name: api-rps labels: app: api spec: replicas: 2 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 resources: requests: memory: "128Mi" cpu: 1m limits: memory: "512Mi" cpu: 0.5 ports: - containerPort: 8080 env: - name: SPRING_DATA_MONGODB_HOST value: mongo-svc - name: SPRING_DATA_MONGODB_PORT value: "27017" ``` Aqui usamos o `kind=ReplicaSet`, damos um nome a ele em `metadata.name` e em `spec.replicas` decimos quantas instâncias de nossos pods vamos ter. Já em `spec.template.spec` utilizamos o mesmo contéudo do arquivo `api.yaml` onde configuramos nosso Pod incialmente, pois é aqui onde fica a especificação utilizada para criar os pods. Então apagamos o Pod `api` e criamos o ReplicaSet. ```bashrc= kubectl delete pod api kubectl apply -f api-rps.yaml ``` Nosso ReplicaSet irá criar dois Pods nomeados de `api-rps-xxxxx` e se olharmos em nosso `api-svc` ele já mapeou os dois na saída `Endpoints`, graças as labels que colocamos na spec do template. ```bashrc= kubectl describe service api-svc ``` Para testar primeiro vamos pegar o nome dos Pods ```bashrc= kubectl get pods | grep api-rps ``` E em terminais separados vamos visualizar os logs de ambos rodando o comando abaixo para cada Pod substituindo `xxxxx` pelo nome correto visto com o comando acima. ```bashrc= kubectl logs -f api-rps-xxxxx ``` E agora em um terceiro terminal vamos chamar o endpoint de produto algumas vezes em loop: ```bashrc= while true; do curl http://<EXTERNAL-IP>:30080/api/v1/products; echo -e '\n\n'$(date);done ``` Podemos ver que ele distribui as chamadas entre os Pods automaticamente. Agora que temos um ReplicaSet configurado podemos fácilmente escalar nossa aplicação apenas executando ```bashrc= kubectl scale --replicas=8 replicaSet/api-rps ``` Esse comando irá aumentar para 8 replicas criando 6 novos pods automaticamente, isso permite integrar com ferramentas de autoscalling que podem apenas rodar esse comando quando necessário escalando nossas aplicações conforme necessário, um exemplo de ferramenta é o [Keda](https://keda.sh/). ### Configurando autoscale Agora que já temos um ReplicaSet configurado além de escalar manualmente como fizemos acima, podemos criar uma `Horizontal Pod Autoscaler` que irá escalar nossos `Pods` automaticamente. Para fazer isso podemos rodar o comando `autoscale` para criar no HPA automaticamente com todos os seguintes parâmetros ```bashrc= kubectl autoscale replicaSet api-rps --max=10 --min=3 --cpu-percent=50 ``` Aqui no terceiro parâmetro definimos o tipo do componente. Como nossos Pods da `api` estão sendo gerênciados por um `replicaSet` informamos ele, mas poderia ser um `deployment` que vamos ver mais a frente. Já no quarto parâmetro vai o nome do nosso `replicaSet` defido em `metadata.name`. Em `max`e `min` vão as quantidades de Pods junto com o parâmetro `cpu-percent` que é usado no algoritimo para definir quantos `Pods` devem estar ativos. Mas também podemos criar o `yaml` e rodar um `apply` como temos feito. ```yaml= apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: hpa-api spec: scaleTargetRef: apiVersion: apps/v1 kind: ReplicaSet name: api-rps minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 50 ``` ```bashrc= kubectl apply -f hpa-api.yaml ``` As tags tem nomes um pouco diferentes dos parâmetros no comando acima mas possuem a mesma função. Agora que temos nosso `HPA` funcionando, precisamos entender como funciona o algoritimo para escalabilidade dos `Pods` Em nosso replicaSet definimos o limite de `CPU` sendo `0.5` ou `500m`, também a porcentagem ideal de uso para nossos `Pods` em `50%` sendo no mínimo 3 `Pods` e o máximo 10 `Pods`. Isso quer dizer que nosso `desiredMetricValue` é `250m` equivalente a `50%` dos `500m` de limite, então se tivermos 3 `Pods` rodando com `700`, `850m` e `530m` será calculado a média dos tres, esse valor é chamdo de `currentMetricValue`. Com isso é feito o seguinte calculo ```javascript= desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )] ``` Em nosso exemplo ```javascript= 9 = ceil[3 * ( 693 / 250 )] ``` Repare que temos um `ceil` que arredonda para cima, em nosso caso teria dado `8.316` mas virou `9`. Esse é o valor de `Pods` desejado, sendo assim o `HPA` irá executar um scale para 9 `Pods` mas nunca deve ultrapassar nosso `maxReplicas` ou ficar menor que o `minReplicas`. Caso nosso `currentMetricValue` caia para um valor menor como `150m` nosso `desiredMetricValue` iria ficar em 6 considerando estar atualmente com 9 `Pods` então um scaledown iria acontecer. ```javascript= 6 = ceil[9 * ( 150 / 250 )] ``` Ref [Algoritimo de autoscale](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details) ### Deployment (Rollout, rollback e histórico de deploys) Utilizando o ReplicaSet, adicionamos recursos para controle de escalabilidade, mas ainda não temos uma boa gestão de nossos Pods quando se trata de subir novas versões de nossas aplicações pois esse tipo de funcionalidade não é responsabilidade do ReplicaSet, podemos ver esse problema assim: Primeiro vamos criar uma nova versão da nossa `ecommerce-api` criando a tag 1.0.1 com o seguinte comando. ```bashrc= docker build -t <username>/ecommerce-api:1.0.1 . ``` Então publicamos para o docker-hub ```bashrc= docker push <username>/ecommerce-api:1.0.1 ``` Rodando o comando podemos ver o hash da nossa imagem para garantir ```bashrc= docker inspect <username>/ecommerce-api:1.0.1 | grep <username>/ecommerce-api@sha256 ``` ![](https://i.imgur.com/UtQwrDP.png) Apos isso vamos mudar a versão da imagem ```bashrc= kubectl set image replicaSet/api-rps api=thsnascimento/ecommerce-api:1.0.1 ``` Podemos dar um describe e ver que a versão da imagem mudou e agora está `thsnascimento/ecommerce-api:1.0.1` ```bash= kubectl describe replicaSet api-rps | grep Image ``` ![](https://i.imgur.com/A65ou7D.png) Mas se observarmos os pods, eles ainda vão estar com a versão `thsnascimento/ecommerce-api:1.0.0` inclusive com o hash diferente do gerado acima. ```bash= kubectl describe pods -l app=api | grep Image ``` ![](https://i.imgur.com/p1e4IE1.png) Ou seja mesmo forçando um update na versão da imagem, nosso ReplicaSet não atualiza nossos Pods a não ser que sejam recriados novamente. Podemos rodar `kl delete pod` com o `-l` usando a label `app=api` para deletar todos os pods com essa label, isso fará o ReplicaSet recriar novos logo em seguida e então confirmaos a imagem com um describe. ```bash= kubectl delete pod -l app=api kubectl describe pods -l app=api | grep Image ``` ![](https://i.imgur.com/lzg5aYQ.png) Agora nossos novos Pods foram criados utilizando a última imagem configurada, mas isso pode ter criado alguns segundos de instabilidade, pois apagamos nossos Pods ficamos sem nenhum e logo após foram criados novos. Podemos evitar isso utilizando o `Deployment` além de gerenciar nosso `ReplicaSet` subindo um novo antes apagar o antigo, também temos histórico de versões, rollout em caso de problemas e outros recursos úteis. Para fazer essa mudança, vamos alterar o `kind=Deployment` do nosso arquivo `api-rps.yaml`, também renomear para `api-dpl.yaml` e mudar a propriedade `metadata.name=api-dpl` só para deixar mais semántico, também vamos voltar a imagem para `thsnascimento/ecommerce-api:1.0.0` que irá nos permitir simular novamente nosso upgrade e vermos a diferença, todas as demais configurações são iguais. ```yaml= apiVersion: apps/v1 kind: Deployment metadata: name: api-dpl labels: app: api spec: replicas: 2 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 resources: requests: memory: "128Mi" cpu: 1m limits: memory: "512Mi" cpu: 0.5 ports: - containerPort: 8080 env: - name: SPRING_DATA_MONGODB_HOST value: mongo-svc - name: SPRING_DATA_MONGODB_PORT value: "27017" ``` Após isso vamos apagar nosso ReplicaSet e criar nosso Deployment ```bashrc= kubectl delete ReplicaSet api-rps kubectl apply -f api-dpl.yaml ``` Também precisamos ajustar no `HPA` criado especificamente para um `ReplicaSet`, nesse caso precisamos só ajustar o `scaleTargetRef.name` e o `scaleTargetRef.kind` já que o nome e o tipo do `Deployment` é diferente do `ReplicaSet` ```yaml= apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: hpa-api spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-dpl minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 50 ``` ```bashrc= kubectl delete hpa hpa-api kubectl apply -f hpa-api.yaml ``` Agora podemos mudar a versão para que o Deployment faça o rollout automaticamente, aqui vamos usar o comando `--record=true` para salvar o comando executado no histórico de rollouts ```bashrc= kubectl set image deployment/api-dpl api=thsnascimento/ecommerce-api:1.0.1 --record=true ``` ![](https://i.imgur.com/z6DjVsy.png) Se em outro terminal deixamos `kl get pods -w` para observar os pods antes de rodar o comando acima, podemos ver que o deployment começou subir o Pod com final `jgm27` e após estar `Ready=1/1` o Pod `9bqbl` que está com a versão anterior começa a ser destruído, e logo abaixo outro Pod `xpnlz` começa subir e temos o mesmo processo com o último Pod antigo acontecendo após ele estar pronto. Isso quer dizer que só de mudarmos a versão, nosso `Deployment` substituiu nossos Pods com a versão antigas pouco a pouco sem deixar nossa aplicação fora do ar. Para ver o histórico de nossos rollout podemos executar o seguinte comando ```bashrc= kubectl rollout history deployment/api-dpl ``` Caso necessário podemos fazer o rollback do rollout utilizando o `rollout undo`, isso irá voltar exatamente para versão anterior ```bashrc= kubectl rollout undo deployment/api-dpl ``` Ou escolher uma revision específica usando `--to-revision` ```bashrc= kubectl rollout undo deployment/api-dpl --to-revision=1 ``` ![](https://i.imgur.com/mLLUNqJ.png) Por último precisamos atualizar nosso `HPA` pois agora estamos utilizando um deployment em vez de replicaSet. Vamos apagar o atual ```bashrc= kubectl delete hpa hpa-api ``` Então podemos criar com linha de comando, mas agora informando ser um `deployment` com o novo nome `api-dpl` ```bashrc= kubectl autoscale deployment api-dpl --max=10 --min=3 --cpu-percent=50 ```` Ou criar o `yaml` também corrigindo o `kind` e `name` ```yaml= apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: hpa-api spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-dpl minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 50 ``` ```bashrc= kubectl apply -f hpa-api.yaml ``` ## ConfigMaps e Secrets (WIP) ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: mongo data: MONGODB_HOST: "mongo-svc" MONGODB_PORT: "27017" ``` ```yaml= apiVersion: apps/v1 kind: Deployment metadata: annotations: kubernetes.io/change-cause: "$(date -d now '+%Y/%m/%d %H:%M:%S %z')" name: api-dpl labels: app: api spec: replicas: 2 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: thsnascimento/ecommerce-api:1.0.0 resources: requests: memory: "128Mi" cpu: 1m limits: memory: "512Mi" cpu: 0.5 ports: - containerPort: 8080 env: - name: SPRING_DATA_MONGODB_HOST valueFrom: configMapKeyRef: name: mongo key: MONGODB_HOST - name: SPRING_DATA_MONGODB_PORT valueFrom: configMapKeyRef: name: mongo key: MONGODB_PORT ``` ```bashrc= kubectl describe pod api-dpl-xxxxxxxxxx-xxxxx ``` ![](https://i.imgur.com/bpZFiG9.png) ## Próximos tópicos: 1. Deployment a. Strategy tag 2. CronJob e Job 3. Change-cause ![Arquitetura](https://i.imgur.com/TpEyb8d.png) [Services types](https://cloud.google.com/kubernetes-engine/docs/concepts/service) [Components](https://kubernetes.io/docs/concepts/workloads/controllers/) [Expose Pods](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/) [Draw.io](https://app.diagrams.net/#G1WZq1Pn9RBB5ielzW0ECG1txVvq67HGvR)