Eu tive que configurar Kubernetes no Google Cloud na pressa. Este aqui é um passo a passo para você configurar kubernetes na sua própria máquina e aprender alguns dos conceitos básicos.

Primeiro de tudo instale kubectl e minikube.

Crie um Cluster

O primeiro passo é criar um cluster.

Até agora eu só vi criar um cluster fora do kubectl e só então configurar o kubectl para acessar aquele cluster. Se você usa Google Cloud eu recomendo que você tenha o gcloud instalado também. Nesse exemplo usamos o minikube para criar o cluster, ele também configura o kubectl para você.

Você pode achar várias definições sobre o que é um cluster, mas eu prefiro essa:

Um cluster é apenas um grupo de máquinas juntas. (Na maioria das vezes eles são do mesmo tipo e podem ser apagadas sem problema algum)

Você precisa de um cluster mesmo se você for usar na sua própria máquina. E no caso do minikube essa é uma máquina no VirtualBox.

Crie o cluster:

$ minikube start
🎉  minikube 1.7.3 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.7.3
💡  To disable this notice, run: 'minikube config set WantUpdateNotification false'

🙄  minikube v1.7.2 on Arch rolling
✨  Automatically selected the virtualbox driver. Other choices: none, docker (experimental)
💿  Downloading VM boot image ...
    > minikube-v1.7.0.iso.sha256: 65 B / 65 B [--------------] 100.00% ? p/s 0s
    > minikube-v1.7.0.iso: 166.68 MiB / 166.68 MiB [-] 100.00% 29.20 MiB p/s 6s
🔥  Creating virtualbox VM (CPUs=2, Memory=2000MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.17.2 on Docker 19.03.5 ...
💾  Downloading kubectl v1.17.2
💾  Downloading kubelet v1.17.2
💾  Downloading kubeadm v1.17.2
🚀  Launching Kubernetes ...
🌟  Enabling addons: default-storageclass, storage-provisioner
⌛  Waiting for cluster to come online ...
🏄  Done! kubectl is now configured to use "minikube"
minikube start  13.39s user 17.70s system 23% cpu 2:09.66 total

Depois de iniciar rode este comando em outro terminal e deixe-o aberto;

$ minikube tunnel

Agora você está pronto para usar o kubectl!

Concepts

  • Cluster: máquinas juntas para rodar os containers (de forma geral você não diz qual máquina vai rodar qual container)
  • Node (Nó): uma máquina dentro do cluster, por exemplo, uma VM no Computer Engine (Google Cloud) ou um Droplet (Digital Ocean)
  • Pod: a documentação original diz que essa é a menor unidade num cluster. Eu prefiro dizer que esse pod é um container rodando (ou seja, um docker run)

Fazendo deploy do primeiro container

Para esse tutorial eu não vou me conectar com um banco de dados, ao invês eu vou fazer o deploy de um container stateless.

O único informação que o container vai pegar fora dele mesmo é uma ENV var. (E se você tiver feito deploy recentemente sabe que você pode se conectar com um banco de dados usando uma ENV var 😉).

Contruindo a imagem

NOTA: Se você não quiser construir a imagem ignore este passe e use a minha.

Estou criando um código similar ao https://cloud.google.com/kubernetes-engine/docs/quickstarts/deploying-a-language-specific-app

Primeiro nós vamos construir a imagem, lembre-se que Kubernetes roda containers.

  1. Crie o diretório e dentro dele crie este Dockerfile:
from ruby

run gem install sinatra
copy app.rb /app.rb
entrypoint ["ruby"]
  1. Crie o arquivo app.rb:
require "sinatra"

set :bind, "0.0.0.0"
set :port, ENV["PORT"] || "8080"

get "/" do
  target = ENV["TARGET"] || "World"
  "Hello #{target}!\n"
end
  1. Crie a imagem:
$ docker build -t hello-target .
Sending build context to Docker daemon  3.072kB
Step 1/4 : from ruby
 ---> 2ff4e698f315
Step 2/4 : run gem install sinatra
 ---> Running in 7d64263bd742
Successfully installed rack-2.2.2
Successfully installed tilt-2.0.10
Successfully installed rack-protection-2.0.8.1
Successfully installed ruby2_keywords-0.0.2
Successfully installed mustermann-1.1.1
Successfully installed sinatra-2.0.8.1
6 gems installed
Removing intermediate container 7d64263bd742
 ---> 4b5946a34e1d
Step 3/4 : copy app.rb /app.rb
 ---> 4b84ef5972c6
Step 4/4 : cmd ["ruby", "/app.rb"]
 ---> Running in 827bc270be8a
Removing intermediate container 827bc270be8a
 ---> e12e46b470d6
Successfully built e12e46b470d6
Successfully tagged hello-target:latest

Enviando a imagem para o servidor

NOTA: Novamente, se você não quiser enviar sua imagem para o servidor use a minha 😉.

O nó principal baixa a imagem do registro.

Para este passo eu vou enviar a imagem para a minha conta do Docker Hub e deixar a imagem pública. Num deploy real você tem que usar autenticação.

Envie a imagem:

$ docker tag hello-target dmitryrck/hello-target
$ docker push dmitryrck/hello-target
The push refers to repository [docker.io/dmitryrck/hello-target]
746b10eafa9d: Pushed
31bde95e4b34: Pushed
3432f61a06d4: Mounted from dmitryrck/ruby
38a0b0e0037c: Mounted from dmitryrck/ruby
2dba91c4f4b7: Mounted from dmitryrck/ruby
9437609235f0: Mounted from dmitryrck/ruby
bee1c15bf7e8: Mounted from dmitryrck/ruby
423d63eb4a27: Mounted from dmitryrck/ruby
7f9bf938b053: Mounted from dmitryrck/ruby
f2b4f0674ba3: Mounted from dmitryrck/ruby
latest: digest: sha256:a8fc5d52012f61807e44ded0e18cfb8054662548926f51d4df709d351ca496f6 size: 2420

ProTip: Você pode construir a sua imagem usando algo como docker build -t YOURUSERNAME/hello-target para evitar ter que taggear a imagem novamente 😊.

Configurando a variável de ambiente

Primeiro vamos criar as variáveis de ambiente.

Crie este arquivo configmap.yml:

apiVersion: v1
kind: ConfigMap

metadata:
  name: my-app-configmap
  namespace: default

data:
  TARGET: "you!"
  PORT: "3000"

E aplique essa configuração.

$ kubectl apply -f configmap.yml
configmap/my-app-configmap created

Quando você aplica uma configuração você basicamente diz para o node principal fazer aquela mudança.

Configurando o deploy

Este é o mais complexo passo/arquivo deste tutorial. A principal razão para isso é que a maioria dos outros deploys vai se basear nesse. As principais características aqui são:

  • Use variáveis de ambiente (assim você pode ignore o arquivo das variáveis e deixar somente um exemplo, se você vai armazenar isso no git)
  • Foca Kubernetes a verificar se a imagem que usa é a última toda vez que fizer um deploy (acredito que esse seja o comportamento padrão nas últimas versões)
  • Adiciona argumentos no caso de você querer reusar a imagem para mais de um tipo de deploy
  • Expõe uma porta. Lembre-se de não expor uma porta se o seu container não precisa aceitar requisião do mundo exterior
  • Duas réplicas do mesmo deploy
  • Health check
  • E, por que temos um health check, zero downtime deployment (mesmo se você tiver só uma relpica)

Crie o arquivo puma-deploy.yml:

apiVersion: apps/v1
kind: Deployment

metadata:
  name: puma-deploy

spec:
  replicas: 2
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  selector:
    matchLabels:
      app: puma

  template:
    metadata:
      labels:
        app: puma

    spec:
      containers:
      - name: puma
        image: dmitryrck/hello-target
        imagePullPolicy: Always
        args: ["app.rb", "-e", "production"]
        envFrom:
        - configMapRef:
            name: my-app-configmap
        ports:
        - containerPort: 3000
        readinessProbe:
          initialDelaySeconds: 5
          periodSeconds: 30
          httpGet:
            port: 3000
            path: /

E aplique o deploy:

$ kubectl apply -f puma-deploy.yml
deployment.apps/puma-deploy created

Se você percebeu esse comando termina extremamente rápido, a razão disso é:

kubectl não faz o trabalho de fazer o deploy, ao invés disso diz para o master node fazer o deploy, se qualquer erro acontecer você só vai saber que aconteceu quando perguntar para o master node.

Um dos jeitos de perguntar se o deploy foi feito com sucesso ou não é assim:

$ kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
puma-deploy-7ccfbcd57c-8fcd4   1/1     Running   0          74s
puma-deploy-7ccfbcd57c-zm7w2   1/1     Running   0          74s

O pod está rodando quando o status é Running.

Exibir o deploy com um Load Balance

O seu container está rodando e o único a saber disso é o master node.

Para expor para o mundo exterior você precisa criar um serviço:

Crie o arquivo load-balance.yml:

apiVersion: v1
kind: Service

metadata:
  name: puma-lb
  labels:
    app: puma

spec:
  type: LoadBalancer
  selector:
    app: puma
  ports:
  - port: 80
    targetPort: 3000
    protocol: TCP

E inicie o seu serviço:

$ kubectl apply -f load-balance.yml
service/puma-lb created

Mais uma vez, esse comando termina extremamente rápido, pelas mesmas razões que antes, kubectl somente diz o master node para criar o serviço.

Se você for pedir uma lista de serviço esse serviço estará como pendente:

$ kubectl get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP        23m
puma-lb      LoadBalancer   10.97.214.203   <pending>     80:30783/TCP   1s

Depois que o seu provedor (no nosso caso minikube) terminar de criar você verá algo como:

$ kubectl get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>          443/TCP        8h
puma-lb      LoadBalancer   10.97.214.203   10.97.214.203   80:30783/TCP   7h57m

Parando e limpando tudo

Para remover tudo:

  • control+c com o minikube tunnel e rode: minikube tunnel --cleanup
  • control+c se você tiver rodado o minikube dashboard
  • E chame esses dois comandos:
$ minikube stop
✋  Stopping "minikube" in virtualbox ...
🛑  "minikube" stopped.
$ minikube delete
🔥  Deleting "minikube" in virtualbox ...
💀  Removed all traces of the "minikube" cluster.

#Protip

  1. Se você atualizar alguma variável de ambiente OU atualizar a imagem, você tem que fazer um deploy novamente. Mas kubernetes só faz o deploy novamente se você atualizar o arquivo YAML. Use essa dica para forçar um deploy
  2. Use secrets para armazenar variáveis de ambiente de sensíveis
  3. Rode este comando para acessar um painel legal (com o minikube):
$ minikube dashboard --url=true
🤔  Verifying dashboard health ...
🚀  Launching proxy ...
🤔  Verifying proxy health ...
http://127.0.0.1:34175/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/