
Malha de serviço Isstio
Na Namely, estamos usando o Istio há um ano. Ele então acabou de sair. Tivemos uma grande queda no desempenho no cluster Kubernetes, queríamos um rastreamento distribuído e levamos o Istio para executar o Jaeger e descobrir isso. A malha de serviço se encaixa tão bem em nossa infraestrutura que decidimos investir nessa ferramenta.
Eu tive que sofrer, mas nós estudamos isso amplamente. Este é o primeiro post de uma série em que descreverei como o Istio se integra ao Kubernetes e o que aprendemos sobre seu trabalho. Às vezes, vagamos pelas áreas técnicas, mas não muito longe. Além disso, haverá mais posts.
O que é o Istio?
Istio é uma ferramenta de configuração de malha de serviço. Ele lê o estado do cluster Kubernetes e atualiza para proxies L7 (HTTP e gRPC), que são implementados como side-car nos pods do Kubernetes. Esses carros laterais são contêineres Envoy que lêem a configuração da API do Istio Pilot (e o serviço gRPC) e direcionam o tráfego através dela. Com o poderoso proxy L7, podemos usar métricas, traços, lógica de repetição, disjuntor, balanceamento de carga e implantações de canárias.
Vamos começar do começo: Kubernetes
No Kubernetes, criamos usando um deploy ou StatefulSet. Ou pode ser apenas "baunilha" sem um controlador de alto nível. Em seguida, o Kubernetes faz o possível para manter o estado desejado - ele cria pods no cluster no nó, garante que eles sejam iniciados e reiniciados. Quando um under é criado, o Kubernetes passa pelo ciclo de vida da API, garante que cada etapa seja bem-sucedida e só então finalmente cria o under no cluster.
Estágios do ciclo de vida da API:

Obrigado ao Banzai Cloud pela imagem bacana.
Uma das etapas é modificar os webhooks de admissão. Esta é uma parte separada do ciclo de vida no Kubernetes, onde os recursos são personalizados antes de serem confirmados no repositório etcd, a fonte da verdade para a configuração do Kubernetes. E aqui Istio faz sua mágica.
Modificando webhooks de admissão
Quando um sub é criado (via kubectl
ou Deployment
), ele passa por esse ciclo de vida e os webhooks modificadores de acesso o alteram antes de liberá-lo para o grande mundo.
Durante a instalação do Istio, o istio-sidecar-injector é adicionado como um recurso de configuração para modificar webhooks:
$ kubectl get mutatingwebhookconfiguration NAME AGE istio-sidecar-injector 87d
E a configuração:
apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: labels: app: istio-sidecar-injector chart: sidecarInjectorWebhook-1.0.4 heritage: Tiller name: istio-sidecar-injector webhooks: - clientConfig: caBundle: redacted service: name: istio-sidecar-injector namespace: istio-system path: /inject failurePolicy: Fail name: sidecar-injector.istio.io namespaceSelector: matchLabels: istio-injection: enabled rules: - apiGroups: - "" apiVersions: - v1 operations: - CREATE resources: - pods
Ele diz aqui que o Kubernetes deve enviar todos os eventos de criação de lareira para o serviço istio-sidecar-injector
no espaço de nome istio-system
se o espaço de nome tiver istio-injection=enabled
. O injetor inclui mais dois contêineres no PodSpec: um temporário para definir regras de proxy e outro para proxy em si. O injetor de carro lateral insere esses recipientes de acordo com o modelo do mapa de configuração istio-sidecar-injector
. Esse processo também é chamado de side-car.
Pods de sidecar
Sidecar são os truques do nosso mágico Istio. O Istio aciona tudo de maneira tão inteligente que, do lado de fora, é apenas mágica, se você não conhece os detalhes. E é útil conhecê-los se você precisar depurar repentinamente solicitações de rede.
Contêineres de inicialização e proxy
O Kubernetes possui contêineres de inicialização únicos temporários que podem ser executados antes dos principais. Eles agrupam recursos, migram bancos de dados ou, como é o caso do Istio, configuram regras de rede.
O Istio usa o Envoy para fazer proxy de todas as solicitações de envio pelas rotas desejadas. Para fazer isso, o Istio cria regras para o iptables
e envia o tráfego de entrada e saída diretamente para o Envoy, além de proxies do tráfego para seu destino. O tráfego faz um pequeno desvio, mas você distribuiu o rastreamento, as métricas de consulta e a aplicação de políticas. Neste arquivo, no repositório do Istio, você pode ver como o Istio cria regras para o iptables.
O @jimmysongio desenhou um excelente diagrama de conexão entre as regras do iptables e o proxy Envoy:

Enviado - Tráfego enviado
O Envoy recebe todo o tráfego de entrada e saída; portanto, todo o tráfego geralmente se move dentro do Envoy, como no diagrama. O proxy Istio é outro contêiner que é adicionado a todos os pods modificados pelo injetor lateral do Istio. Nesse contêiner, o processo do Envoy é iniciado, que recebe todo o tráfego da lareira (com algumas exceções, como o tráfego do cluster Kubernetes).
O processo do Envoy descobre todas as rotas por meio da API do Envoy v2, que implementa o Istio.
Enviado e Piloto
O próprio enviado não tem lógica para detectar pods e serviços em um cluster. É um plano de dados e precisa de um plano de controle para guiá-lo. O parâmetro de configuração do Envoy solicita que a porta do host ou serviço receba essa configuração por meio da API do gRPC. O Istio, por meio do serviço Pilot, atende aos requisitos da API do gRPC. O Enviado se conecta a essa API com base em uma configuração de side-car implementada por meio de um webhook de modificação. A API possui todas as regras de tráfego que o Envoy precisa descobrir e rotear para o cluster. Esta é a malha de serviço.

Troca de dados "sob o <-> piloto"
O piloto se conecta ao cluster Kubernetes, lê o status do cluster e aguarda atualizações. Ele monitora os pods, serviços e pontos finais no cluster Kubernetes, para fornecer a configuração correta a todos os carros laterais Envoy conectados ao Pilot. Esta é a ponte entre Kubernetes e Enviado.

De Piloto a Kubernetes
Quando pods, serviços ou pontos de extremidade são criados ou atualizados no Kubernetes, o Pilot aprende sobre ele e envia a configuração necessária para todas as instâncias do Envoy conectadas.
Que configuração está sendo enviada?
Que configuração o Enviado obtém do Istio Pilot?
Por padrão, o Kubernetes resolve seus problemas de rede com um serviço (serviço) que gerencia endpoint
. A lista de terminais pode ser aberta com o comando:
kubectl get endpoints
Esta é uma lista de todos os IP e portas do cluster e seus endereços (geralmente são pods criados a partir de uma implantação). É importante saber o Istio para configurar e enviar dados de rota para o Envoy.
Serviços, ouvintes e rotas
Ao criar um serviço em um cluster Kubernetes, você inclui atalhos pelos quais todos os pods adequados serão selecionados. Quando você envia tráfego para o IP do serviço, o Kubernetes seleciona o tráfego para esse tráfego. Por exemplo, o comando
curl my-service.default.svc.cluster.local:3000
Primeiro, ele encontrará o IP virtual atribuído ao my-service
no espaço para nome default
e esse IP encaminhará o tráfego para uma sub que corresponda à etiqueta de serviço.
Istio e Envoy mudam ligeiramente essa lógica. O Istio configura o Envoy com base nos serviços e pontos de extremidade no cluster Kubernetes e usa os recursos de roteamento inteligente e balanceamento de carga da Envoy para ignorar o serviço Kubernetes. Em vez de proxy de um IP de cada vez, o Envoy se conecta diretamente à lareira do IP. Para fazer isso, o Istio mapeia a configuração do Kubernetes para a configuração do Envoy .
Os termos Kubernetes, Istio e Enviado são um pouco diferentes, e não está claro o que eles comem imediatamente.
Serviços
Um serviço no Kubernetes é mapeado para um cluster no Envoy. O cluster Envoy contém uma lista de pontos de extremidade , ou seja, o IP (ou nomes de host) das instâncias para o processamento de solicitações. Para ver a lista de clusters configurados no Istio sidecar-pod, execute o istioctl proxy-config cluster < >
. Este comando mostra o estado atual das coisas em termos da lareira. Aqui está um exemplo de um de nossos ambientes:
$ istioctl proxy-config cluster taxparams-6777cf899c-wwhr7 -n applications SERVICE FQDN PORT SUBSET DIRECTION TYPE BlackHoleCluster - - - STATIC accounts-grpc-gw.applications.svc.cluster.local 80 - outbound EDS accounts-grpc-public.applications.svc.cluster.local 50051 - outbound EDS addressvalidator.applications.svc.cluster.local 50051 - outbound EDS
Todos os mesmos serviços estão neste espaço para nome:
$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) accounts-grpc-gw ClusterIP 10.3.0.91 <none> 80/TCP accounts-grpc-public ClusterIP 10.3.0.202 <none> 50051/TCP addressvalidator ClusterIP 10.3.0.56 <none> 50051/TCP
Como o Istio sabe qual protocolo usa o serviço? Configura protocolos para manifestos de serviço pelo campo de name
na entrada da porta.
$ kubectl get service accounts-grpc-public -o yaml apiVersion: v1 kind: Service metadata: name: accounts-grpc-public spec: ports: - name: grpc port: 50051 protocol: TCP targetPort: 50051
Se houver grpc
ou o prefixo grpc, o Istio configurará o protocolo HTTP2 para o serviço. Aprendemos por experiência amarga como o Istio usa o nome da porta quando as configurações de proxy estão corrompidas porque não especificaram prefixos http ou grpc ...
Se você usar o kubectl e a página de encaminhamento da porta administrativa no Envoy, poderá ver que os pontos de extremidade account-grpc-public são implementados pelo Pilot como um cluster no Envoy com o protocolo HTTP2. Isso confirma nossas suposições:
$ kubectl -n applications port-forward otherpod-dc56885ff-dqc6t 15000:15000 & $ curl http://localhost:15000/config_dump | yq r - ... - cluster: circuit_breakers: thresholds: - {} connect_timeout: 1s eds_cluster_config: eds_config: ads: {} service_name: outbound|50051||accounts-grpc-public.applications.svc.cluster.local http2_protocol_options: max_concurrent_streams: 1073741824 name: outbound|50051||accounts-grpc-public.applications.svc.cluster.local type: EDS ...
A porta 15000 é a página de administração do Envoy , disponível em todos os carros laterais.
Ouvintes
Os ouvintes reconhecem os pontos de extremidade do Kubernetes para passar o tráfego para as lareiras. O serviço de verificação de endereço possui um terminal aqui:
$ kubectl get ep addressvalidator -o yaml apiVersion: v1 kind: Endpoints metadata: name: addressvalidator subsets: - addresses: - ip: 10.2.26.243 nodeName: ip-10-205-35-230.ec2.internal targetRef: kind: Pod name: addressvalidator-64885ccb76-87l4d namespace: applications ports: - name: grpc port: 50051 protocol: TCP
Portanto, a lareira de verificação de endereço tem um ouvinte na porta 50051:
$ kubectl -n applications port-forward addressvalidator-64885ccb76-87l4d 15000:15000 & $ curl http://localhost:15000/config_dump | yq r - ... dynamic_active_listeners: - version_info: 2019-01-13T18:39:43Z/651 listener: name: 10.2.26.243_50051 address: socket_address: address: 10.2.26.243 port_value: 50051 filter_chains: - filter_chain_match: transport_protocol: raw_buffer ...
Rotas
No Istio, em vez do objeto padrão do Kubernetes Ingress, um recurso personalizado mais abstrato e eficiente é VirtualService
- VirtualService
. O VirtualService mapeia rotas para clusters upstream, vinculando-os ao gateway. É assim que se usa o Kubernetes Ingress com um controlador do Ingress.
Na Namely, usamos o Istio Ingress-Gateway para todo o tráfego GRPC interno:
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: grpc-gateway spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: http2 number: 80 protocol: HTTP2 --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: grpc-gateway spec: gateways: - grpc-gateway hosts: - '*' http: - match: - uri: prefix: /namely.address_validator.AddressValidator retries: attempts: 3 perTryTimeout: 2s route: - destination: host: addressvalidator port: number: 50051
À primeira vista, você não entenderá nada no exemplo. Não é visível aqui, mas a implantação do Istio-IngressGateway registra quais pontos de extremidade são necessários com base no istio: ingressgateway
. Neste exemplo, o IngressGateway roteia o tráfego para todos os domínios pela porta 80 pelo HTTP2. O VirtualService implementa rotas para esse gateway, corresponde ao prefixo /namely.address_validator.AddressValidator
e passa o addressvalidator
pela porta 50051 para o serviço upstream addressvalidator
uma regra de repetição em dois segundos.
Se redirecionarmos a porta de pod do Istio-IngressGateway e vermos a configuração do Envoy, veremos o que o VirtualService faz:
$ kubectl -n istio-system port-forward istio-ingressgateway-7477597868-rldb5 15000 ... - match: prefix: /namely.address_validator.AddressValidator route: cluster: outbound|50051||addressvalidator.applications.svc.cluster.local timeout: 0s retry_policy: retry_on: 5xx,connect-failure,refused-stream num_retries: 3 per_try_timeout: 2s max_grpc_timeout: 0s decorator: operation: addressvalidator.applications.svc.cluster.local:50051/namely.address_validator.AddressValidator* ...
O que pesquisamos no Google enquanto procurávamos no Istio
Ocorre o erro 503 ou 404
Os motivos são diferentes, mas geralmente são:
- Os aplicativos do side-car não podem entrar em contato com o Pilot (verifique se o Pilot está em execução).
- O manifesto do serviço Kubernetes tem um protocolo inválido.
- A configuração do VirtualService / Envoy grava a rota no cluster upstream errado. Comece com o serviço de borda, onde você espera tráfego de entrada, e examine os logs do Envoy. Ou use algo como Jaeger para encontrar erros.
O que significa NR / UH / UF nos logs de proxy do Istio?
- NR - Sem rota.
- UH - Upstream Insalubre (upstream inoperável).
- UF - Falha a montante (falha a montante).
Leia mais no site da Envoy .
Em relação à alta disponibilidade com o Istio
- Adicione NodeAffinity aos componentes do Istio para distribuir uniformemente lares em diferentes zonas de disponibilidade e aumentar o número mínimo de réplicas.
- Inicie a nova versão do Kubernetes com o recurso de escalonamento horizontal do pod horizontal. As lareiras mais importantes serão dimensionadas com base na carga.
Por que o cronjob não termina?
Quando a carga de trabalho principal estiver concluída, o contêiner lateral continuará funcionando. Para contornar o problema, desative o sidecar nos cronjobs adicionando a anotação sidecar.istio.io/inject: “false”
ao PodSpec.
Como instalar o Istio?
Usamos o Spinnaker para implantações, mas geralmente pegamos os gráficos Helm mais recentes, conjuramos, usamos o helm template -f values.yml
e confirmamos os arquivos no Github para ver as alterações antes de aplicá-las via kubectl apply -f -
. Isso ocorre para não alterar acidentalmente o CRD ou a API em versões diferentes.
Agradecemos a Bobby Tables e Michael Hamrah por ajudarem a escrever este post.