Iniciando com Kubernetes
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.
- Crie o diretório e dentro dele crie este
Dockerfile
:
from ruby
run gem install sinatra
copy app.rb /app.rb
entrypoint ["ruby"]
- 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
- 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 ominikube tunnel
e rode:minikube tunnel --cleanup
control
+c
se você tiver rodado ominikube 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
- 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
- Use secrets para armazenar variáveis de ambiente de sensíveis
- 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/