Contêineres de Servidor SSH Para Desenvolvimento no Kubernetes

Built for Speed: ~10ms Latency, Even Under Load
Blazingly fast way to build, track and deploy your models!
- Handles 350+ RPS on just 1 vCPU — no tuning needed
- Production-ready with full enterprise support
No ano passado, discutimos a execução de Jupyter Notebooks Hospedados e VS Code (Code Server) em seus Clusters Kubernetes. Comparamos várias abordagens e soluções existentes, incluindo o aluguel e o gerenciamento de VMs. Em seguida, descrevemos nossas abordagens para resolver problemas de usabilidade, a fim de tornar a experiência mais agradável e abstrair os detalhes do Kubernetes.
Desde então, recebemos muitos feedbacks de nossos clientes, principalmente a falta de uma melhor experiência de desenvolvimento para aplicativos completos. Embora o Jupyter Lab seja ótimo para notebooks interativos e edição leve, levá-lo a capacidades completas de IDE pode exigir muito ajuste com Extensões Jupyter e ainda pode não ser uma ótima experiência para bases de código que não são Python. Para resolver essas deficiências, lançamos Code Server suporte, que é mais ou menos o VS Code no navegador. Embora seja uma solução sem configuração e facilite muitos problemas de Experiência do Desenvolvedor com o Jupyter Lab, os usuários relataram atrito ao trabalhar com extensões do VS Code.
Por exemplo, Pylance, a extensão que oferece excelente suporte à linguagem Python no VS Code, não pode ser instalada no Code Server devido a uma licença proprietária da Microsoft. Em vez disso, os usuários precisam contar com uma combinação de Jedi e Pyright que ainda não estão no mesmo nível do Pylance. Outro exemplo é Github CoPilot – embora seja possível instalá-lo no Code Server, requer ajuste manual do arquivo de extensão e a atualização da versão do Code Server.
Embora a experiência de edição do Code Server não seja ruim, às vezes há um atraso perceptível e texto embaralhado no terminal, o que pode ser irritante. Sempre soubemos que conectar o VS Code local com o VS Code Server remoto via SSH ou Tunnels seria uma experiência melhor.
O objetivo é permitir que os usuários criem um deployment executando o Servidor OpenSSH em uma imagem de contêiner baseada em Ubuntu, com a mesma persistência de disco do Jupyter Lab, e permitir que os usuários se conectem a ele.
Veja como fica na plataforma (documentação):
Neste post, explicaremos como implementamos a conexão a contêineres via SSH sem fornecer acesso direto ao cluster e sem enviar tráfego para fora da VPC.
Istio e Roteamento
Ao implantar aplicações, geralmente configuramos um nome de domínio para acessar esses serviços, mas comprar um domínio para cada aplicação é proibitivamente caro. Em vez disso, compramos um único domínio (por exemplo, acmecorp.com), configuramos subdomínios (docs.acmecorp.com) e/ou prefixos de caminho (acmecorp.com/blog/) e então usamos um roteador para corresponder regras e rotear o tráfego para diferentes aplicações.
Usamos Istio para todo o nosso roteamento de ingress. O Istio, entre muitas funcionalidades, oferece abstrações convenientes para configurar o Envoy proxy subjacente que realmente lida com todo o roteamento.
Vamos entender como uma requisição HTTP é roteada

No exemplo acima, quando um usuário tenta acessar https://myapp.acmecorp.com/api/v1
- Primeiro, *.acmecorp.com é resolvido via DNS para o IP público do balanceador de carga externo. A porta é inferida como 443 devido ao protocolo HTTPS.
- Uma conexão TCP é estabelecida com o balanceador de carga e o payload da requisição HTTP é enviado
- O Balanceador de Carga roteia o payload da requisição para os Pods de Ingress do Istio
- O Istio Ingress analisa todas as VirtualServices(e as configurações de Gateway), corresponde ao nome do host (mais importante, o subdomínio myapp) e ao prefixo de caminho, e roteia para o Serviço Kubernetes correspondente
- O Kubernetes roteia a requisição para um dos Endpoints (Pod) do Serviço
Isso é chamado de Roteamento de Camada 7 porque usamos campos reais da especificação HTTP para fazer o roteamento.
Roteamento no contexto de SSH
SSH usa um protocolo personalizado que utiliza TCP para transporte. Uma conexão SSH simples se parece com o seguinte
ssh user@somemachine.acmecorp.com -p 22
Aqui estamos tentando nos conectar a somemachine.acmecorp.com na porta 22. Aqui somemachine.acmecorp.com:22 precisa ser resolvido para uma combinação única de endereço IP e porta para alcançar o destino. Mas lembre-se que em nossa configuração, todos os subdomínios estão configurados para apontar para o mesmo balanceador de carga – o que significa que abc.acmecorp.com, xyz.acmecorp.com, somemachine.acmecorp.com todos se resolvem para o mesmo endereço IP e então Istio/Envoy deve analisar o subdomínio e decidir para onde rotear. Mas no caso de SSH, isso não é possível porque, depois de resolver o endereço IP e estabelecer uma conexão TCP, tudo o que o Istio vê é o IP e o número da porta do balanceador de carga, e o conteúdo real dos pacotes está sendo criptografado por SSH. Então, como podemos rotear para múltiplos destinos SSH diferentes no cluster?
Opção 1: Usar portas únicas no mesmo Balanceador de Carga
Como só precisamos garantir combinações únicas de endereço IP e portas, podemos simplesmente atribuir portas diferentes no balanceador de carga a contêineres SSH únicos
Podemos então configurar Correspondência de porta de rota TCP usando Istio

Aqui, todo o tráfego TCP que chega à porta 22 do Balanceador de Carga alcançará o Serviço A e todo o tráfego TCP na porta 23 alcançará o Serviço B.
Embora isso funcione bem, existem algumas limitações
- Um máximo de 65.535 contêineres SSH pode ser alcançado por trás de um único balanceador de carga. Isso não é um grande problema porque, realisticamente, não esperamos tantos contêineres SSH implantados ao mesmo tempo.
- O problema mais complicado é abrir e liberar portas dinamicamente e com precisão no balanceador de carga externo sem nunca interromper qualquer outro tráfego normal. Embora certamente possível, qualquer bug ou condição de corrida poderia causar sérias interrupções para outras aplicações. Sem mencionar que abrir portas arbitrárias é um grande risco de segurança para muitos de nossos clientes.
Opção 2: Usar um novo Balanceador de Carga para cada contêiner SSH

Neste caso, apontamos explicitamente abc.acmecorp.com e xyz.acmecorp.com para dois Balanceadores de Carga externos diferentes, em vez do curinga *.acmecorp.com. Agora eles apontam para um endereço IP único cada e podem ser roteados por dois diferentes Istio Gateway (ligado um-para-um a um balanceador de carga externo). A limitação óbvia aqui é que provisionar um novo balanceador de carga por contêiner SSH torna-se proibitivamente caro.
Existe alguma forma de aproveitar o roteamento em nível HTTP, mas ainda assim trabalhar apenas com tráfego TCP? Apresentamos o HTTP CONNECT!
Proxying usando HTTP CONNECT
O HTTP CONNECT método permite estabelecer um "túnel" entre dois destinos através de um Proxy. Imagine os velhos tempos das centrais telefônicas - você quer ligar para um número, mas não tem uma linha direta para alcançá-lo; em vez disso, um operador intermediário facilita a conexão em seu nome e depois se afasta para permitir que as duas partes se comuniquem.

Recomendamos assistir ao seguinte vídeo para uma boa explicação: https://www.youtube.com/watch?v=PAJ5kK50qp8
Felizmente, no nosso caso, já usamos um proxy capaz de usar CONNECT - o Envoy Proxy. Vejamos como funcionaria no nosso caso de uso:

- O cliente abre uma conexão para acmecorp.com:80 - o balanceador de carga externo que roteia o tráfego para o Envoy.
- O cliente envia uma requisição HTTP CONNECT
CONNECT svc-a.ns.cluster.svc.local:80 HTTP/1.1
Host: svc-a.ns.cluster.svc.local
que está instruindo o Envoy a estabelecer uma Conexão TCP com svc-a.ns.cluster.svc.local:80 em seu nome
- Uma vez estabelecida a conexão, um 200 OK é retornado ao cliente.
- A partir deste ponto, o Envoy deixa de se importar com o conteúdo do tráfego e atua como um "túnel", permitindo que o tráfego flua entre o cliente e o pod. Pode ser qualquer coisa que funcione sobre TCP, incluindo, mas não se limitando a, SSH.

Note que svc-a.ns.cluster.svc.local:80 é um Serviço Kubernetes e não aponta para nenhum endereço IP público; em vez disso, só pode ser resolvido dentro do Cluster Kubernetes. Como o Envoy reside dentro do cluster, podemos configurá-lo para alcançar os pods por trás dele.
Tudo o que resta é configurar o Envoy para fazer tal roteamento. Infelizmente, o Istio não possui abstrações de alto nível para configurar isso facilmente; em vez disso, temos que aplicar patches à configuração do Envoy usando Envoy Filters
Envoy Filters
Compreender as capacidades do Envoy e os Envoy Filters está fora do escopo desta publicação de blog, mas considere-o apenas como uma forma conveniente de modificar as regras de roteamento do Istio usando pequenos patches. Para habilitar o roteamento baseado em CONNECT, precisamos
- Ter uma porta exposta publicamente no LoadBalancer para aceitar tráfego TCP (por exemplo, digamos 2222) e configurar o correspondente Istio Gateway para aceitar tráfego HTTP. Optamos por manter a porta 80 porque já a usamos para tráfego HTTP normal e o tráfego SSH será criptografado de qualquer forma.
- Configure a porta exposta publicamente no Gateway para aceitar requisições do tipo CONNECT. Descobrimos que isso já está habilitado para requisições na Porta 80. Para qualquer outra porta, você pode aplicar um Filtro Envoy da seguinte forma:
Ex.: Habilitar CONNECT na Porta 2222apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
portNumber: 2222
patch:
operation: MERGE
value:
typed_config:
'@type': >-
type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
http2_protocol_options:
allow_connect: true
upgrade_configs:
- upgrade_type: CONNECT
workloadSelector:
labels:
app: tfy-istio-ingress
kind: EnvoyFilter
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
portNumber: 2222
patch:
operation: MERGE
value:
typed_config:
'@type': >-
type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
http2_protocol_options:
allow_connect: true
upgrade_configs:
- upgrade_type: CONNECT
workloadSelector:
labels:
app: tfy-istio-ingress
- Para cada Contêiner SSH, configure o roteamento baseado em CONNECT:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: svc-a-ns-ssh-envoy-filter
namespace: istio-system
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
filterChain:
filtro:
name: envoy.filters.network.http_connection_manager
númeroDaPorta: 80
patch:
operation: MERGE
valor:
typed_config:
'@type': >-
type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
route_config:
name: local_route
hosts_virtuais:
- domínios:
- svc-a.ns.svc.cluster.local:80
name: svc-a-ns-ssh-vh
rotas:
- correspondência:
connect_matcher: {}
rota:
cluster: >-
outbound|80||svc-a.ns.svc.cluster.local
configuracoes_de_upgrade:
- config_de_conexao: {}
habilitado: true
tipo_de_upgrade: CONNECT
seletorDeCargaDeTrabalho:
rotulos:
istio: tfy-istio-ingress
Esse é um YAML de aparência bem assustadora, mas tudo o que estamos fazendo é modificar o listener na porta 80 no Gateway para corresponder a requisições CONNECT para svc-a.ns.svc.cluster.local:80 e roteá-las para outbound|80||svc-a.ns.svc.cluster.local, ou seja, a porta 80 do serviço Kubernetes svc-a.ns.svc.cluster.local onde nosso servidor OpenSSH está aguardando conexões SSH dentro do Contêiner.
Iniciando CONNECT no Lado do Cliente SSH
Por si só, o cliente SSH não sabe nada sobre HTTP CONNECT. Em vez disso, ele oferece uma ProxyCommand opção que permite a outros programas facilitar a Conexão SSH. Aqui usamos o ProxyTunnel projeto que facilita isso. A configuração em ~/.ssh/config é a seguinte
Host svc-a-ns
Usuário jovyan
Nome do Host svc-a.ns.svc.cluster.local
Porta 80
Intervalo de Atividade do Servidor 100
Arquivo de Identidade ~/.ssh/my-private-key
Comando Proxy proxytunnel -v -p ssh.acmecorp.com:80 -o %h -d %h:%p
Com tudo isso feito, os usuários podem agora conectar-se facilmente e configurar seu fluxo de trabalho de desenvolvimento favorito - seja Neovim, VS Code, JetBrains IDE, etc.

Limitações e Soluções Potenciais
Embora este recurso melhore significativamente a Experiência do Desenvolvedor em termos de edição e execução de código, algumas limitações ainda se aplicam porque ainda estamos a ser executados dentro de um contentor.
- O Docker não funciona porque já estamos dentro de um contentor. Teoricamente, é possível fazer algumas coisas funcionarem com DIND, mas isso vem com os seus desafios.
- As alterações feitas no sistema de ficheiros raiz do contentor
/não são persistentes após reinícios do contentor. Nós fornecemos uma forma de estender a nossa imagem de Servidor SSH e iniciar a partir dessas imagens personalizadas. - Os Pods do Kubernetes são concebidos para serem efémeros e podem ser movidos, mas isso é indesejável para um ambiente de desenvolvimento. Nós configuramos orçamentos de interrupção de pod para evitar que o pod seja movido.
- Embora o proxy seja transparente, o tráfego ainda flui através do balanceador de carga e dos pods Istio Envoy. Isso significa que fazer algo incomum no desenvolvimento, como carregar/descarregar ficheiros enormes, pode consumir largura de banda e recursos e afetar outro tráfego. É melhor usar um conjunto separado de pods LoadBalancer, Gateway, Envoy para conectar-se a Contentores SSH.
Contentores de servidor SSH no Kubernetes na TrueFoundry
TrueFoundry é uma PaaS de implementação de ML/LLM sobre Kubernetes para acelerar os fluxos de trabalho dos desenvolvedores, ao mesmo tempo que lhes permite total flexibilidade no teste e implementação de modelos, garantindo total segurança e controlo para a equipa de Infraestrutura. Através da nossa plataforma, permitimos que as equipas Implantar e monitorar modelos em 15 minutos com 100% de confiabilidade, escalabilidade e a capacidade de reverter em segundos - permitindo-lhes economizar custos e lançar modelos em produção mais rapidamente, possibilitando a concretização de valor de negócio real.
TrueFoundry AI Gateway delivers ~3–4 ms latency, handles 350+ RPS on 1 vCPU, scales horizontally with ease, and is production-ready, while LiteLLM suffers from high latency, struggles beyond moderate RPS, lacks built-in scaling, and is best for light or prototype workloads.
The fastest way to build, govern and scale your AI

















.webp)






.webp)

.webp)
.webp)





.png)



