SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor

k3s em um VPS: quando vale a pena usar

Veja quanto de RAM o k3s consome, por que a porta 80 pode causar conflito na instalação e quando o Docker Compose continua sendo a melhor opção.

O que é o k3s e o que um nó oferece

O k3s é uma distribuição completa do Kubernetes empacotada como um único binário. Executá-lo num único VPS fornece a API real do Kubernetes sem um control plane com três máquinas. É uma distribuição Kubernetes certificada. Por isso, um manifest aplicado aqui também pode ser aplicado mais tarde num cluster gerido. A instalação requer um comando e demora cerca de um minuto. O custo é a memória que deixa de estar disponível para as aplicações, além de um conjunto de modos de falha que não existem com o Docker Compose.

A SUSE desenvolve o k3s para sites edge e instalações pequenas. Cada diferença em relação ao Kubernetes upstream existe para o tornar menor. O datastore predefinido é o sqlite, através de um shim chamado kine, e não o etcd. Assim, não é necessário manter um quorum do etcd. O containerd está incorporado no binário, em vez de ser instalado separadamente. O mesmo binário também inclui o CoreDNS para o DNS do cluster, o Traefik como controlador de ingress, o ServiceLB (também chamado klipper-lb) para que os serviços LoadBalancer funcionem sem um cloud provider, o local-path provisioner para volumes persistentes, o metrics-server e o flannel para a rede dos pods. Todos esses componentes arrancam por predefinição. Por isso, a colisão de portas descrita abaixo é o primeiro problema mais comum num VPS que já estava a executar outros serviços.

Quando vale a pena usar um único nó k3s

Siga esta regra. Use k3s quando a API do Kubernetes for o que pretende: para aprender Kubernetes numa máquina que controla ou quando o software que pretende usar só disponibiliza um Helm chart. A portabilidade dos manifestos também conta, porque um Deployment criado aqui pode ser transferido para um cluster gerido sem alterações. Use Docker Compose quando as aplicações forem o que pretende executar. O Compose inicia os mesmos contentores com muito menos componentes, e um ficheiro Compose num VPS é mais fácil de ler um ano depois do que um diretório de manifestos.

Tenha em conta o que um único nó não oferece.

  • Não oferece alta disponibilidade. Quando o VPS reinicia, todas as cargas de trabalho param. O Kubernetes reagenda um pod noutro nó, mas não existe outro nó.
  • Não oferece uma atualização contínua que mantenha um serviço ativo, exceto quando a aplicação suporta duas réplicas na mesma máquina a partilhar um volume.
  • O armazenamento fica associado à máquina, pelo motivo descrito na secção sobre local-path abaixo.
  • O plano de controlo consome cerca de um gigabyte de RAM, independentemente de implementar alguma coisa.

Nada disso torna o k3s uma má escolha. Torna-o uma má escolha quando o motivo é aquele que as pessoas normalmente apresentam: fiabilidade. Se o que realmente pretende são várias máquinas para criar um cluster real com vários nós, essa decisão vem primeiro: Proxmox no seu próprio hardware ou num VPS alugado define de onde vêm os nós antes de o k3s definir o que é executado neles.

O custo de k3s em RAM e CPU antes de implementar qualquer coisa

O projeto k3s publica valores medidos, não estimativas. Leia-os com atenção, porque o valor que as pessoas citam não corresponde ao k3s em estado ocioso.

ChartPublished k3s resource use, 95th percentile, Intel 8375C, k3s v1.26.5
The data behind this chart
[
  {
    "label": "Server, sqlite datastore",
    "ram_mb": "1,596",
    "cpu_percent_of_one_core": 6
  },
  {
    "label": "Server, embedded etcd",
    "ram_mb": "1,606",
    "cpu_percent_of_one_core": 6
  },
  {
    "label": "Agent node only",
    "ram_mb": "275",
    "cpu_percent_of_one_core": 3
  }
]

Um nó server nesse teste utilizou 1,596 MB de RAM no percentil 95 e cerca de 6 por cento de um core. Estes são valores publicados, não medições deste guia. O teste executou o k3s v1.26.5 com todos os componentes incluídos ativados, além de uma stack de monitorização com Prometheus e Grafana. Por isso, o valor inclui uma carga de trabalho real, não um cluster vazio. Substituir sqlite por embedded etcd aumentou o valor para 1,606 MB. Um nó agent, que executa kubelet e containerd sem o control plane, utilizou 275 MB. O mínimo documentado para um server é de 2 cores e 2 GB de RAM. Esse mínimo abrange o k3s e os componentes incluídos antes das suas cargas de trabalho.

A leitura prática é a seguinte: num VPS com 2 GB, o control plane e os add-ons incluídos deixam muito pouca memória disponível. Sob pressão, a primeira coisa que acontece é o kubelet expulsar pods. 4 GB é um valor mínimo confortável para um nó com alguns serviços pequenos. Meça o seu próprio servidor em vez de confiar em qualquer valor publicado, incluindo este.

free -h
sudo k3s kubectl top node
sudo k3s kubectl get pods -A

Execute free -h antes da instalação e novamente quando todos os pods em kube-system apresentarem o estado Running. A diferença corresponde ao custo do control plane no seu hardware. k3s kubectl top node devolve error: Metrics API not available durante o primeiro ou os primeiros dois minutos após a instalação, porque o metrics-server ainda não recolheu dados. Isso não indica uma falha. Se estiver a dimensionar um servidor para executar isto e também outras cargas de trabalho em simultâneo, os cálculos em dimensionar a RAM e a CPU de um VPS aplicam-se aqui sem alterações.

Instale o k3s fixado numa release, não em latest

A linha de início rápido que toda a gente copia usa o que o canal stable indicar no dia em que a executar. Numa máquina que pretende manter, fixe a versão. O k3s publica um canal por versão minor do Kubernetes, por isso INSTALL_K3S_CHANNEL=v1.36 acompanha as releases de patch dentro de v1.36 e nunca avança para outra versão minor sem a sua intervenção. Em agosto de 2026, o canal stable aponta para v1.36.3+k3s1.

Escreva primeiro o ficheiro de configuração e só depois instale. O k3s lê /etc/rancher/k3s/config.yaml quando arranca, por isso tudo o que estiver nesse ficheiro aplica-se tanto ao primeiro arranque como aos seguintes.

sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'EOF'
tls-san:
  - k3s.example.com
EOF
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=v1.36 sh -

Para fixar uma release exata em vez de um canal, use INSTALL_K3S_VERSION=v1.36.3+k3s1. O sinal de mais faz parte da tag. Depois, confirme que o serviço arrancou.

k3s --version
sudo systemctl status k3s
sudo k3s kubectl get node
sudo k3s kubectl get pods -A

kubectl get node deve listar um nó com STATUS Ready ao fim de cerca de trinta segundos, e todos os pods em kube-system devem atingir o estado Running ou Completed. Um nó preso em NotReady normalmente indica que o runtime de contentores nunca arrancou, por isso consulte sudo journalctl -u k3s -n 100 --no-pager. Numa imagem VPS incomum, execute sudo k3s check-config antes de investigar qualquer outra coisa: o comando indica as funcionalidades do kernel em falta, dando uma resposta muito mais rápida do que a leitura dos logs.

Por que a porta 80 já está em uso e ao que deve renunciar para resolver o problema

Esta é a falha que apanha utilizadores num VPS que já estava a servir alguma coisa. A instalação é concluída. Depois, o Traefik nunca obtém um endereço, e o site que já estava a funcionar continua disponível. Por isso, nada parece estar avariado até tentar aceder a um Ingress.

O mecanismo é o seguinte: o chart do Traefik incluído cria um Service do tipo LoadBalancer nas portas 80 e 443. O ServiceLB responde a esse pedido criando um DaemonSet de pods pequenos, com nomes que começam pelo prefixo svclb-, que reservam esses números de porta como hostPort em cada nó. O hostPort publica a porta do contentor diretamente no namespace de rede do próprio nó, exatamente como docker run -p 80:80. Se nginx, Caddy, Apache ou outro contentor já ocupar a porta 80, o kernel não a pode atribuir duas vezes. Por isso, o scheduler não tem onde colocar o pod.

sudo k3s kubectl -n kube-system get pods
sudo k3s kubectl -n kube-system get svc traefik
sudo ss -lntp '( sport = :80 or sport = :443 )'

Verá o pod svclb no estado Pending e o serviço sem endereço externo:

svclb-traefik-8f2c1a-r6k9x   0/2   Pending   0   3m
traefik   LoadBalancer   10.43.62.11   <pending>   80:31480/TCP,443:30219/TCP

A execução de kubectl -n kube-system describe pod svclb-traefik-... mostra diretamente a causa:

0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.

ss -lntp indica qual é o processo que ocupa a porta. Há mais de uma forma de resolver o problema, e cada uma implica renunciar a alguma coisa.

Atribua as portas ao k3s. Pare e desative o servidor Web existente e deixe o Traefik assumir as portas 80 e 443. Esta é a opção correta quando o VPS vai ser uma máquina k3s dedicada e tudo o que estava a servir passa a estar atrás de um Ingress.

Desative o ServiceLB e mantenha o proxy existente. Instale com --disable=servicelb. Um Service do tipo LoadBalancer continua a atribuir um NodePort. Assim, o Traefik permanece acessível numa porta alta, como 31480, e o nginx ou Caddy encaminha os pedidos para 127.0.0.1:31480. O que perde é o endereço externo: o serviço apresenta <pending> para sempre. Isto parece uma falha, embora seja uma escolha intencional.

Desative o Traefik e encaminhe os pedidos com o seu próprio proxy. Instale com --disable=traefik. Nesse caso, não terá um controlador de Ingress. Os objetos Ingress não terão qualquer efeito: ficam na API sem nenhum controlador a monitorizá-los. Isto é adequado quando encaminha os pedidos de um proxy no host para NodePorts. Também é a escolha correta se já sabe como quer tratar o tráfego HTTP. Se ainda não decidiu o que deve ficar à frente, resolva a escolha entre nginx, Caddy e Traefik como reverse proxy antes de desativar qualquer componente.

Ambas as flags devem ser usadas no instalador ou no ficheiro de configuração:

tls-san:
  - k3s.example.com
disable:
  - traefik
  - servicelb

Também pode editar esse ficheiro depois da instalação e executar sudo systemctl restart k3s, porque --disable faz mais do que ignorar um componente durante a instalação. Também elimina um componente que já esteja implementado. Assim, a alteração entra em vigor num cluster em execução.

Para manter o Traefik, mas alterar a configuração do chart, não edite /var/lib/rancher/k3s/server/manifests/traefik.yaml. O k3s reescreve esse ficheiro com os valores predefinidos sempre que arranca. Em vez disso, adicione um ficheiro separado no mesmo diretório. Tudo o que estiver em /var/lib/rancher/k3s/server/manifests é aplicado automaticamente no arranque e sempre que o ficheiro for alterado no disco.

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    ports:
      web:
        forwardedHeaders:
          trustedIPs:
            - 10.0.0.0/8

Esse exemplo define um valor do chart do Traefik: os endereços dos proxies confiáveis. O mesmo mecanismo permite definir qualquer outro valor exposto pelo chart, incluindo as portas.

Armazenamento persistente num único nó

O k3s disponibiliza uma StorageClass predefinida chamada local-path, suportada pelo provisionador local-path do Rancher. Um PersistentVolumeClaim sem storageClassName utiliza essa classe. Os volumes ficam em /var/lib/rancher/k3s/storage, com um subdiretório por volume, no disco do próprio nó.

sudo k3s kubectl get storageclass
sudo k3s kubectl get pvc -A
sudo ls -l /var/lib/rancher/k3s/storage

A expressão "no disco do próprio nó" tem duas consequências. Ambas só causam problemas mais tarde.

A StorageClass utiliza volumeBindingMode: WaitForFirstConsumer, por isso um novo PVC permanece no estado Pending até um pod o montar. kubectl describe pvc apresenta:

waiting for first consumer to be created before binding

Isto é normal. Criar um PVC isoladamente e esperar nunca fará com que saia desse estado.

Depois de associado, o volume recebe uma afinidade de nó correspondente ao nó que o criou. Isto fixa nesse nó todos os pods que utilizam esse claim durante toda a vida do volume. Num único nó, isto passa despercebido. Se adicionar um segundo nó mais tarde, um pod que se recusa a mudar de nó pode parecer um erro do scheduler. Execute kubectl get pv -o yaml e verá o nome do host em nodeAffinity.

A responsabilidade pelas cópias de segurança é sua. Reconstruir o VPS elimina esse diretório, tal como o script de desinstalação abaixo. Faça uma cópia de /var/lib/rancher/k3s/storage e também do datastore sqlite em /var/lib/rancher/k3s/server/db/state.db, copiando-o com o serviço parado, pois é uma base de dados em utilização. A alternativa é tratar o cluster como descartável e manter todos os manifestos no git.

Ingress e TLS

Com o Traefik ativado, basta um objeto Ingress padrão.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - hello.example.com
      secretName: hello-tls
  rules:
    - host: hello.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello
                port:
                  number: 80

Os certificados não aparecem automaticamente. A solução habitual é o cert-manager, instalado a partir do seu manifesto publicado, juntamente com um ClusterIssuer. A versão v1.21.1 é a atual em agosto de 2026.

sudo k3s kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.1/cert-manager.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    email: you@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik

O desafio HTTP-01 significa que o servidor ACME (ambiente de gestão automática de certificados) se liga a http://hello.example.com/.well-known/acme-challenge/... a partir da Internet pública. Por isso, o registo DNS A já deve apontar para o VPS, e a porta 80 deve chegar ao Traefik. Se desativou o ServiceLB e colocou o seu próprio proxy à frente, esse proxy também tem de encaminhar o caminho do desafio. Caso contrário, o cert-manager fica bloqueado e apresenta o seguinte no objeto Challenge:

Waiting for HTTP-01 challenge propagation: wrong status code '404', expected '200'

Monitorize a emissão com sudo k3s kubectl describe certificate hello-tls e sudo k3s kubectl get order,challenge -A.

O kubeconfig e por que o servidor da API permanece privado

O k3s grava as credenciais de administrador em /etc/rancher/k3s/k3s.yaml. O ficheiro pertence a root e, por predefinição, é gravado com o modo 600. Contém um certificado de cliente com direitos de cluster-admin, por isso qualquer pessoa que consiga lê-lo passa a controlar o cluster.

sudo k3s kubectl get node
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get node

Um kubectl instalado separadamente, sem KUBECONFIG definido, falha com The connection to the server localhost:8080 was refused - did you specify the right host or port? porque recorre a um valor predefinido que não tem relação com o k3s. Defina KUBECONFIG ou use sudo k3s kubectl, que lê automaticamente o ficheiro correto.

Verá --write-kubeconfig-mode 644 recomendado para que um utilizador normal possa executar kubectl. Entenda o efeito dessa configuração: torna uma credencial de cluster-admin legível por todas as contas locais da máquina. Num servidor usado por um único administrador, esse compromisso pode ser aceitável. Num servidor partilhado, não é. Copiar o ficheiro dá acesso a um utilizador sem o tornar público:

mkdir -p ~/.kube
sudo install -o $USER -g $USER -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config

A linha server: desse ficheiro lê https://127.0.0.1:6443. Para usar kubectl a partir do seu portátil, não abra a porta 6443 à Internet. Uma API Kubernetes pública é um alvo permanente, e uma API exposta pode fazer com que pequenos clusters acabem a minerar criptomoeda para terceiros. Encaminhe a ligação por SSH e mantenha o endereço do ficheiro inalterado:

ssh -N -L 6443:127.0.0.1:6443 user@your-vps
KUBECONFIG=./k3s.yaml kubectl get node

Se tiver de aceder à API através de um endereço de rede privado, instale com tls-san, incluindo esse nome ou endereço, e depois edite a linha server: do ficheiro copiado para corresponder. Sem a entrada SAN (subject alternative name), o kubectl recusa a ligação:

x509: certificate is valid for 127.0.0.1, 10.43.0.1, not 203.0.113.10

As portas de entrada documentadas para um cluster são TCP 6443 para a API, UDP 8472 para o VXLAN do flannel entre nós e TCP 10250 para as métricas do kubelet. Num nó único, nenhuma delas precisa de estar aberta à Internet.

containerd não é Docker

O k3s usa o seu próprio containerd integrado e não partilha um armazenamento de imagens com o Docker. Uma imagem que acabou de criar com docker build fica invisível para o k3s. Por isso, o pod falha com ErrImagePull, mesmo que docker images a liste. Importe-a explicitamente:

docker save myapp:0.1 | sudo k3s ctr images import -
sudo k3s crictl images

Depois, evite a tag :latest nesse container, porque :latest usa por predefinição um imagePullPolicy de Always e o kubelet consulta um registry de qualquer forma. Qualquer outra tag usa IfNotPresent por predefinição, que utiliza a imagem importada. Executar ambos no mesmo VPS funciona. É importante saber que uma instalação normal do Docker num VPS e o k3s mantêm cada um o seu próprio armazenamento de imagens e as suas próprias regras de iptables na mesma máquina.

Como remover o k3s

O instalador cria um script de desinstalação. Não existe remoção parcial nem forma de desfazer a operação.

sudo /usr/local/bin/k3s-uninstall.sh

O script para e remove o serviço, elimina o datastore, apaga os dados dos volumes persistentes, remove a configuração do nó e remove as ferramentas adicionadas pelo instalador. Num nó agent, o script é k3s-agent-uninstall.sh. Copie primeiro tudo o que estiver em /var/lib/rancher/k3s/storage para fora do servidor, porque esse diretório também é removido. Depois, confirme com ip link show e sudo ss -lntp que não ficou nada a manter portas ou interfaces ocupadas. Uma interface cni0 ou flannel.1 residual desaparece no próximo reboot.

Concluir que um único nó Kubernetes acrescentava mais complexidade do que o trabalho exigia é um resultado normal, não uma falha. Regressar com essas cargas de trabalho ao Compose costuma demorar uma tarde.

Modos de falha e mensagens que verá

Node NotReady ou k3s a reiniciar continuamente. Consulte sudo journalctl -u k3s -n 200 --no-pager primeiro. Numa VPS pequena, a causa comum é o OOM killer do kernel terminar o processo. Isto aparece em dmesg como uma linha que identifica k3s-server. O mínimo documentado de 2 GB é um limite real.

Pod bloqueado em Pending. kubectl describe pod identifica sempre o problema. Insufficient memory ou Insufficient cpu significa que o node já não tem espaço disponível. didn't have free ports é a colisão de hostPort referida acima. waiting for first consumer num PVC significa que WaitForFirstConsumer está a funcionar como previsto.

ImagePullBackOff. A tag não existe em nenhum registry que o node consiga alcançar, ou a imagem foi criada com Docker e nunca foi importada para o containerd.

Traefik responde, mas a aplicação não. Um corpo de resposta com 404 page not found é gerado pelo próprio Traefik e significa que o pedido chegou, mas nenhum router correspondeu. Confirme se o host do Ingress corresponde ao nome introduzido e se ingressClassName é traefik.

O DNS do cluster falha, mas o host resolve nomes corretamente. Verifique o CoreDNS com sudo k3s kubectl -n kube-system logs -l k8s-app=kube-dns. Uma mensagem como plugin/loop: Loop ... detected for zone "." impede o CoreDNS de arrancar. Isto acontece porque o CoreDNS está a encaminhar pedidos para um resolver que os encaminha de volta para o próprio CoreDNS. É isso que um endereço de loopback em /etc/resolv.conf provoca. Indique ao k3s o ficheiro upstream real com --resolv-conf /run/systemd/resolve/resolv.conf.

FAQ

Vale a pena executar k3s num único VPS?

Vale a pena quando a API do Kubernetes é aquilo de que precisa: aprendê-la numa máquina que controla, manter os deployments portáveis como manifests, executar software que só disponibiliza um Helm chart ou criar algo que mais tarde irá mover para um cluster gerido. Não vale a pena quando apenas quer executar contentores, porque o Docker Compose faz isso com muito menos manutenção e cerca de mais um gigabyte de RAM livre. Um único nó não oferece alta disponibilidade, por isso a fiabilidade nunca é uma razão para o escolher.

Porque é que o meu serviço LoadBalancer do k3s fica em Pending?

O ServiceLB cria pods svclb- que reclamam as portas do serviço como hostPort no nó. Por isso, só são agendados onde essas portas estão livres. Se o nginx ou outro proxy já ocupar a porta 80, o pod fica em Pending e o serviço nunca recebe um endereço externo. kubectl -n kube-system describe pod svclb-... comunica 0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports. Liberte a porta ou reinstale com --disable=servicelb e encaminhe o tráfego para o NodePort que o serviço continua a atribuir.

De quanta RAM precisa o k3s num VPS?

O mínimo documentado para um nó de servidor é 2 cores e 2 GB. Esse valor cobre o k3s e os respetivos componentes incluídos antes de executar as suas cargas de trabalho. O profiling do próprio projeto mediu um nó de servidor com 1,596 MB, com uma stack de monitorização em execução. Por isso, considere 2 GB o mínimo absoluto e 4 GB o primeiro tamanho em que um único nó funciona com margem. Meça a sua própria máquina com free -h antes da instalação e novamente depois de cada pod em kube-system apresentar o estado Running.

Posso executar Docker e k3s no mesmo VPS?

Sim, e permanecem separados. O k3s usa o seu próprio containerd incorporado. Por isso, uma imagem criada com docker build não fica visível para ele até executar docker save myapp:0.1 | sudo k3s ctr images import -. Cada um também escreve as suas próprias regras de iptables e as suas próprias redes bridge. Monitorize a memória total, porque Docker, k3s e os seus contentores não cabem numa máquina com 2 GB.

Como removo completamente o k3s?

Execute sudo /usr/local/bin/k3s-uninstall.sh num nó de servidor ou sudo /usr/local/bin/k3s-agent-uninstall.sh num agent. O comando para o serviço, elimina o datastore, elimina os dados dos persistent volumes em /var/lib/rancher/k3s/storage e remove as ferramentas incluídas. Copie primeiro para fora da máquina todos os dados que pretende manter, porque não existe uma operação de desfazer. Uma interface de rede cni0 ou flannel.1 que fique para trás desaparece no reboot seguinte.

#kubernetes#k3s#docker-compose#ingress#sizing