0 Seguidores · 13 Postagens

Amazon Web Services (AWS) é uma plataforma segura de serviços em nuvem, que oferece poder de computação, armazenamento de banco de dados, entrega de conteúdo e outras funcionalidades para ajudar empresas a escalar e crescer.

Saber mais.

Artigo Danusa Calixto · Set. 17, 2025 3m read

Olá, pessoal! Tendo me integrado recentemente à InterSystems, percebi que, apesar de ter uma Edição Comunitária totalmente gratuita e incrível, não é muito claro como obtê-la. Decidi escrever um guia destacando todas as diferentes maneiras de acessar a Edição Comunitária do InterSystems IRIS:

Obtenha o InterSystems IRIS Community Edition como um contêiner

Trabalhar com uma instância em contêiner da Community Edition é a abordagem recomendada para quem está começando a desenvolver no InterSystems IRIS e, na minha opinião, é a mais simples. A Community Edition do InterSystems IRIS pode ser encontrada no DockerHub; se você tiver uma conta InterSystems SSO, também poderá encontrá-la no Registro de Contêineres da InterSystems.

Em ambos os casos, você vai querer extrair a imagem desejada usando o Docker CLI:

docker pull intersystems/iris-community:latest-em
// or
docker pull containers.intersystems.com/intersystems/iris-community:latest-em

Em seguida, você precisará iniciar o contêiner: para interagir com o IRIS de fora do contêiner (por exemplo, para usar o portal de gerenciamento), você precisará publicar algumas portas. O comando a seguir executará o contêiner IRIS Community Edition com as portas do superservidor e do servidor web publicadas; observe que você não pode executar nada que dependa das portas 1972 ou 52773!

docker run --name iris -d --publish 1972:1972 --publish 52773:52773 intersystems/iris-community:latest-em
0
0 35
Artigo Danusa Calixto · Dez. 4, 2023 14m read

Com frequência, me pedem para avaliar dados de desempenho relacionados a aplicativos IRIS de clientes para entender se os recursos do sistema são sub ou superprovisionados.

Este exemplo recente é interessante, porque envolve um aplicativo que fez uma migração "lift and shift" de um grande aplicativo de banco de dados IRIS para a nuvem. AWS, no caso.

Um aprendizado importante é que, depois de migrar para a nuvem, os recursos podem ser dimensionados corretamente ao longo do tempo conforme necessário. Não é preciso comprar e provisionar infraestrutura local para o crescimento que você espera alcançar daqui a vários anos no futuro.

É necessário monitoramento contínuo. A taxa de transações do seu aplicativo mudará à medida que seu negócio e o próprio aplicativo ou o uso dele mudar. Isso alterará os requisitos de recursos do sistema. Planejadores também devem considerar picos sazonais na atividade. Claro, uma vantagem da nuvem é que os recursos podem ser aumentados ou reduzidos conforme necessário.

Para mais informações contextuais, há vários posts detalhados sobre AWS e IRIS na comunidade. Um bom ponto de partida é pesquisar "referência da AWS". Também adicionei alguns links úteis no final deste post.

Os serviços da AWS são como blocos de Lego: tamanhos e formatos diferentes podem ser combinados. Ignorei networking, segurança e preparação de uma VPC para este post. Foquei em dois dos componentes de blocos de Lego;

  • Requisitos de computação.
  • Requisitos de armazenamento.

Visão geral

O aplicativo é um sistema de informações de saúde usado em um grupo de hospitais movimentados. Os componentes da arquitetura em que estou focando aqui incluem dois servidores de bancos de dados em um cluster de tolerância a falhas de espelho da InterSystems.

Barra lateral: os espelhos estão em zonas de disponibilidade separadas para alta disponibilidade adicional.


Requisitos de computação

Tipos de instância EC2

O Amazon EC2 oferece uma ampla seleção de tipos de instância otimizados para diferentes casos de uso. Os tipos de instância compreendem combinações FIXAS variadas de CPU e memória, além de limites máximos fixos de capacidade de armazenamento e de networking. Cada tipo de instância inclui um ou mais tamanhos de instância.

Os atributos da instância EC2 que devem ser analisados com mais atenção incluem:

  • Núcleos de vCPU e memória.
  • Máximo de IOPS e taxa de transferência de IO.

Para aplicativos IRIS como esse com um grande servidor de banco de dados, dois tipos de instâncias EC2 são apropriadas: 

  • EC2 R5 e R6i estão na família de instâncias com memória otimizada e são adequadas para cargas de trabalho com uso intensivo da memória, como IRIS. Há 8 GB de memória por vCPU.
  • EC2 M5 e M6i estão na família de instâncias de uso geral. Há 4 GB de memória por vCPU. Elas são mais usadas para servidores da Web, de impressão e de não produção.

Observação: nem todos os tipos de instâncias estão disponíveis em todas as regiões da AWS. As instâncias R5 foram usadas nesse caso porque a R6i lançada mais recentemente não estava disponível.

Planejamento de capacidade

Quando um sistema local existente está disponível, o planejamento da capacidade significa medir o uso atual de recursos, traduzindo isso para os recursos na nuvem pública e adicionando recursos para o crescimento previsto a curto prazo. Geralmente, se não há outros limites de recursos, os aplicativos do banco de dados do IRIS são escalados linearmente nos mesmos processadores. Por exemplo, imagine adicionar um novo hospital ao grupo. O aumento do uso do sistema (taxa de transações) em 20% exigiria 20% mais recursos de vCPU usando os mesmos tipos de processadores. É claro que isso não é garantido. Valide seus aplicativos.

Requisitos de vCPU

Antes da migração, a utilização da CPU chegava a quase 100% em períodos movimentados. O servidor no local tem 26 vCPUs. Uma boa regra geral é avaliar sistemas com um pico esperado de 80% de utilização da CPU. Isso permite picos temporários na atividade ou outras atividades incomuns. Confira abaixo um gráfico de exemplo para a utilização da CPU em um dia típico.

image

O monitoramento dos servidores locais exigiria o aumento para 30 núcleos de vCPUs, trazendo o pico geral de utilização para abaixo de 80%. O cliente esperava adicionar um crescimento de transações de 20% no curto prazo. Então, um buffer de 20% é adicionado aos cálculos, permitindo também uma capacidade de reserva adicional para o período de migração.

Um cálculo simples é que 30 núcleos + 20% de crescimento e buffer de migração equivale a 36 núcleos de vCPU necessários

Dimensionamento para a nuvem

Lembre-se de que as instâncias EC2 da AWS em cada tipo de família têm tamanhos fixos de vCPU e memória e definem limites máximos de IOPS, armazenamento e taxa de transferência de rede.

Por exemplo, os tipos de instância disponíveis nas famílias R5 e R6i incluem:

  • 16 vCPUs e memória de 128 GB
  • 32 vCPUs e memória de 256 GB
  • 48 vCPUs e memória de 384 GB
  • 64 vCPUs e memória de 512 GB
  • E assim por diante.

Regra geral: Uma maneira simplificada de dimensionar uma instância EC2 a partir de métricas locais conhecidas para a nuvem é arredondar os requisitos de vCPU locais recomendados para o próximo tamanho de instância EC2 disponível.

Ressalvas: Pode haver várias outras considerações. Por exemplo, diferenças nos tipos e nas velocidades dos processadores locais e do EC2, ou um armazenamento mais eficiente na nuvem do que em um sistema local antigo, podem significar que os requisitos de vCPU mudam. Por exemplo, é possível ter mais IO e fazer mais trabalho em menos tempo, aumentando o pico de utilização da vCPU. Os servidores locais podem ter um processador de CPU completo, incluindo hyper-threading, e as vCPUs das instâncias na nuvem serem um único hyper thread. Por outro lado, as instâncias EC2 são otimizadas para transferir parte do processamento para placas Nitro integradas. Assim, os principais núcleos de vCPU gastam mais ciclos processando as cargas de trabalho, melhorando o desempenho das instâncias. Porém, em resumo, a regra acima é um ótimo guia para começar. A vantagem da nuvem é que, com monitoramento contínuo, você pode planejar e mudar o tipo de instância para otimizar o desempenho e custo.

Por exemplo, para traduzir 30 ou 36 vCPUs locais em tipos de instância EC2 semelhantes:

  • O r5.8xlarge tem 32 vCPUs, 256 GB de memória e um máximo de 30.000 IOPS.
  • O r512xlarge tem 48 vCPUs, 384 GB de memória e um máximo de 40.000 IOPS

Observe o máximo de IOPS. Isso será importante mais tarde.

Resultados

Uma instância r512xlarge foi selecionada para os espelhos do banco de dados do IRIS para a migração.

Nas semanas seguintes à migração, o monitoramento mostrou que o tipo de instância de 48 vCPUs sustentou picos de quase 100% de utilização da vCPU. No entanto, em geral, o processamento chegou a aproximadamente 70%. Isso está bem dentro da faixa aceitável e, se os períodos de alta utilização forem atribuídos a um processo que pode ser otimizado, há bastante capacidade de reserva para considerar o dimensionamento para uma especificação inferior e um tipo de instância EC2 mais barato.

image

Um tempo depois, o tipo de instância permaneceu igual. Uma verificação do desempenho do sistema mostra que o pico de utilização da vCPU caiu para cerca de 50%. No entanto, ainda há picos temporários perto de 100%.

image

Recomendação

É necessário monitoramento contínuo. Com o monitoramento constante, o sistema pode ser dimensionado corretamente para alcançar o desempenho necessário e ter uma execução mais barata.

Os picos temporários na utilização da vCPU devem ser investigados. Por exemplo, um relatório ou uma tarefa em lote podem ser retirados do horário comercial, diminuindo o pico geral da vCPU e reduzindo qualquer impacto negativo nos usuários interativos do aplicativo.

Revise os requisitos de IOPS e taxa de transferência de armazenamento antes de mudar o tipo de instância. Não se esqueça que os tipos de instância têm limites máximos fixos de IOPS.

As instâncias podem ser dimensionadas usando o espelhamento de tolerância a falhas. Etapas simplificadas:

  • Desligue o espelho de backup.
  • Ligue o espelho de backup usando uma instância menor ou maior com configurações alteradas para montar o armazenamento do EBS e contar com uma menor pegada de memória (considere, por exemplo, hugepages do Linux e buffers globais do IRIS).
  • Aguarde a atualização do espelho de backup.
  • Aplique a tolerância a falhas ao espelho de backup para que se torne principal.
  • Repita, redimensione o espelho restante, coloque-o online novamente e atualize.

Observação: durante a tolerância a falhas do espelho, haverá uma breve interrupção para todos os usuários, interfaces etc. No entanto, se forem usados servidores de aplicações ECP, poderá não haver nenhuma interrupção para os usuários. Os servidores de aplicações também podem fazer parte de uma solução de escalonamento automático.

Outras opções econômicas incluem executar o espelho de backup em uma instância menor. No entanto, há um risco significativo de desempenho reduzido (e usuários insatisfeitos) se uma tolerância a falhas ocorrer em momentos de pico de processamento.

Ressalvas: As vCPUs e memória das instâncias são fixas. Recomeçar com uma instância e pegada de memória menores implica em um cache de buffer global menor, o que pode aumentar a taxa de IOPS de leitura do banco de dados. Considere os requisitos de armazenamento antes de reduzir o tamanho da instância. Automatize e teste o dimensionamento para minimizar o risco de erro humano, especialmente se for uma ocorrência comum.


Requisitos de armazenamento

Um desempenho de IO de armazenamento previsível com baixa latência é essencial para fornecer escalabilidade e confiabilidade para seus aplicativos.

Tipos de armazenamento

O armazenamento do Amazon Elastic Block Store (EBS) é recomendado para a maioria dos aplicativos de banco de dados do IRIS com altas taxas de transações. O EBS oferece vários tipos de volumes que permitem otimizar o desempenho do armazenamento e o custo para uma ampla variedade de aplicativos. O armazenamento baseado em SSD é necessário para cargas de trabalho transacionais, como aplicativos que usam bancos de dados do IRIS.

Dos tipos de armazenamento SSD, os volumes gp3 são geralmente recomendados para bancos de dados do IRIS, porque equilibram o preço e o desempenho para aplicativos transacionais. No entanto, para casos excepcionais com taxas muito altas de IOPS ou de transferência, o io2 pode ser usado (geralmente por um custo mais alto). Há outras opções, como as soluções de armazenamento temporário anexado localmente e arrays virtuais de terceiros. Se você tiver requisitos que ultrapassam as capacidades do io2, fale com a InterSystems sobre suas necessidades.

O armazenamento acompanha limites e custos, por exemplo.

  • Os volumes gp3 oferecem uma linha de base de medição de desempenho de 3.000 IOPS e 125 MiBps em qualquer tamanho de volume, com uma latência de milissegundos de um dígito 99% do tempo para o custo base da capacidade de GB de armazenamento. Os volumes gp3 podem ser escalonados para 16.000 IOPS e 1.000 MiBps de taxa de transferência por um custo adicional. O armazenamento é precificado por GB e com base no provisionamento de IOPS acima da linha de base de 3.000 IOPS.
  • Os volumes io2 oferecem uma linha de base de medição de desempenho constante de até 500 IOPS/GB para um máximo de 64.000 IOPS com uma latência de milissegundos de um dígito 99% do tempo. O armazenamento é precificado por GB e com base no provisionamento de IOPS.

Lembre-se: as instâncias EC2 também têm limites de IOPS e taxa de transferência total do EBS. Por exemplo, o r5.8xlarge tem 32 vCPUs e um máximo de 30.000 IOPS. Nem todos os tipos de instâncias são otimizados para usar os volumes do EBS.

Planejamento de capacidade

Quando um sistema local existente está disponível, o planejamento da capacidade significa medir o uso atual de recursos, traduzindo isso para os recursos na nuvem pública e adicionando recursos para o crescimento previsto a curto prazo.

Os dois recursos essenciais que devem ser considerados:

  • Capacidade de armazenamento. Quantos GB de armazenamento de banco de dados são necessários e qual é o crescimento esperado? Por exemplo, você sabe o crescimento médio histórico de banco de dados do seu sistema local para uma taxa de transações conhecida. Nesse caso, você pode calcular os tamanhos de banco de dados futuros com base em qualquer crescimento previsto da taxa de transações. Você também precisará considerar outros tipos de armazenamento, como diários.
  • IOPS e taxa de transferência. Esse é o mais interessante e é abordado em detalhes abaixo.

Requisitos de banco de dados

Antes da migração, as leituras de disco de banco de dados atingiam cerca de 8.000 IOPS.

image

A taxa de IOPS de leitura e escrita ultrapassava 40.000 em alguns dias. Embora, durante o horário comercial, os picos sejam bem mais baixos.

image

A taxa de transferência total de leituras e escritas atingiu cerca de 600 MB/s.

image

Lembre-se: as instâncias EC2 e os volumes do EBS têm limites de IOPS E taxa de transferência*. O limite que for atingido primeiro resultará na restrição desse recurso pela AWS, causando a degradação do desempenho e possivelmente afetando os usuários do seu sistema. Você precisa provisionar IOPS E taxa de transferência.

Dimensionamento para a nuvem

Os volumes gp3 são usados para equilibrar o preço e o desempenho. No entanto, nesse caso, o limite de 16.000 IOPS para um único volume gp3 foi excedido, e há a expectativa de que os requisitos aumentarão no futuro.

Para permitir o provisionamento de uma taxa IOPS mais alta do que o possível em um único volume gp3, é usada uma distribuição do LVM.

Para a migração, o banco de dados é implantado usando uma distribuição do LVM de quatro volumes gp3 com o seguinte:

  • 8.000 IOPS provisionadas em cada volume (para um total de 32.000 IOPS).
  • Taxa de transferência de 250 MB/s provisionada em cada volume (para um total de 1.000 MB/s).

O processo exato de planejamento de capacidade foi realizado para o Write Image Journal (WIJ) e os discos locais de diário de transações. O WIJ e os discos de diário foram provisionados em um único disco gp3 cada.

Para mais detalhes e um exemplo de como usar uma distribuição do LVM, veja: https://community.intersystems.com/post/using-lvm-stripe-increase-aws-ebs-iops-and-throughput

Regra geral: Se os seus requisitos ultrapassarem os limites de um único volume gp3, investigue a diferença de custo entre usar IOPS provisionadas do gp3 do LVM e do io2.

Ressalvas: Garanta que a instância EC2 não limite as taxas de IOPS ou de transferência.

Resultados

Nas semanas seguintes à migração, as IOPS de escrita de banco de dados atingiram cerca de 40.000 IOPS, semelhante a no local. No entanto, as IOPS de leitura de banco de dados foram muito mais baixas.

Uma menor taxa de IOPS de leitura é esperada devido à instância EC2 ter mais memória disponível para armazenar dados em cache nos buffers globais. Mais dados do conjunto de trabalho do aplicativo na memória significa que eles não precisam ser chamados de um armazenamento SSD muito mais lento. Lembre-se, acontecerá o oposto se você reduzir a pegada de memória.

image

Durante os momentos de pico de processamento, o volume do banco de dados teve picos de latência acima de 1 ms. No entanto, os picos são temporários e não afetam a experiência do usuário. O desempenho do armazenamento é excelente.

image

Depois, uma verificação de desempenho do sistema mostra que, embora haja alguns picos, geralmente, a taxa de IOPS de leitura ainda é menor do que no local.

image

Recomendação

É necessário monitoramento contínuo. Com o monitoramento constante, o sistema pode ser dimensionado corretamente para alcançar o desempenho necessário e ter uma execução mais barata.

O processo de aplicação responsável pelos 20 minutos de alta IOPS de escrita de banco de dados durante a noite (gráfico não exibido) deve ser analisado para entender o que ele está fazendo. As escritas não são afetadas por grandes buffers globais e ainda estão na faixa de 30-40.000 IOPS. O processo poderia ser concluído com um menor provisionamento de IOPS. No entanto, haverá um impacto mensurável na latência de leitura do banco de dados se as escritas sobrecarregarem o caminho de IO, afetando negativamente os usuários interativos. A latência de leitura precisa ser monitorada atentamente se as leituras forem limitadas por um longo período.

O provisionamento de IOPS e taxa de transferência de disco do banco de dados pode ser ajustado pelas APIs da AWS ou interativamente pelo console da AWS. Como quatro volumes do EBS compõem o disco LVM, os atributos de IOPS e de taxa de transferência dos volumes do EBS precisam ser ajustados igualmente.

O WIJ e o diário também devem ser monitorados continuamente para entender se é possível fazer alguma alteração no provisionamento de IOPS e de taxa de transferência.

Observação: o volume do WIJ tem requisitos de alta taxa de transferência (não IOPS) devido ao tamanho do bloco de 256 kB. A taxa de IOPS do volume do WIJ pode estar abaixo da linha de base de 3.000 IOPS, mas a taxa de transferência está atualmente acima da linha de base de taxa de transferência de 125 MB/s. É provisionada uma taxa de transferência adicional no volume do WIJ.

Ressalvas: Diminuir o provisionamento de IOPS para limitar o período de altas escritas noturnas resultará em um ciclo mais longo do daemon de escrita (WIJ mais escritas aleatórias de banco de dados). Isso pode ser aceitável se as escritas forem concluídas em 30-40 segundos. No entanto, pode haver um grave impacto na latência de leitura e IOPS de leitura e, portanto, na experiência dos usuários interativos no sistema por 20 minutos ou mais. Prossiga com cautela.


Links úteis

AWS


0
0 119
Artigo Danusa Calixto · Nov. 27, 2023 9m read

A maioria dos aplicativos transacionais tem um perfil de leitura/escrita (RW) de 70:30. No entanto, alguns casos especiais têm perfis de IO extremamente altos.

Realizei testes de IO de armazenamento na região da AWS ap-southeast-2 (Sydney) para simular os padrões de IO do banco de dados do IRIS e uma taxa de transferência semelhante a de um aplicativo com uma alta taxa de escrita.

O objetivo do teste era determinar se os tipos de instância EC2 e os tipos de volume do EBS disponíveis nas regiões australianas da AWS oferecem suporte às altas taxas de IO e transferência necessárias.

Foram realizados ajustes mínimos no sistema operacional e no IRIS (veja a configuração do sistema operacional e do IRIS abaixo).

  • Os tipos de instância EC2 e de volume do EBS foram selecionados para maximizar as taxas de IOPS e transferência.

Os seguintes testes foram realizados:

  • O uso de um único volume io2 Block Express para cada banco de dados e WIJ.
  • O uso de um volume distribuído do Logical Volume Manager (LVM) de 16 discos gp3 para o banco de dados e um volume distribuído do LVM de cinco discos gp3 para o WIJ.
  • Duas instâncias em zonas de disponibilidade separadas usando o espelhamento do banco de dados síncrono do IRIS com um único volume io2 Block Express para o banco de dados e o WIJ em cada instância.

Resumo

Foi executado um número limitado de testes. No entanto, os resultados mostram que é possível executar uma carga de trabalho do IRIS de alta taxa de IO na região de Sydney da AWS.

Há limites em todo lugar na nuvem

  • É importante notar que, além dos limites das taxas de IOPS e de transferência publicados para instâncias e armazenamento, a AWS tem limites no nível da conta. A AWS precisou aumentar as cotas regionais de IOPS padrão da conta da InterSystems para 600.000 a fim de possibilitar os testes com altas taxas de IO — especificamente, EBS — de volumes de Provisioned IOPS SSD (io2).
    • Lembre-se de revisar os limites antes de começar os testes, principalmente para os testes de espelhamento, já que todos os volumes na região são incluídos no mesmo total.

Tipos de volume do EBS

  • Os testes de IO foram realizados com o banco de dados do IRIS usando um único volume io2 Block Express do EBS e vários (16) volumes gp3 do EBS utilizando o Logical Volume Manager (LVM).
    • A taxa de IOPS de escrita, a taxa de transferência e a latência do banco de dados foram semelhantes nos testes de io2 Block Express e de gp3 LVM. A latência da escrita do banco de dados foi de aproximadamente 1 ms.
    • A latência de leitura do gp3 foi duas vezes maior que o io2 Block Express. No entanto, a latência de leitura máxima do gp3 ainda era aceitável a menos de 0,8 ms.
    • A latência de escrita do WIJ foi cerca de 50% mais alta em um único volume io2 Block Express do que em cinco volumes gp3 distribuídos do LVM.
    • Para detalhes sobre o io2 Block Express, confira https://aws.amazon.com/ebs/provisioned-iops/.
    • Para detalhes sobre o gp3, confira https://aws.amazon.com/ebs/general-purpose/.

Tipos de instância EC2

  • Durante o teste em julho de 2023, somente um tipo de instância EC2 otimizada para a memória (r5b) na região de Sydney usava o sistema nitro capaz de executar os volumes io2 Block Express.
    • O tipo de instância EC2 selecionado precisa ser capaz de corresponder ou exceder a IO e a taxa de transferência do armazenamento necessárias para os volumes de dados e WIJ.
    • Os únicos tipos de instâncias em Sydney que são compatíveis com o io2 Block Express são r5b e c7g. c7g não foi usado devido a menor IOPS e baixa memória.
    • O io2 Block Express é necessário para uma maior taxa de transferência e de IOPS (256.000 IOPS por volume) em comparação com o io2 padrão (64.000 IOPS por volume).
    • As instâncias EC2 capazes de usar o io2 Block Express não estão disponíveis em Melbourne no momento.

Testes

  • Os utilitários RANREAD e RANWRITE disponíveis para o público foram usados para simular o perfil de IO do IRIS. Por exemplo, o RANWRITE usa o ciclo de WD do IRIS, resultando em uma rajada de grandes IOs do WIJ e escritas aleatórias de bancos de dados que geralmente não são esperadas ou compreendidas pelos fornecedores de armazenamento.

Ambiente da AWS

O seguinte ambiente foi testado em ap-southeast-2 (Sydney). O mesmo tipo de instância foi usado para todos os testes, fazendo uma comparação equivalente.

Perfil de instância EC2

Tipo de instância EC2: 

  • R5b.24xlarge: 96 vCPU, 780 GB de memória, 25 Gbps de rede.
    • Limites do EBS por instância EC2
      • Taxa de transferência máxima de (MB/s) 7.500
      • Taxa de IOPS máxima de 260.000

O pacote de benchmark usa uma distribuição do LVM de 4 MB mesmo se houver um ou vários volumes. Todos os volumes usam o sistema de arquivo xfs.


Testes de benchmark

  • Para o teste 1 e 2, o WIJ está em um volume separado.
    • O WIJ e o banco de dados podem estar no mesmo volume para reduzir os custos de armazenamento.
    • No entanto, ter o WIJ em um volume separado isola o impacto do WIJ nas leituras do banco de dados.
    • O volume do diário é sempre separado dos volumes do WIJ e do banco de dados.

Teste 1 - io2 Block Express

Layout do armazenamento

  • Diário
    • Único volume gp3. Usei os valores padrão: 3.000 IOPS e taxa de transferência de 125 MB/s.
  • WIJ
    • Único volume io2 Block Express. 256.000 IOPS e taxa de transferência de 4.000 MB/s.
  • Banco de dados
    • Único volume io2 Block Express. 100.000 IOPS e taxa de transferência de 4.000 MB/s.

Teste 2 - distribuição de LVM gp3

Layout do armazenamento

  • Diário
    • Único volume gp3. Usei os valores padrão: 3.000 IOPS e taxa de transferência de 125 MB/s.
  • WIJ
    • Cinco volumes gp3. Cada um com 16.000 IOPS e taxa de transferência de 1.000 MB/s. Total de 80.000 IOPS e taxa de transferência de 5.000 MB/s.
  • Banco de dados
    • 16 volumes gp3. Cada um com 16.000 IOPS e taxa de transferência de 1.000 MB/s. Total de 256.000 IOPS e taxa de transferência de 16.000 MB/s.

Teste 3 - espelho de banco de dados assíncrono do IRIS - io2 Block Express

Foi criado um espelho do banco de dados com o membro espelho principal na zona b de disponibilidade da AWS e o membro espelho de backup na zona c de disponibilidade. O árbitro estava na zona de disponibilidade a.

  • A mesma taxa de IO de leitura/escrita foi executada como nos outros testes.

Layout do armazenamento

  • Devido à cota total de 600.000 IOPS em todos os volumes, o WIJ e o banco de dados estão no mesmo volume io2 Block Express. Isso permite um total de (256K + 256K) 512 IOPS em todos os espelhos.

  • Diário

    • Único volume gp3. Usei os valores padrão: 3.000 IOPS e taxa de transferência de 125 MB/s.
  • WIJ e banco de dados

    • Único volume io2 Block Express. 256.000 IOPS e taxa de transferência de 4.000 MB/s.

Observações

IO de leitura do banco de dados

O gráfico abaixo mostra um io2 Block Express com aproximadamente metade da latência como uma distribuição do LVM de volumes gp3. O andamento dos testes de benchmark é controlado usando vários processos, resultando na metade de IOPS e no dobro da latência. Um teste baseado em aplicativo pode oferecer resultados diferentes ou o andamento dos testes pode ser alterado para aumentar os processos e a taxa de IOPS resultante.

image

IO de escrita do banco de dados

O gráfico abaixo mostra um pico semelhante de IOPS e latência entre o io2 Block Express e uma distribuição do LVM de volumes gp3. A maior latência de escrita do banco de dados do espelho principal ocorre porque o WIJ está no mesmo volume. A maior latência é o WIJ. A latência de escrita do banco de dados aleatório foi semelhante ao io2 Block Express. A latência é medida para escritas diferentes de zero.

image

IO de escrita do WIJ

O gráfico abaixo mostra a maior taxa de transferência para uma distribuição do LVM de volumes gp3. No entanto, isso é enganoso, já que o volume io2 Block Express teve uma taxa de transferência mais alta e um tempo de escrita do WIJ menor. Investigações adicionais podem mostrar, por exemplo, mais IOs no volume io2 Block Express. A latência é medida para escritas diferentes de zero.

image

Taxa de transferência

A taxa de transferência é uma das métricas cobradas pela AWS e tem um limite para cada instância EC2 (10.000 MB/s) e cada volume do EBS (4.000 MB/s para io2 Block Express e 1.000 MB/s para gp3). Semelhante à forma como IOPS são monitoradas e restringidas pela AWS ao aumentar a latência. O gráfico a seguir mostra a taxa de transferência em kB/s. Os requisitos da taxa de transferência precisam ser conhecidos para garantir que o subprovisionamento dessa taxa não se torne um gargalo. A taxa de transferência do WIJ é mais alta do que a taxa de transferência provisionada por volume. Isso pode ocorrer, já que a AWS leva alguns segundos para registrar que está ocorrendo uma taxa de IOPS ou de transferência mais alta do que o provisionado antes de limitá-la.

image

As escritas do banco de dados afetam a latência de leitura do banco de dados.

O gráfico mostra as tendências. No entanto, surgem vários detalhes ao examinar as métricas de forma mais detalhada. Por exemplo, as escritas do banco de dados são afetadas por uma rajada de escritas no mesmo volume. As escritas de maior tamanho do WIJ têm um impacto mais significativo. Os padrões de IO dos diferentes tipos de volumes também são interessantes.

io2 Block Express

O seguinte gráfico mostra os aumentos escalonados no processo de leitura durante o teste de io2 Block Express. Observe a queda nas leituras quando o armazenamento precisa ler e escrever ao mesmo tempo.

image

O gráfico a seguir mostra os picos correspondentes na latência. Observe a taxa de IOPS de leitura suave no gráfico acima e a latência achatada abaixo.

image

Distribuição do LVM gp3

Os mesmos gráficos da distribuição do LVM dos discos gp3 mostram um padrão de IO menos estável (às vezes, chamado de "jitter") dos discos gp3.

image

Compare a latência de leitura da linha de base com a latência máxima para os diferentes tipos de volumes.

image


Outros detalhes

Configuração do IRIS

A versão do IRIS do teste foi "2023.2.0.204.0".

A configuração do IRIS foi mínima. É exibido abaixo um fragmento do arquivo cpf.

[config]
Asyncwij=16
:
globals=0,0,589824,0,0,0
:
wdparm=16,32,512,128
wduseasyncio=1
  • Asyncwij = 16 significa 16 escritas do WIJ em trânsito por vez. O padrão é oito.

O parâmetro wdparm é usado para configurar o número de daemons de escrita (WDs) escravos, o tamanho da tabela do ID do dispositivo (usado para atribuir escritas a WDs) e o número máximo de escritas pendentes em todos os WDs, além de determinar se o banco de dados ajustará ou não o número de escritas permitidas POR WD e se alguns WDs estão inativos ou serão encerrados mais cedo em uma determinada passagem.

16 = número de daemons de escrita (8 é o padrão)
32 = tamanho da tabela de ID do dispositivo (32 é o padrão)
512 = número máximo de IOs pendentes para todos os daemons de escrita
128 = número máximo de IOs pendentes por daemon de escrita


Tempo de ping das zonas de disponibilidade (AZ) da AWS no dia do teste de espelho

O espelho principal estava na AZ b; o de backup, na AZ c.

  • O ping da AZ b para AZ c foi uma média de 1,16 ms
  • O ping da AZ c para AZ b foi uma média de 1,12 ms
0
0 168
Artigo Danusa Calixto · Nov. 27, 2023 1m read

Os contêineres nos arquivos ECS não são editáveis se o tamanho do arquivo for maior do que o espaço disponível de armazenamento temporário. Por exemplo, se eu tenho 4 GB livres, não posso editar um arquivo de 8 GB. No entanto, se eu iniciar um contêiner com 50 GB de armazenamento temporário (24 GB livre), posso editar normalmente meu arquivo de 8 GB. Até os atributos do arquivo não podem ser alterados: chattr -i <file> falha se a quantidade de armazenamento temporário não for suficiente (e então o banco de dados não pode ser montado para escrita).

Isso pode fazer com que o contêiner iris falhe na inicialização se o banco de dados grande for ambas leitura/escrita e tiver sido montado na inicialização. Possivelmente, pode causar a falha do contêiner durante o tempo de execução se o banco de dados passar de um limite definido pelo espaço disponível.

Para resolver o problema:

  • Torne grandes bancos de dados somente leitura
  • Aloque armazenamento temporário suficiente

Isso provavelmente se aplica ao EKS Fargate, mas não verifiquei.

0
0 89
Artigo Danusa Calixto · Maio 9, 2023 2m read

Neste artigo, vou tentar explicar a etapa de implantação do IAM no meu EC2(ubuntu).

O que é o IAM?

IAM é o InterSystems API Manager
consulte o link abaixo para saber mais sobre o IAM

https://docs.intersystems.com/components/csp/docbook/Doc.View.cls?KEY=PAGE_apimgr

descrição gerada: apimgr description.jpg

Antes de implantar o IAM

Confira a licença do host da API

 

Ative o IAM do usuário

Implante o IAM

Referência 

https://community.intersystems.com/post/introducing-intersystems-api-manager

Faça o download da imagem no link a seguir

https://wrc.intersystems.com/wrc/coDistGen.csp

Fiz o download da seguinte versão no meu PC

Upload da imagem no EC2

Usei o comando scp para fazer upload da imagem na minha nuvem

 

Confira se o docker e o docker compose estão instalados

Caso contrário, acesse o link a seguir

 https://docs.docker.com/engine/install/ubuntu/

Descompacte o arquivo tar de imagem

tar zpxvf IAM-3.0.2.0-4.tar.gz

 

Carregue a imagem no docker

sudo docker load -i iam_image.tar

Execute o iam-setup.sh

source ./iam-setup.sh  

Edite o arquivo: docker-compose.yml 

Para que seja possível acessar a IU do IAM pelo ambiente fora do EC2, substitua o localhost pelo endereço público do EC2 no parâmetro KONG_PORTAL_GUI_HOST e KONG_ADMIN_GUI_URL

vi docker-compose.yml 

Inicie o contêiner

sudo docker compose up -d

 

Confira a IU do IAM

Acesse a IU do IAM pelo link a seguir

http://yourEC2publicAddress:8002/overview

0
1 95
Artigo Danusa Calixto · Set. 13, 2022 31m read

A interoperabilidade é cada vez mais importante atualmente. O InterSystems IRIS 2022.1 tem uma nova API de mensagens para a comunicação com plataformas de streaming de eventos, como Kafka, AWS SQS/SNS, JMS e RabbitMQ.

Este artigo mostra como você pode se conectar ao Kafka e AWS SQS com facilidade.
Começamos com uma breve discussão dos conceitos e termos básicos das plataformas de streaming de eventos. 

Finalidade das plataformas de streaming de eventos e termos comuns

As plataformas de streaming de eventos, como Kafka ou AWS SQS, são capazes de consumir um stream de eventos sem restrições em uma frequência bastante alta e podem reagir a eventos. Os consumidores leem os dados dos streams para processamento adicional. Eles são geralmente usados em ambientes de IoT.

Os termos comuns nessa área são:

  • Tópico/Fila, o local onde os dados são armazenados
  • Produtor, cria e envia os dados (eventos, mensagens) para um tópico ou uma fila
  • Consumidor, lê os eventos/mensagens de um ou mais tópicos ou filas
  • Publicar/Assinar, os produtores enviam os dados para um tópico/fila (publicar), os consumidores assinam um tópico/fila e recebem notificações automáticas com a chegada de novos dados
  • Polling, os consumidores precisam buscar ativamente novos dados em um tópico/fila

Por que eles são usados?

  • Dissociação de produtores e consumidores
  • Altamente escalonável para dados em tempo real

Preciso mesmo deles? Como desenvolvedor do InterSystems IRIS, provavelmente não, mas você não está sozinho...

API de mensagens externas

As novas classes da API estão localizadas no pacote %External.Messaging. Ele contém as classes genéricas Client-, Settings- e Message.  As classes especializadas do Kafka, AWS SQS/SNS, JMS e RabbitMQ são subclasses dessas classes genéricas. 

O fluxo de comunicação básico é:

  1. Criar um objeto de configurações para a plataforma de destino. Isso também é responsável pela autenticação em relação à plataforma de destino.
  2. Criar um objeto de cliente específico e transmitir o objeto de configurações a ele.
  3. Criar um objeto de mensagem e enviar para o destino.

As seguintes seções demonstram como você pode se comunicar com Kafka e AWS SQS (Simple Queue Service).

Interação com o Kafka

Vamos começar com um exemplo do Kafka. Primeiro, criamos uma classe que usa a nova API %External Messaging para criar um tópico, enviar uma mensagem ao Kafka e receber uma mensagem dele.

Primeiro, ela cria um objeto de configurações do Kafka:

&lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
&lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
&lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = &lt;span class="hljs-string">"iris-consumer"&lt;/span>

Após definir o endereço do servidor do Kafka, ela define um ID de grupo do Kafka.

Com essas configurações, um objeto de cliente do Kafka é criado:

&lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)

Em seguida, um topico é criado ao invocar o método CreateTopic() do cliente do Kafka:

&lt;span class="hljs-keyword">Set&lt;/span> tSC = tClient.CreateTopic(pTopicName,tNumberOfPartitions,tReplicationFactor)

 Veja abaixo a amostra de código completa:

Include Kafka.Settings

&lt;span class="hljs-keyword">Class&lt;/span> Kafka.api [ Abstract ]
{

&lt;span class="hljs-keyword">ClassMethod&lt;/span> CreateTopic(pTopicName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = &lt;span class="hljs-string">"iris-consumer"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> tNumberOfPartitions = &lt;span class="hljs-number">1&lt;/span>
        &lt;span class="hljs-keyword">Set&lt;/span> tReplicationFactor = &lt;span class="hljs-number">1&lt;/span>
        &lt;span class="hljs-keyword">Set&lt;/span> tSC = tClient.CreateTopic(pTopicName,tNumberOfPartitions,tReplicationFactor)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSC)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())
        }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}



}

Depois de criar um tópico, podemos enviar e receber mensagens do Kafka. O código é semelhante ao exibido acima.

&lt;span class="hljs-keyword">ClassMethod&lt;/span> SendMessage(pMessage &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, pTopic &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tMessage &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaMessage&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = &lt;span class="hljs-string">"iris-consumer"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tMessage = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaMessage&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tMessage.topic = pTopic
        &lt;span class="hljs-keyword">set&lt;/span> tMessage.value = pMessage
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> producerSettings = &lt;span class="hljs-string">"{""key.serializer"":""org.apache.kafka.common.serialization.StringSerializer""}"&lt;/span>
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.UpdateProducerConfig(producerSettings))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.SendMessage(tMessage))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())

    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> ReceiveMessage(pTopicName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, pGroupId &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span> = &lt;span class="hljs-string">"iris-consumer"&lt;/span>, Output pMessages) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tMessage &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaMessage&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = pGroupId
        
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> producerSettings = &lt;span class="hljs-string">"{""key.serializer"":""org.apache.kafka.common.serialization.StringSerializer""}"&lt;/span>
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.UpdateProducerConfig(producerSettings))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.ReceiveMessage(pTopicName, .pMessages))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())
        }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

Vamos testar. Com uma instância do Kafka em execução, criamos um tópico community com o método CreateTopic acima:

Ignore os avisos log4j aqui. O método retorna um código de status OK. Então, o tópico foi criado. Em seguida, vamos enviar uma mensagem para esse tópico. Para verificar que a mensagem foi enviada ao tópico, executo um consumidor do Kafka genérico. Esse consumidor ouve o tópico community:

Então, vamos enviar uma mensagem para esse tópico. Vou enviar uma JSON-String para ele, mas você basicamente pode enviar qualquer formato de mensagem para um tópico.

Vamos conferir se o consumidor recebeu a mensagem:

A mensagem foi recebida com sucesso pelo consumidor.

Para receber mensagens e excluir tópicos, é parecido com a amostra acima. Veja abaixo a implementação da amostra completa. O arquivo include Kafka.settings só contém uma definição macro: #define KAFKASERVER <Kafka server location and port>.

Include Kafka.Settings

&lt;span class="hljs-keyword">Class&lt;/span> Kafka.api [ Abstract ]
{

&lt;span class="hljs-keyword">ClassMethod&lt;/span> CreateTopic(pTopicName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = &lt;span class="hljs-string">"iris-consumer"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> tNumberOfPartitions = &lt;span class="hljs-number">1&lt;/span>
        &lt;span class="hljs-keyword">Set&lt;/span> tReplicationFactor = &lt;span class="hljs-number">1&lt;/span>
        &lt;span class="hljs-keyword">Set&lt;/span> tSC = tClient.CreateTopic(pTopicName,tNumberOfPartitions,tReplicationFactor)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSC)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())
        }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> DeleteTopic(pTopicName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = &lt;span class="hljs-string">"iris-consumer"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> tNumberOfPartitions = &lt;span class="hljs-number">1&lt;/span>
        &lt;span class="hljs-keyword">Set&lt;/span> tReplicationFactor = &lt;span class="hljs-number">1&lt;/span>
        &lt;span class="hljs-keyword">Set&lt;/span> tSC = tClient.DeleteTopic(pTopicName)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSC)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())
        }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> SendMessage(pMessage &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, pTopic &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tMessage &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaMessage&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = &lt;span class="hljs-string">"iris-consumer"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tMessage = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaMessage&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tMessage.topic = pTopic
        &lt;span class="hljs-keyword">set&lt;/span> tMessage.value = pMessage
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> producerSettings = &lt;span class="hljs-string">"{""key.serializer"":""org.apache.kafka.common.serialization.StringSerializer""}"&lt;/span>
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.UpdateProducerConfig(producerSettings))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.SendMessage(tMessage))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())

    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> ReceiveMessage(pTopicName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, pGroupId &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span> = &lt;span class="hljs-string">"iris-consumer"&lt;/span>, Output pMessages) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tMessage &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.KafkaMessage&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.KafkaSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.servers = &lt;span class="hljs-built_in">$$$KAFKASERVER&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.groupId = pGroupId
        
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateKafkaClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-keyword">Set&lt;/span> producerSettings = &lt;span class="hljs-string">"{""key.serializer"":""org.apache.kafka.common.serialization.StringSerializer""}"&lt;/span>
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.UpdateProducerConfig(producerSettings))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.ReceiveMessage(pTopicName, .pMessages))
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.&lt;span class="hljs-keyword">Close&lt;/span>())
        }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

}

 

Interação com o AWS SQS

Como você se comunicaria com o AWS SQS (Simple Queue Service)? 
O procedimento básico é bastante similar. No entanto, o AWS exige a autenticação e não usa o termo "tópico". Ele fala sobre filas. Você pode enviar uma mensagem para uma fila, e os consumidores podem receber mensagens de uma ou mais filas.

Semelhante à classe da API acima, criei algo para o AWS SQS.

&lt;span class="hljs-keyword">Class&lt;/span> AWS.SQS.api [ Abstract ]
{

&lt;span class="hljs-keyword">ClassMethod&lt;/span> SendMessage(pMessage &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, pQueue &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tMessage &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSMessage&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(&lt;span class="hljs-keyword">##class&lt;/span>(AWS.Utils).GetCredentials(.tCredentials))
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.accessKey = tCredentials(&lt;span class="hljs-string">"aws_access_key_id"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.secretKey = tCredentials(&lt;span class="hljs-string">"aws_secret_access_key"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.sessionToken = tCredentials(&lt;span class="hljs-string">"aws_session_token"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.region = &lt;span class="hljs-string">"eu-central-1"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tMessage = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSMessage&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tMessage.body = pMessage
        &lt;span class="hljs-keyword">set&lt;/span> tMessage.queue = pQueue

        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateSQSClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.SendMessage(tMessage))

    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> ReceiveMessage(pQueueName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, Output pMessages) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(&lt;span class="hljs-keyword">##class&lt;/span>(AWS.Utils).GetCredentials(.tCredentials))
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.accessKey = tCredentials(&lt;span class="hljs-string">"aws_access_key_id"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.secretKey = tCredentials(&lt;span class="hljs-string">"aws_secret_access_key"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.sessionToken = tCredentials(&lt;span class="hljs-string">"aws_session_token"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.region = &lt;span class="hljs-string">"eu-central-1"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateSQSClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.ReceiveMessage(pQueueName, .pMessages))

    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> DeleteMessage(pQueueName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>, pReceiptHandle &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(&lt;span class="hljs-keyword">##class&lt;/span>(AWS.Utils).GetCredentials(.tCredentials))
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.accessKey = tCredentials(&lt;span class="hljs-string">"aws_access_key_id"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.secretKey = tCredentials(&lt;span class="hljs-string">"aws_secret_access_key"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.sessionToken = tCredentials(&lt;span class="hljs-string">"aws_session_token"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.region = &lt;span class="hljs-string">"eu-central-1"&lt;/span>
                &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateSQSClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.DeleteMessage(pQueueName, pReceiptHandle))

    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> CreateQueue(pQueueName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSQSSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSQueueSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(&lt;span class="hljs-keyword">##class&lt;/span>(AWS.Utils).GetCredentials(.tCredentials))
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.accessKey = tCredentials(&lt;span class="hljs-string">"aws_access_key_id"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.secretKey = tCredentials(&lt;span class="hljs-string">"aws_secret_access_key"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.sessionToken = tCredentials(&lt;span class="hljs-string">"aws_session_token"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.region = &lt;span class="hljs-string">"eu-central-1"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateSQSClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)

        &lt;span class="hljs-keyword">set&lt;/span> tSQSSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSQueueSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()

        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.CreateQueue(pQueueName,tSQSSettings))


    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

&lt;span class="hljs-keyword">ClassMethod&lt;/span> DeleteQueue(pQueueName &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%String&lt;/span>) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tClient &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSClient&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSQSSettings &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%External.Messaging.SQSQueueSettings&lt;/span>
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(&lt;span class="hljs-keyword">##class&lt;/span>(AWS.Utils).GetCredentials(.tCredentials))
        &lt;span class="hljs-keyword">set&lt;/span> tSettings = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.SQSSettings&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.accessKey = tCredentials(&lt;span class="hljs-string">"aws_access_key_id"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.secretKey = tCredentials(&lt;span class="hljs-string">"aws_secret_access_key"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.sessionToken = tCredentials(&lt;span class="hljs-string">"aws_session_token"&lt;/span>)
        &lt;span class="hljs-keyword">set&lt;/span> tSettings.region = &lt;span class="hljs-string">"eu-central-1"&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tClient = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%External.Messaging.Client&lt;/span>).CreateSQSClient(tSettings.ToJSON(),.tSc)
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tSc)

        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tClient.DeleteQueue(pQueueName))


    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    }

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

}

Ele contém métodos para criar e excluir filas, além de enviar e receber mensagens de uma fila.

Um dos principais pontos aqui é como autenticar. Não queria colocar minhas credenciais no meu código. Por isso, criei um método de ajuda para recuperar minhas credenciais no meu arquivo local e retornar como uma array subscrita para usar nos meus métodos da API:

&lt;span class="hljs-keyword">ClassMethod&lt;/span> GetCredentials(Output pCredentials) &lt;span class="hljs-keyword">As&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span>
{
    &lt;span class="hljs-keyword">#dim&lt;/span> tSc &lt;span class="hljs-keyword">as&lt;/span> &lt;span class="hljs-built_in">%Status&lt;/span> = &lt;span class="hljs-built_in">$$$OK&lt;/span>
    &lt;span class="hljs-keyword">set&lt;/span> tFilename = &lt;span class="hljs-string">"/dur/.aws/credentials"&lt;/span>
    &lt;span class="hljs-keyword">try&lt;/span> {
        &lt;span class="hljs-keyword">set&lt;/span> tCredentialsFile = &lt;span class="hljs-keyword">##class&lt;/span>(&lt;span class="hljs-built_in">%Stream.FileCharacter&lt;/span>).&lt;span class="hljs-built_in">%New&lt;/span>()
        &lt;span class="hljs-built_in">$$$ThrowOnError&lt;/span>(tCredentialsFile.LinkToFile(tFilename))
        &lt;span class="hljs-comment">// first read the header&lt;/span>
        &lt;span class="hljs-keyword">set&lt;/span> tBuffer = tCredentialsFile.ReadLine()
        &lt;span class="hljs-keyword">for&lt;/span> i=&lt;span class="hljs-number">1&lt;/span>:&lt;span class="hljs-number">1&lt;/span>:&lt;span class="hljs-number">3&lt;/span> {
            &lt;span class="hljs-keyword">set&lt;/span> tBuffer = tCredentialsFile.ReadLine()
            &lt;span class="hljs-keyword">set&lt;/span> pCredentials(&lt;span class="hljs-built_in">$piece&lt;/span>(tBuffer,&lt;span class="hljs-string">" ="&lt;/span>,&lt;span class="hljs-number">1&lt;/span>)) = &lt;span class="hljs-built_in">$tr&lt;/span>(&lt;span class="hljs-built_in">$piece&lt;/span>(tBuffer,&lt;span class="hljs-string">"= "&lt;/span>,&lt;span class="hljs-number">2&lt;/span>),&lt;span class="hljs-built_in">$c&lt;/span>(&lt;span class="hljs-number">13&lt;/span>))
        }
    }
    &lt;span class="hljs-keyword">catch&lt;/span> tEx {
        &lt;span class="hljs-keyword">set&lt;/span> tSc = tEx.AsStatus()
    } 

    &lt;span class="hljs-keyword">return&lt;/span> tSc
}

Para concluir este artigo, vamos criar uma fila community na região do AWS "eu-central-1" (Frankfurt, Alemanha).

A fila foi criada com sucesso e está visível no console do AWS para minha conta:

Em seguida, vamos enviar uma mensagem para essa fila:

O método call retorna 1. Portanto, a mensagem foi enviada com êxito.

Por fim, vamos usar o poll na fila do console do AWS:

A mensagem foi enviada para a fila com êxito.

Conclusão

A API de mensagens externas no InterSystems IRIS 2022.1 simplifica bastante a comunicação com as plataformas de streaming de eventos.
Espero que isso tenha sido útil.

0
0 87
Anúncio Danusa Calixto · Ago. 5, 2022

Olá, Comunidade,

Estamos felizes em anunciar que os webinars da comunidade estão de volta!

Convidamos todos vocês para o webinar de @sween sobre Scaling InterSystems FHIR Server on Amazon Web Services with ECP.

Participe deste webinar para fazer um aprofundamento técnico, ver uma demonstração e comparar o dimensionamento horizontal do InterSystems FHIR Server na Amazon Web Services com Enterprise Cache Protocol (ECP).

Data & Hora: Quinta-Feira, 18 de Agosto às 9 hrs
👨‍🏫 Palestrante: @sween, Arquiteto Full Stack na Integration Required


0
0 101
Artigo Angelo Bruno Braga · Fev. 25, 2022 27m read

Neste artigo iremos construir uma configuração IRIS de alta disponibilidade utilizando implantações Kubernetes com armazenamento persistente distribuído substituindo o "tradicional" espelhamento IRIS. Esta implantação será capaz de tolerar falhas relacionadas a infraestrutura como falhas em nós, armazenamento e de Zonas de Disponibilidade. A abordagem descrita reduz muito a complexidade da implantação em detrimento um objetivo de tempo de recuperação (RTO) ligeiramente estendido.

Figura 1 - Espelhamento Tradicional x Kubernetes com Armazenamento Distribuído

Todos os códigos fonte deste artigo estão disponíveis em https://github.com/antonum/ha-iris-k8s
TL;DR

Assumindo que você possui um cluster com 3 nós e também uma certa familiaridade com o Kubernetes – vá em frente:

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml

Se você não tem certeza sobre o que as duas linhas acima fazem ou não possui o sistema onde executá-las - pule para a seção “Requisitos de Alta Disponibilidade". Iremos explicar tudo detalhadamente conforme evoluirmos no artigo.

Esta primeira linha instala o Longhorn - armazenamento distribuído Kubernetes de código aberto. A segunda instala o InterSystems IRIS, utilizando um volume baseado no Longhorn para o Durable SYS.

Aguarde até que todos os pods atinjam o estado de "em execução". kubectl get pods -A

Agora você deve ser capaz de acessar o portal de administração do IRIS em http://<IP Público do IRIS>:52773/csp/sys/%25CSP.Portal.Home.zen  (a senha padrão é 'SYS') e a linha de comando do IRIS através de:

kubectl exec -it iris-podName-xxxx -- iris session iris

Simule a Falha

Agora pode começar a bagunçar as coisas. Mas, antes de fazê-lo, tente adicionar alguns dados na base de dados para se certificar que estarão lá quando o IRIS voltar a ficar disponível.

kubectl exec -it iris-6d8896d584-8lzn5 -- iris session iris
USER>set ^k8stest($i(^k8stest))=$zdt($h)_" running on "_$system.INetInfo.LocalHostName()
USER>zw ^k8stest
^k8stest=1
^k8stest(1)="01/14/2021 14:13:19 running on iris-6d8896d584-8lzn5"

Nossa "engenharia do caos" inicia aqui:

# Parar o IRIS - Contêiner será reiniciado automaticamente 
kubectl exec -it iris-6d8896d584-8lzn5 -- iris stop iris quietly
 
# Deletar o  pod - O Pod será recriado 
kubectl delete pod iris-6d8896d584-8lzn5
 
# "Drenar à força" o nó que está servindo o pod IRIS - O Pod seria recriado em outro nó
kubectl drain aks-agentpool-29845772-vmss000001 --delete-local-data --ignore-daemonsets --force
 
# Deletar o nó - O Pod seria recriado em outro nó
# bem... você não pode realmente fazê-lo com o kubectl. Localize a instância ou a VM e a destrua.
# se você possuir acesso à maquina - desligue a força ou desconecte o cabo de rede. Estou falando sério!

Requisitos de Alta Disponibilidade

 

Estamos construindo um sistema que possa tolerar uma falha das seguintes:

  • Instância IRIS dentro de um contêiner/VM. Falha a nível do IRIS.
  • Falha no Pod/Contêiner.
  • Indisponibilidade temporária do nó de cluster individual. Um bom exemplo seria quando uma Zona de Disponibilidade fica temporariamente fora do ar.
  • Falha permanente do nó de cluster individual ou disco.

Basicamente os cenários que executamos na seção ˜Simule a falha".

Se alguma destas falhas ocorrer, o sistema deverá se recuperar sem que necessite de nenhum envolvimento humano e sem que ocorra nenhuma perda de dados.. Tecnicamente existem limites do que a persistência de dados garante. O IRIS por si só provê com base no Ciclo do Journal e no uso de transações em uma aplicação: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=GCDI_journal#GCDI_journal_writecycle De qualquer forma, estamos falando de menos de dois segundos de objetivo de ponto de recuperação (RPO).

Outros componentes do sistema (Serviço de APIs Kubernetes, base de dados etcd, serviço de Balanceamento de Carga, DNS e outros) estão fora do escopo e são gerenciados tipicamente pelo Serviço Gerenciado Kubernetes como o Azure AKS ou AWS EKS, então assumimos que eles já estão em alta disponibilidade.

Outra forma de se ver – somos responsáveis por lidar com falhas individuais de componentes e armazenamento, assumindo que o resto é tratado pelo provedor de infraestrutura/nuvem.

Arquitetura

Quando se trata de alta disponibilidade para o InterSystems IRIS, a recomendação tradicional é a utilização de Espelhamento. Utilizando o Espelhamento você terá duas instâncias ligadas do IRIS replicando de forma síncrona os dados. Cada nó mantém uma cópia completa da base de dados e, se o nó principal falhar, os usuários reconectam no nó Backup. Essencialmente, com a abordagem de uso do Espelhamento, o IRIS é responsável pela redundância tanto de computação quanto de armazenamento.

Com os servidores de espelhamento implantados em zonas de disponibilidade distintas o espelhamento provê a redundância necessária tanto para falhas de computação quanto para falhas de armazenamento e permite o excelente objetivo de tempo de recuperação (RTO - tempo necessário para que um sistema se recupere após uma falha) de apenas poucos segundos. Você pode encontrar o modelo de implantação para o IRIS Espelhado na Nuvem AWS aqui: https://community.intersystems.com/post/intersystems-iris-deployment%C2%A0guide-aws%C2%A0using-cloudformation-template

O lado menos bonito do espelhamento é a complexidade de configurá-lo, realizando procedimentos de backups e restore e lidando com a falta de replicação para configurações de segurança e arquivos locais que não os de bases de dados.

Orquestradores de contêineres como o Kubernetes (espere, estamos em 2021… exitem outros?!) proveem uma redundância computacional através da implantação de objetosautomaticamente reiniciando o Pod/Contêiner IRIS no caso de falha. É por isso que você vê apenas um nó IRIS executando no diagrama de arquitetura Kubernetes. Ao invés de manter um segundo nó de IRIS executando nós terceirizamos a disponibilidade de computação para o Kubernetes. O Kubernetes se certificará que o pod IRIS pode ser recriado no caso de falha do pod original por qualquer motivo.

Figura 2 Cenario de Failover

Tudo bem até agora… Se o nó do IRIS falhar, o Kubernetes apenas cria um novo. Dependendo do seu cluster, ele leva algo entre 10 a 90 segundos para trazer o IRIS de volta ao ar após uma falha de computação. É um passo atrás comparado com apenas alguns segundos no espelhamento mas, se é algo que você pode tolerar no caso de um evento indesejado, a recompensa é a grande redução da complexidade. Sem configuração de espelhamento e nem configurações de segurança e replicações de arquivos com que se preocupar.

Sinceramente, se você se logar em um contêiner, executando o IRIS no Kubernetes, você nem mesmo perceberá que está executando em um ambiente de alta disponibilidade. Tudo parece e se comporta como uma implantação de uma instância individual de IRIS.

Espere, e quanto ao armazenamento? Estamos lidando com um banco de dados … Seja qual for o cenário de falha que possamos imaginar, nosso sistema deverá cuidar da persistência dos dados também. O Espelhamento depende da computação, local no nó IRIS. Se o nó morre ou fica temporariamente indisponível – o armazenamento para o nó também o fica. É por isso que na configuração de espelhamento o IRIS cuida da replicação das bases de dados no nível do IRIS.

Precisamos de um armazenamento que possa não só preservar o estado da base de dados na reinicialização do contêiner mas também possa prover redundância em casos como a queda do nó ou de um segmento inteiro da rede (Zona de Disponibilidade). A apenas alguns anos atrás não existia uma resposta fácil para isso.Como você pode supor a partir do diagrama acima – temos uma baita resposta agora. É chamada de armazenamento distribuído de contêineres.

O armazenamento distribuído abstrai os volumes de hospedeiros subjacentes e os apresenta como um armazenamento conjunto disponível para todos os nós do cluster k8s. Nós utilizamos o Longhorn https://longhorn.io neste artigo; é grátis, de código aberto e bem fácil de instalar. Mas, você também pode verificar outros como o OpenEBS, Portworx e StorageOS que devem disponibilizar as mesmas funcionalidades. Rook Ceph é outro projeto de incubação CNCF a se considerar. No outro lado do espectro existem soluções de armazenamento de nível empresarial como o NetApp, PureStorage e outros.

Guia Passo a Passo

Na seção TL;DR nós instalamos tudo de uma vez. O Apêndice B lhe guiará através da instalação passo a passo e dos procedimentos de validação.

Armazenamento Kubernetes

Vamos voltar um pouco por um segundo e falar sobre contêineres e armazenamento em geral e como o IRIS se encaixa no cenário.

Por padrão todos os dados dentro do contêiner são efêmeros. Quando o contêiner morre, o dado desaparece. No Docker, você pode utilizar o conceito de volumes. Essencialmente isto permite que você exponha o diretório de seu SO hospedeiro para o contêiner.

docker run --detach
  --publish 52773:52773
  --volume /data/dur:/dur
  --env ISC_DATA_DIRECTORY=/dur/iconfig
  --name iris21 --init intersystems/iris:2020.3.0.221.0

No exemplo acima estamos iniciando o contêiner IRIS e fazendo com que o diretório local do hospedeiro ‘/data/dur’ fique acessível no ponto de montagem ‘/dur’ do contêiner. Desta forma, se o contêiner estiver armazenando qualquer coisa neste diretório, ela será preservada e disponível para utilização quando o próximo contêiner iniciar.

Do lado IRIS das coisas, podemos instruir o IRIS para armazenar todos os dados que devem sobreviver ao reinicio do contêiner em um diretório determinado especificando ISC_DATA_DIRECTORY. Durable SYS é o nome da funcionalidade do IRIS que você pode precisar dar uma olhada na documentação https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_durable_running

No Kubernetes a sintaxe é diferente mas os conceitos são os mesmos.

Aqui está a Implantação Kubernetes básica para IRIS.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: iris
spec:
  selector:
    matchLabels:
      app: iris
  strategy:
    type: Recreate
  replicas: 1
  template:
    metadata:
      labels:
        app: iris
    spec:
      containers:
      - image: store/intersystems/iris-community:2020.4.0.524.0
        name: iris
        env:
        - name: ISC_DATA_DIRECTORY
          value: /external/iris
        ports:
        - containerPort: 52773
          name: smp-http
        volumeMounts:
        - name: iris-external-sys
          mountPath: /external
      volumes:
      - name: iris-external-sys
        persistentVolumeClaim:
          claimName: iris-pvc

 

Na especificação de implantação acima, a parte ‘volumes’ lista os volumes de armazenamento. Eles podem estar disponíveis fora do contêiner através do Requisição de Volume Persistente (PersistentVolumeClaim) como ‘iris-pvc’. O 'volumeMounts' expõe este volume dentro do contêiner. ‘iris-external-sys’ é o identificador que amarra a montagem do volume ao volume específico. Na verdade, podemos ter múltiplos volumes e este nome é utilizado apenas para distinguir um de outro. Você pode chamá-lo de ‘steve’ se quiser.

A variável de ambiente ISC_DATA_DIRECTORY, já familiar, instrui o IRIS a utilizar um ponto de montagem específico para armazenar todos os dados que precisam sobreviver ao reinício do contêiner.

Agora vamos dar uma olhada na Requisição de Volume Persistente iris-pvc.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: iris-pvc
spec:
  storageClassName: longhorn
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

 

Bastante direto. Requisitando 10 gigabytes, montado como Read/Write em apenas um nó, utilizando a classe de armazenamento de ‘longhorn’.

Aquela storageClassName: longhorn é de fato crítica aqui.

Vamos olhar quais classes de armazenamento estão disponíveis no meu cluster AKS:

kubectl get StorageClass
NAME                             PROVISIONER                     RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
azurefile                        kubernetes.io/azure-file        Delete          Immediate           true                   10d
azurefile-premium                kubernetes.io/azure-file        Delete          Immediate           true                   10d
default (default)                kubernetes.io/azure-disk        Delete          Immediate           true                   10d
longhorn                         driver.longhorn.io              Delete          Immediate           true                   10d
managed-premium                  kubernetes.io/azure-disk        Delete          Immediate           true                   10d

Existem poucas classes de armazenamento do Azure, instaladas por padrão e uma do Longhorn que instalamos como parte de nosso primeiro comando:

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml

Se você comentar #storageClassName: longhorn na definição da Requisição de Volume Persistente, será utilizada a classe de armazenamento que estiver marcada como “default” que é o disco regular do Azure.

Para ilustrar porquê precisamos de armazenamento distribuído vamos repetir o experimento da “engenharia do caos” que descrevemos no início deste artigo sem o armazenamento longhorn. Os dois primeiros cenários (parar o IRIS e deletar o Pod) deveriam completar com sucesso e os sistemas deveriam se recuperar ao estado operacional. Ao tentar tanto drenar ou destruir o nó deveria deixar o sistema em estado de falha.

#drenar o nó a força
kubectl drain aks-agentpool-71521505-vmss000001 --delete-local-data --ignore-daemonsets

kubectl describe pods ...   Type     Reason            Age                  From               Message   ----     ------            ----                 ----               -------   Warning  FailedScheduling  57s (x9 over 2m41s)  default-scheduler  0/3 nodes are available: 1 node(s) were unschedulable, 2 node(s) had volume node affinity conflict.

Essencialmente, o Kubernetes tentará reiniciar o pod IRIS pod no cluster mas, o nó onde ele iniciou originalmente não está disponível e os outros dois nós apresentam “conflito de afinidade de nó de volume”. Com este tipo de armazenamento o volume só está disponível no nó onde ele foi originalmente criado, visto que ele é basicamente amarrado ao disco disponível no nó hospedeiro.

Com o longhorn como classe de armazenamento, tanto o experimento “drenar a força” quanto o “destruir nó” funcionam com sucesso e o pod IRIS retorna a operação brevemente. Para conseguí-lo o Longhorn assume controle dos armazenamentos disponíveis dos 3 nós do cluster e replica os dados através deles. O Longhorn prontamente repara o armazenamento do cluster se um dos nós fica permanentemente indisponível. No nosso cenário “Destruir nó” o pod IRIS é reiniciado em outro nó rapidamente utilizando as replicas nos dois outros volumes remanescentes.  Então, o AKS provisiona um novo nó para substituir o nó perdido e assim que ele está pronto, o Longhorn entra em ação e reconstrói os dados necessários no novo nó. Tudo é automático, sem seu envolvimento.

Figura 3 Longhorn reconstruindo a réplica do volume no nó substituído

Mais sobre implantação k8s

Vamos dar uma olhada em alguns outros aspectos de nossa implantação:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: iris
spec:
  selector:
    matchLabels:
      app: iris
  strategy:
    type: Recreate
  replicas: 1
  template:
    metadata:
      labels:
        app: iris
    spec:
      containers:
      - image: store/intersystems/iris-community:2020.4.0.524.0
        name: iris
        env:
        - name: ISC_DATA_DIRECTORY
          value: /external/iris
        - name: ISC_CPF_MERGE_FILE
          value: /external/merge/merge.cpf
        ports:
        - containerPort: 52773
          name: smp-http
        volumeMounts:
        - name: iris-external-sys
          mountPath: /external
        - name: cpf-merge
          mountPath: /external/merge
        livenessProbe:
          initialDelaySeconds: 25
          periodSeconds: 10
          exec:
            command:
            - /bin/sh
            - -c
            - "iris qlist iris | grep running"
      volumes:
      - name: iris-external-sys
        persistentVolumeClaim:
          claimName: iris-pvc
      - name: cpf-merge
        configMap:
          name: iris-cpf-merge

 

strategy: Recreate, replicas: 1 informa ao Kubernetes que em algum momento ele deve manter uma e exatamente uma instância de do pod IRIS executando. Isto é o que dá conta do nosso cenário “deletar pod”.

A seção livenessProbe garante que o IRIS está sempre ativo no contêiner e trata o cenário “IRIS está fora”. initialDelaySeconds garante algum tempo para que o IRIS inicie. Você pode querer aumentá-lo se o IRIS estiver levando um tempo considerável para iniciar em sua implementação.

CPF MERGE funcionalidade do IRIS que lhe permite modificar o conteúdo do arquivo de configuração iris.cpf durante a inicialização do contêiner. Veja https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_cpf#RACS_cpf_edit_merge para a documentação referente a ela. Neste exemplo estou utilizando o Kubernetes Config Map para gerenciar o conteúdo do arquivo mesclado: https://github.com/antonum/ha-iris-k8s/blob/main/iris-cpf-merge.yaml Aqui ajustamos os valores para os global buffers e gmheap, utilizados pela instância do IRIS, mais tudo que você pode encontrar no arquivo iris.cpf. Você pode até mesmo alterar a senha padrão do IRIS utilizando o campo `PasswordHash`no arquivo CPF Merge. Leia mais em: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_images_password_auth

Além da Requisição de Volume Persistente https://github.com/antonum/ha-iris-k8s/blob/main/iris-pvc.yaml da implantação https://github.com/antonum/ha-iris-k8s/blob/main/iris-deployment.yaml e ConfigMap com o conteúdo do CPF Merge https://github.com/antonum/ha-iris-k8s/blob/main/iris-cpf-merge.yaml nossa implantação precisa de um serviço que exponha a implantação IRIS para a Internet pública: https://github.com/antonum/ha-iris-k8s/blob/main/iris-svc.yaml

kubectl get svc
NAME         TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)           AGE
iris-svc     LoadBalancer   10.0.18.169   40.88.123.45   52773:31589/TCP   3d1h
kubernetes   ClusterIP      10.0.0.1      <none>          443/TCP           10d

O IP Externo do iris-svc pode ser utilizado para acessar o portal de administração do IRIS através de  http://40.88.123.45:52773/csp/sys/%25CSP.Portal.Home.zen. A senha padrão é 'SYS'.

Backup/Restore e Dimensionamento do Armazenamento

Longhorn disponibiliza uma interface web para usuários para configurar e gerenciar volumes.

Identificar o pod, executar o componente de interface para usuários (longhorn-ui) e estabelecer um encaminhamento de portas com kubectl:

kubectl -n longhorn-system get pods
# note the longhorn-ui pod id.

kubectl port-forward longhorn-ui-df95bdf85-gpnjv 9000:8000 -n longhorn-system

A interface para usuários Longhorn ficará disponível em http://localhost:9000

Figura 4 Longhorn UI

Além da alta disponibilidade, a maioria das soluções de armazenamento para contêineres disponibilizam opções convenientes para backup, snapshots e restore. Os detalhes são específicos para cada implantação mas a convenção comum é de que o backup é associado ao VolumeSnapshot. Também é assim para o Longhorn. Dependendo da sua versão do Kubernetes e do seu provedor você também pode precisar instalar o volume snapshotter https://github.com/kubernetes-csi/external-snapshotter

`iris-volume-snapshot.yaml` é um exemplo de um snapshot de volume. Antes de utilizá-lo você deve configurar backups ou para um bucket S3 ou para um volume NFS no Longhorn. https://longhorn.io/docs/1.0.1/snapshots-and-backups/backup-and-restore/set-backup-target/

# Realizar um backup consistente do volume IRIS
kubectl apply -f iris-volume-snapshot.yaml

Para o IRIS é recomendado que você execute o External Freeze antes de realizar o backup/snapshot e então o Thaw depois. Veja os detalhes aqui: https://docs.intersystems.com/irisforhealthlatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=Backup.General#ExternalFreeze  

Para aumentar o tamanho do volume IRIS - ajuste a requisição de armazenamento na requisição de volume persistente (arquivo `iris-pvc.yaml`), utilizado pelo IRIS.

...
  resources:
    requests:
      storage: 10Gi #altere este valor para o necessário

Então, aplique novamente a especificação pvc. O Longhorn não consegue aplicar esta alteração enquanto o volume está conectado ao Pod em execução. Temporariamente altere o contador de réplicas para zero na implantação para que o tamanho do volume possa ser aumentado.

Alta Disponibilidade – Visão Geral

No início deste artigo nós definimos alguns critérios para alta disponibilidade. Aqui está como conseguimos alcançá-los com esta arquitetura:

Domínio de Falha

Mitigado automaticamente por

Instância IRIS no contêiner/VM. Falha no nível do IRIS.

Sonda Deployment Liveness reinicia o contêiner no caso do IRIS estar fora

Falha de Pod/Contêiner.

Implantação recria o Pod

Indisponibilidade temporária de um nó do cluster individual. Um bom exemplo seria uma Zona de Disponibilidade fora.

Implantação recria o pod em outro nó. O Longhorn torna os dados disponíveis em outro nó.

Falha permanente de um nó de cluster individual ou disco.

Mesmo do anteiror + k8s cluster autoscaler substituindo um nó danificado por um novo. O Longhorn reconstrói os dados no novo nó.

Zumbis e outras coisas a considerar

Se você estiver familiarizado em executar o IRIS em contêineres Docker, você já deve ter utilizado a flag `--init`.

docker run --rm -p 52773:52773 --init store/intersystems/iris-community:2020.4.0.524.0

O objetivo desta flag é previnir a formação de processos "zumbis". No Kubernetes, você pode tanto utilizar ‘shareProcessNamespace: true’ (considerações de segurança se aplicam) ou em seus próprios contêineres utilizar `tini`. Exemplo de Dockerfile com tini:

FROM iris-community:2020.4.0.524.0
...
# Add Tini
USER root
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
USER irisowner
ENTRYPOINT ["/tini", "--", "/iris-main"]

Desde 2021, todas as imagens de contêineres disponibilizadas pela InterSystems incluiria tini por padrão.

Você pode posteriormente diminuir o tempo de recuperação para os cenários “Drenar a força o nó/destruir nó” ajustando poucos parâmetros:

Política de Deleção de Pods do Longhorn https://longhorn.io/docs/1.1.0/references/settings/#pod-deletion-policy-when-node-is-down e despejo baseado em taint do kubernetes: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-based-evictions

Disclaimer

Como funcionário InterSystems, Eu tenho que colocar isto aqui: O Longhorn é utilizado neste artigo como um exemplo de Armazenamento em Blocos Distribuído para Kubernetes. A InterSystems não valida e nem emite uma declaração de suporte oficial para soluções ou produtos de armazenamento individual. Você precisa testar e validar se qualquer solução específica de armazenamento atende a suas necessidades.

Solucões para armazenamento distribuído podem possuir características substancialmente distintas de performance., quando comparadas a armazenamento em nó local. Especialmente para operações de escrita, onde os dados devem ser escritos em múltiplas localidades antes de ser considerado em estado persistente. Certifique-se de testar suas cargas de trabalho e entender o comportamento específico, bem como as opções que seu driver para Interface de Armazenamento de Contêiner (CSI) oferece.

Basicamente, a InterSystems não valida e/ou endossa soluções específicas de armazenamento como o Longhorn da mesma forma que não valida marcas de HDs ou fabricantes de hardware para servidores. Eu pessoalmente achei o Longhorn fácil de se utilizar e seu time de desenvolvimento extremamente responsivo e útil na página do projeto no GitHub. https://github.com/longhorn/longhorn 

Conclusão

O ecossistema Kubernetes evoluiu de forma significante nos últimos anos e, com a utilização de soluções de armazenamento em blocos distribuído, você pode agora construir uma configuração de Alta Disponibilidade que pode manter uma instância IRIS, nó de cluster e até mesmo falhas de Zonas de Disponibilidade.

Você pode terceirizar a alta disponibilidade de computação e armazenamento para componentes do Kubernetes, resultando em um sistema significativamente mais simples de se configurar e manter, comparando-se ao espelhamento tradicional do IRIS. Da mesma forma, esta configuração pode não lhe prover o mesmo RTO e nível de performance de armazenamento que uma configuração de Espelhamento.

Neste artigo criamos uma configuração IRIS de alta disponibilidade utilizando o Azure AKS como Kubernetes gerenciado e o sistema de armazenamento distribuído Longhorn. Você pode explorar múltiplas alternativas como AWS EKS, Google Kubernetes Engine para K8s gerenciados, StorageOS, Portworx e OpenEBS para armazenamento distribuído para contêiner ou mesmo soluções de armazenamento de nível empresarial como NetApp, PureStorage, Dell EMC e outras.

Apêndice A. Criando um Cluster Kubernetes na nuvem

Serviço Gerenciado Kubernetes de um dos provedores públicos de nuvem é uma forma fácil de criar um cluster k8s necessário para esta configuração.A configuração padrão do AKS da Azure é pronto para ser utilizado para a implantação descrita neste artigo.

Criar um novo cluster AKS com 3 nós. Deixe todo o resto padrão.

Figura 5 Criar um cluster AKS

Instale o kubectl em seu computador localmente: https://kubernetes.io/docs/tasks/tools/install-kubectl/

Registre seu cluster AKS com o kubectl local

 

Figura 6 Registre o cluster AKS com kubectl

Depois disto, você pode voltar para o início do artigoe instalar o longhorn e a implantação IRIS.

A instalação no AWS EKS é um pouco mais complicada. Você precisa se certificar que cada instância em seu grupo de nós tem o open-iscsi instalado.

sudo yum install iscsi-initiator-utils -y

Instalar o Longhorn no GKE necessita de um passo extra, descrito aqui: https://longhorn.io/docs/1.0.1/advanced-resources/os-distro-specific/csi-on-gke/

Apêndice B. Instalação Passo a Passo

Passo 1 – Cluster Kubernetes e kubectl

Você precisa um cluster k8s com 3 nós. Apêndice A descreve como conseguir um na Azure.

$ kubectl get nodes
NAME                                STATUS   ROLES   AGE   VERSION
aks-agentpool-29845772-vmss000000   Ready    agent   10d   v1.18.10
aks-agentpool-29845772-vmss000001   Ready    agent   10d   v1.18.10
aks-agentpool-29845772-vmss000002   Ready    agent   10d   v1.18.10

Passo 2 – Instalar o Longhorn

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml

Certifique-se de que todos os pods no namespace ‘longhorn-system’ estão no estado de em execução. Isso pode levar alguns minutos.

$ kubectl get pods -n longhorn-system
NAME                                       READY   STATUS    RESTARTS   AGE
csi-attacher-74db7cf6d9-jgdxq              1/1     Running   0          10d
csi-attacher-74db7cf6d9-l99fs              1/1     Running   1          11d
...
longhorn-manager-flljf                     1/1     Running   2          11d
longhorn-manager-x76n2                     1/1     Running   1          11d
longhorn-ui-df95bdf85-gpnjv                1/1     Running   0          11d

Consulteo guia de instalação do Longhornpara detalhes e solução de problemas https://longhorn.io/docs/1.1.0/deploy/install/install-with-kubectl

Passo 3 – Faça um clone do repositório GitHub

$ git clone https://github.com/antonum/ha-iris-k8s.git
$ cd ha-iris-k8s
$ ls
LICENSE                   iris-deployment.yaml      iris-volume-snapshot.yaml
README.md                 iris-pvc.yaml             longhorn-aws-secret.yaml
iris-cpf-merge.yaml       iris-svc.yaml             tldr.yaml

Passo 4 – implemente e valide os componentes um a um

o arquivo tldr.yaml contém todos os componentes necessários para a implantação em um pacote. Aqui iremos instalá-los um a um e validar a configuração de cada um deles individualmente.

# Se você aplicou o tldr.yaml previamente, apague-o.
$ kubectl delete -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml

Criar a Requisição de Volume Persistente

$ kubectl apply -f iris-pvc.yaml persistentvolumeclaim/iris-pvc created

$ kubectl get pvc NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE iris-pvc   Bound    pvc-fbfaf5cf-7a75-4073-862e-09f8fd190e49   10Gi       RWO            longhorn       10s

Criar o Mapa de Configuração

$ kubectl apply -f iris-cpf-merge.yaml

$ kubectl describe cm iris-cpf-merge Name:         iris-cpf-merge Namespace:    default Labels:       <none> Annotations:  <none>

Data

merge.cpf:

[config] globals=0,0,800,0,0,0 gmheap=256000 Events:  <none>

criar a implantação iris

$  kubectl apply -f iris-deployment.yaml deployment.apps/iris created

$ kubectl get pods                     NAME                    READY   STATUS              RESTARTS   AGE iris-65dcfd9f97-v2rwn   0/1     ContainerCreating   0          11s

Anote o nome do pod. Você irá utilizá-lo para conectar ao pod no próximo comando

$ kubectl exec -it iris-65dcfd9f97-v2rwn   -- bash

irisowner@iris-65dcfd9f97-v2rwn:~$ iris session iris Node: iris-65dcfd9f97-v2rwn, Instance: IRIS

USER>w $zv IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2020.4 (Build 524U) Thu Oct 22 2020 13:04:25 EDT

h<enter> to exit IRIS shell

exit<enter> to exit pod

acesse os logs do contêiner IRIS

$ kubectl logs iris-65dcfd9f97-v2rwn ... [INFO] ...started InterSystems IRIS instance IRIS 01/18/21-23:09:11:312 (1173) 0 [Utility.Event] Private webserver started on 52773 01/18/21-23:09:11:312 (1173) 0 [Utility.Event] Processing Shadows section (this system as shadow) 01/18/21-23:09:11:321 (1173) 0 [Utility.Event] Processing Monitor section 01/18/21-23:09:11:381 (1323) 0 [Utility.Event] Starting TASKMGR 01/18/21-23:09:11:392 (1324) 0 [Utility.Event] [SYSTEM MONITOR] System Monitor started in %SYS 01/18/21-23:09:11:399 (1173) 0 [Utility.Event] Shard license: 0 01/18/21-23:09:11:778 (1162) 0 [Database.SparseDBExpansion] Expanding capacity of sparse database /external/iris/mgr/iristemp/ by 10 MB.

crie o serviço iris

$ kubectl apply -f iris-svc.yaml    service/iris-svc created

$ kubectl get svc NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)           AGE iris-svc     LoadBalancer   10.0.214.236   20.62.241.89   52773:30128/TCP   15s

Passo 5 – Acesse o portal de administração

Finalmente – conecte-se ao portal de administração do IRIS, utilizando o IP externo do serviço: http://20.62.241.89:52773/csp/sys/%25CSP.Portal.Home.zen usuário _SYSTEM, senha SYS. Será solicitado que você altere no seu primeiro login.

 

0
0 324
Anúncio Ben Spead · Jul. 7, 2021

Olá Desenvolvedores!

Vocês já tiveram que converter mensagens HL7v2 para FHIR (Fast Healthcare Interoperability Resources) e acharam o processo de transformação complicado e confuso? A InterSystems está criando uma nova oferta de SaaS baseada em nuvem chamada de Serviços de Transformação de Mensagens do HealthShare que faz com que este processo se torne simples. Estamos muito empolgados de anunciar um Programa de Acesso Antecipado para esta nossa nova oferta e adoraríamos que vocês o utilizassem e então nos dessem seu feedback a respeito do mesmo! Para isso basta que vocês possuam uma conta grátis AWS, com um bucket S3 para colocar suas mensagens HL7v2 e outro bucket S3 para colocar a saída em FHIR. 

0
0 128
Anúncio Rochael Ribeiro · Maio 31, 2021

Desenvolvedores,

Decidimos estender o período de inscrição para o InterSystems FHIR Accelerator Programming Contest e a votação começará apenas na quinta-feira, 3 de junho! O período de votação vai durar até domingo, 6 de junho.

Portanto, não se esqueça de apoiar as melhores soluções construídas usando o InterSystems IRIS FHIR Accelerator Service (FHIRaaS) no AWS.

➡️ Vote aqui de 3 a 6 de Junho!

How to vote and what's new?

0
0 71
Anúncio Angelo Bruno Braga · Maio 20, 2021

Olá Desenvolvedores,

O período de registro para o Concurso de Programação Acelerador FHIR da InterSystems está em pleno andamento! Nós convidamos todos os desenvolvedores FHIR para criar uma aplicação nova ou testar aplicações existentes utilizando o InterSystems IRIS FHIR Accelerator Service (FHIRaaS) on AWS

E agora você tem uma grande oportunidade para ganhar um acesso GRÁTIS ao FHIRaaS na AWS!  Então, para dar o primeiro passo para dominar o FHIRaaS, você precisa se registrar em nosso Portal FHIR Portal utilizando este link:

👉🏼 https://portal.trial.isccloud.io/account/signup

0
0 127
Artigo Vinicius Maranhao Ribeiro de Castro · Mar. 9, 2021 12m read

Introdução

Com a transformação digital no mundo dos negócios, novos recursos ou funcionalidades nos softwares oferecidos por uma empresa, podem significar vantagem competitiva. No entanto, se o time de TI não estiver preparado com a cultura, metodologia, práticas e ferramentas corretas, pode ser muito difícil garantir a entrega dessas novas funcionalidades a tempo hábil.

0
0 859
Artigo Mikhail Khomenko · Nov. 23, 2020 21m read

Imagine que você queira ver o que a tecnologia InterSystems pode oferecer em termos de análise de dados. Você estudou a teoria e agora quer um pouco de prática. Felizmente, a InterSystems oferece um projeto que contém alguns bons exemplos: Samples BI. Comece com o arquivo README, pulando qualquer coisa associada ao Docker, e vá direto para a instalação passo a passo. Inicie uma instância virtual, instale o IRIS lá, siga as instruções para instalar o Samples BI e, a seguir, impressione o chefe com belos gráficos e tabelas. Por enquanto, tudo bem. 

Inevitavelmente, porém, você precisará fazer alterações.

Acontece que manter uma máquina virtual sozinha tem algumas desvantagens e é melhor mantê-la com um provedor em nuvem. A Amazon parece sólida e você cria uma conta no AWS (gratuita para iniciar), lê que usar a identidade do usuário root para tarefas diárias é ruim e cria um usuário IAM normal com permissões de administrador.

Clicando um pouco, você cria sua própria rede VPC, sub-redes e uma instância virtual EC2, e também adiciona um grupo de segurança para abrir a porta web IRIS (52773) e a porta ssh (22) para você. Repete a instalação do IRIS e Samples BI. Desta vez, usa o script Bash ou Python, se preferir. Mais uma vez, impressiona o chefe.

Mas o movimento DevOps onipresente leva você a começar a ler sobre infraestrutura como código e você deseja implementá-la. Você escolhe o Terraform, já que ele é bem conhecido de todos e sua abordagem é bastante universal - adequada com pequenos ajustes para vários provedores em nuvem. Você descreve a infraestrutura em linguagem HCL e traduz as etapas de instalação do IRIS e Samples BI para o Ansible. Em seguida, você cria mais um usuário IAM para permitir que o Terraform funcione. Executa tudo. Ganha um bônus no trabalho.

Gradualmente, você chega à conclusão de que, em nossa era de microsserviços, é uma pena não usar o Docker, especialmente porque a InterSystems lhe diz como. Você retorna ao guia de instalação do Samples BI e lê as linhas sobre o Docker, que não parecem complicadas:

$ docker pull intersystemsdc/iris-community:2019.4.0.383.0-zpm
$ docker run --name irisce -d --publish 52773:52773 intersystemsdc/iris-community:2019.4.0.383.0-zpm
$ docker exec -it irisce iris session iris
USER>zpm
zpm: USER>install samples-bi

Depois de direcionar seu navegador para ttp://localhost:52773/csp/user/_DeepSee.UserPortal.Home.zen?$NAMESPACE=USER, você vai novamente ao chefe e tira um dia de folga por um bom trabalho.

Você então começa a entender que “docker run” é apenas o começo e você precisa usar pelo menos docker-compose. Não é um problema:

$ cat docker-compose.yml
version: "3.7"
services:
  irisce:
    container_name: irisce
    image: intersystemsdc/iris-community:2019.4.0.383.0-zpm
    ports:
    - 52773:52773
$ docker rm -f irisce # We don’t need the previous container
$ docker-compose up -d

Então, você instala o Docker e o docker-compose com o Ansible e, em seguida, apenas executa o contêiner, que fará o download de uma imagem se ainda não estiver presente na máquina. Em seguida, você instala Samples BI.

Você certamente gosta do Docker, porque é uma interface simples e legal para várias coisas do kernel. Você começa a usar o Docker em outro lugar e geralmente inicia mais de um contêiner. E descobre que muitas vezes os contêineres devem se comunicar entre si, o que leva à leitura sobre como gerenciar vários contêineres. 

E você chega ao Kubernetes

Uma opção para mudar rapidamente de docker-compose para Kubernetes é usar o kompose. Pessoalmente, prefiro simplesmente copiar os manifestos do Kubernetes dos manuais e, em seguida, editá-los, mas o kompose faz um bom trabalho ao concluir sua pequena tarefa:

$ kompose convert -f docker-compose.yml
INFO Kubernetes file "irisce-service.yaml" created
INFO Kubernetes file "irisce-deployment.yaml" created

Agora você tem os arquivos de implantação e serviço que podem ser enviados para algum cluster do Kubernetes. Você descobre que pode instalar um minikube, que permite executar um cluster Kubernetes de nó único e é exatamente o que você precisa neste estágio. Depois de um ou dois dias brincando com a sandbox do minikube, você está pronto para usar uma implantação real e ao vivo do Kubernetes em algum lugar da nuvem AWS.

Preparação

Então, vamos fazer isso juntos. Neste ponto, faremos algumas suposições:

Primeiro, presumimos que você tenha uma conta no AWS, saiba seu ID e não use credenciais de root. Você criou um usuário IAM (vamos chamá-lo de “my-user”) com direitos de administrador e apenas acesso programático e armazenou suas credenciais. Você também criou outro usuário IAM, chamado “terraform”, com as mesmas permissões:

Em seu nome, o Terraform irá para sua conta no AWS e criará e excluirá os recursos necessários. Os amplos direitos de ambos os usuários são explicados pelo fato de que se trata de uma demonstração. Você salva as credenciais localmente para os dois usuários IAM:

$ cat ~/.aws/credentials
[terraform]
aws_access_key_id = ABCDEFGHIJKLMNOPQRST
aws_secret_access_key = ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890123
[my-user]
aws_access_key_id = TSRQPONMLKJIHGFEDCBA
aws_secret_access_key = TSRQPONMLKJIHGFEDCBA01234567890123

Observação: não copie e cole as credenciais acima. Eles são fornecidos aqui como um exemplo e não existem mais. Edite o arquivo ~/.aws/credentials e introduza seus próprios registros.

Em segundo lugar, usaremos o ID do conta AWS fictícia (01234567890) para o artigo e a região AWS “eu-west-1.” Sinta-se à vontade para usar outra região.

Terceiro, presumimos que você esteja ciente de que o AWS não é gratuito e que você terá que pagar pelos recursos usados.

Em seguida, você instalou o utilitário AWS CLI para comunicação via linha de comando com o AWS. Você pode tentar usar o aws2, mas precisará definir especificamente o uso do aws2 em seu arquivo de configuração do kube, conforme descrito aqui.

Você também instalou o utilitário kubectl para comunicação via linha de comando com AWS Kubernetes.

E você instalou o utilitário kompose para docker-compose.yml para converter manifestos do Kubernetes.

Finalmente, você criou um repositório GitHub vazio e clonou-o em seu host. Vamos nos referir ao seu diretório raiz como . Neste repositório, vamos criar e preencher três diretórios: .github/workflows/, k8s/, e terraform/.

Observe que todo o código relevante é duplicado no repositório github-eks-samples-bi para simplificar a cópia e a colagem.

Vamos continuar.

Provisionamento AWS EKS

Já conhecemos o EKS no artigo Implementando uma aplicação web simples baseado em IRIS usando o Amazon EKS. Naquela época, criamos um cluster semiautomático. Ou seja, descrevemos o cluster em um arquivo e, em seguida, iniciamos manualmente o utilitário eksctl de uma máquina local, que criou o cluster de acordo com nossa descrição. 

O eksctl foi desenvolvido para a criação de clusters EKS e é bom para uma implementação de prova de conceito, mas para o uso diário é melhor usar algo mais universal, como o Terraform. Um ótimo recurso, Introdução ao AWS EKS, explica a configuração do Terraform necessária para criar um cluster EKS. Uma ou duas horas gastas para conhecê-lo não será uma perda de tempo.

Você pode brincar com o Terraform localmente. Para fazer isso, você precisará de um binário (usaremos a versão mais recente para Linux no momento da redação do artigo, 0.12.20) e o usuário IAM “terraform” com direitos suficientes para o Terraform ir para o AWS. Crie o diretório /terraform/ para armazenar o código do Terraform:

$ mkdir /terraform
$ cd /terraform

Você pode criar um ou mais arquivos .tf (eles são mesclados na inicialização). Basta copiar e colar os exemplos de código da Introdução ao AWS EKS e, em seguida, executar algo como:

$ export AWS_PROFILE=terraform
$ export AWS_REGION=eu-west-1
$ terraform init
$ terraform plan -out eks.plan

Você pode encontrar alguns erros. Nesse caso, brinque um pouco com o modo de depuração, mas lembre-se de desligá-lo mais tarde:

$ export TF_LOG=debug
$ terraform plan -out eks.plan

$ unset TF_LOG

Esta experiência será útil, e muito provavelmente você terá um cluster EKS iniciado (use “terraform apply” para isso). Verifique no console do AWS:

Limpe-o quando você ficar entediado:

$ terraform destroy

Em seguida, vá para o próximo nível e comece a usar o módulo Terraform EKS, especialmente porque ele é baseado na mesma introdução ao EKS. No diretório examples/ você verá como usá-lo. Você também  encontrará outros exemplos lá.

Simplificamos um pouco os exemplos. Este é o arquivo principal em que os módulos de criação de VPC e de criação de EKS são chamados:

$ cat /terraform/main.tf
terraform {
  required_version = ">= 0.12.0"
  backend "s3" {
    bucket         = "eks-github-actions-terraform"
    key            = "terraform-dev.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "eks-github-actions-terraform-lock"
  }
}

provider "kubernetes" {
  host                   = data.aws_eks_cluster.cluster.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
  token                  = data.aws_eks_cluster_auth.cluster.token
  load_config_file       = false
  version                = "1.10.0"
}

locals {
  vpc_name             = "dev-vpc"
  vpc_cidr             = "10.42.0.0/16"
  private_subnets      = ["10.42.1.0/24", "10.42.2.0/24"]
  public_subnets       = ["10.42.11.0/24", "10.42.12.0/24"]
  cluster_name         = "dev-cluster"
  cluster_version      = "1.14"
  worker_group_name    = "worker-group-1"
  instance_type        = "t2.medium"
  asg_desired_capacity = 1
}

data "aws_eks_cluster" "cluster" {
  name = module.eks.cluster_id
}

data "aws_eks_cluster_auth" "cluster" {
  name = module.eks.cluster_id
}

data "aws_availability_zones" "available" {
}

module "vpc" {
  source               = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc?ref=master"

  name                 = local.vpc_name
  cidr                 = local.vpc_cidr
  azs                  = data.aws_availability_zones.available.names
  private_subnets      = local.private_subnets
  public_subnets       = local.public_subnets
  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
  }

  public_subnet_tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
    "kubernetes.io/role/elb" = "1"
  }

  private_subnet_tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
    "kubernetes.io/role/internal-elb" = "1"
  }
}

module "eks" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-eks?ref=master"
  cluster_name     = local.cluster_name
  cluster_version  = local.cluster_version
  vpc_id           = module.vpc.vpc_id
  subnets          = module.vpc.private_subnets
  write_kubeconfig = false

  worker_groups = [
    {
      name                 = local.worker_group_name
      instance_type        = local.instance_type
      asg_desired_capacity = local.asg_desired_capacity
    }
  ]

  map_accounts = var.map_accounts
  map_roles    = var.map_roles
  map_users    = var.map_users
}

Vejamos um pouco mais de perto o bloco "terraform" em main.tf:

terraform {
  required_version = ">= 0.12.0"
  backend "s3" {
    bucket         = "eks-github-actions-terraform"
    key            = "terraform-dev.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "eks-github-actions-terraform-lock"
  }
}

Aqui, indicamos que seguiremos a sintaxe não inferior ao Terraform 0.12 (muito mudou em comparação com as versões anteriores) e também que Terraform não deve armazenar seu estado localmente, mas sim remotamente, no S3 bucket. 

É conveniente se o código do terraform possa ser atualizado de lugares diferentes por pessoas diferentes, o que significa que precisamos ser capazes de bloquear o estado de um usuário, então adicionamos um bloqueio usando uma tabela dynamodb. Leia mais sobre bloqueios na página State Locking (bloqueio de estado).

Como o nome do bucket deve ser único em toda o AWS, o nome “eks-github-actions-terraform” não funcionará para você. Pense por conta própria e certifique-se de que ele ainda não foi usado (se sim, você receberá um erro NoSuchBucket):

$ aws s3 ls s3://my-bucket
An error occurred (AllAccessDisabled) when calling the ListObjectsV2 operation: All access to this object has been disabled
$ aws s3 ls s3://my-bucket-with-name-that-impossible-to-remember
An error occurred (NoSuchBucket) when calling the ListObjectsV2 operation: The specified bucket does not exist

Tendo criado um nome, crie o bucket (usamos o usuário IAM “terraform” aqui. Ele tem direitos de administrador para que possa criar um bucket) e habilite o controle de versão para ele (o que lhe salvará em caso de um erro de configuração):

$ aws s3 mb s3://eks-github-actions-terraform --region eu-west-1
make_bucket: eks-github-actions-terraform
$ aws s3api put-bucket-versioning --bucket eks-github-actions-terraform --versioning-configuration Status=Enabled
$ aws s3api get-bucket-versioning --bucket eks-github-actions-terraform
{
  "Status": "Enabled"
}

Com o DynamoDB, ser único não é necessário, mas você precisa criar uma tabela primeiro:

$ aws dynamodb create-table                                                                                     \
  --region eu-west-1                                                                                                           \
  --table-name eks-github-actions-terraform-lock                                              \
  --attribute-definitions AttributeName=LockID,AttributeType=S                \
  --key-schema AttributeName=LockID,KeyType=HASH                                   \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

Lembre-se de que, em caso de falha do Terraform, você pode precisar remover um bloqueio manualmente do console AWS. Mas tenha cuidado ao fazer isso.

Com relação aos blocos do módulo eks/vpc em main.tf, a forma de referenciar o módulo disponível no GitHub é simples:

git::https://github.com/terraform-aws-modules/terraform-aws-vpc?ref=master

Agora vamos dar uma olhada em nossos outros dois arquivos Terraform (variables.tf e outputs.tf). O primeiro contém nossas variáveis Terraform:

$ cat /terraform/variables.tf
variable "region" {
  default = "eu-west-1"
}

variable "map_accounts" {
  description = "Additional AWS account numbers to add to the aws-auth configmap. See examples/basic/variables.tf for example format."
  type        = list(string)
  default     = []
}

variable "map_roles" {
  description = "Additional IAM roles to add to the aws-auth configmap."
  type = list(object({
    rolearn  = string
    username = string
    groups   = list(string)
  }))
  default = []
}

variable "map_users" {
  description = "Additional IAM users to add to the aws-auth configmap."
  type = list(object({
    userarn  = string
    username = string
    groups   = list(string)
  }))
  default = [
    {
      userarn  = "arn:aws:iam::01234567890:user/my-user"
      username = "my-user"
      groups   = ["system:masters"]
    }
  ]
}

A parte mais importante aqui é adicionar o usuário IAM “my-user” à variável map_users, mas você deve usar seu próprio ID de conta aqui no lugar de 01234567890.

O que isto faz? Quando você se comunica com o EKS por meio do cliente kubectl local, ele envia solicitações ao servidor da API Kubernetes, e cada solicitação passa por processos de autenticação e autorização para que o Kubernetes possa entender quem enviou a solicitação e o que elas podem fazer. Portanto, a versão EKS do Kubernetes pede ajuda ao AWS IAM com a autenticação do usuário. Se o usuário que enviou a solicitação estiver listado no AWS IAM (apontamos seu ARN aqui), a solicitação vai para a fase de autorização, que o EKS processa sozinho, mas de acordo com nossas configurações. Aqui, indicamos que o usuário IAM “my-user” é muito legal (grupo “system: masters”).

Por fim, o arquivo outputs.tf descreve o que o Terraform deve imprimir após concluir um job:

$ cat /terraform/outputs.tf
output "cluster_endpoint" {
  description = "Endpoint for EKS control plane."
  value       = module.eks.cluster_endpoint
}

output "cluster_security_group_id" {
  description = "Security group ids attached to the cluster control plane."
  value       = module.eks.cluster_security_group_id
}

output "config_map_aws_auth" {
  description = "A kubernetes configuration to authenticate to this EKS cluster."
  value       = module.eks.config_map_aws_auth
}

Isso completa a descrição da parte do Terraform. Voltaremos em breve para ver como vamos iniciar esses arquivos.

Manifestos Kubernetes

Até agora, cuidamos de onde iniciar a aplicação. Agora vamos ver o que executar. 

Lembre-se de que temos o docker-compose.yml (renomeamos o serviço e adicionamos alguns rótulos que o kompose usará em breve) no diretório /k8s/:

$ cat /k8s/docker-compose.yml
version: "3.7"
services:
  samples-bi:
    container_name: samples-bi
    image: intersystemsdc/iris-community:2019.4.0.383.0-zpm
    ports:
    - 52773:52773
    labels:
      kompose.service.type: loadbalancer
      kompose.image-pull-policy: IfNotPresent

Execute o kompose e adicione o que está destacado abaixo. Exclua as anotações (para tornar as coisas mais inteligíveis):

$ kompose convert -f docker-compose.yml --replicas=1
$ cat /k8s/samples-bi-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    io.kompose.service: samples-bi
  name: samples-bi
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        io.kompose.service: samples-bi
    spec:
      containers:
      - image: intersystemsdc/iris-community:2019.4.0.383.0-zpm
        imagePullPolicy: IfNotPresent
        name: samples-bi
        ports:
        - containerPort: 52773
        resources: {}
        lifecycle:
          postStart:
            exec:
              command:
              - /bin/bash
              - -c
              - |
                echo -e "write\nhalt" > test
                until iris session iris < test; do sleep 1; done
                echo -e "zpm\ninstall samples-bi\nquit\nhalt" > samples_bi_install
                iris session iris < samples_bi_install
                rm test samples_bi_install

        restartPolicy: Always

Usamos a estratégia de atualização de Recriar, o que significa que o pod será excluído primeiro e depois recriado. Isso é permitido para fins de demonstração e nos permite usar menos recursos.
Também adicionamos o postStart hook, que será disparado imediatamente após o início do pod. Esperamos até o IRIS iniciar e instalar o pacote samples-bi do repositório zpm padrão.
Agora adicionamos o serviço Kubernetes (também sem anotações):

$ cat /k8s/samples-bi-service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    io.kompose.service: samples-bi
  name: samples-bi
spec:
  ports:
  - name: "52773"
    port: 52773
    targetPort: 52773
  selector:
    io.kompose.service: samples-bi
  type: LoadBalancer

Sim, vamos implantar no namespace "padrão", que funcionará para a demonstração.

Ok, agora sabemos onde e o que queremos executar. Resta ver como.

O fluxo de trabalho do GitHub Actions

Em vez de fazer tudo do zero, criaremos um fluxo de trabalho semelhante ao descrito em Implantando uma solução InterSystems IRIS no GKE usando GitHub Actions. Desta vez, não precisamos nos preocupar em construir um contêiner. As partes específicas do GKE são substituídas pelas específicas do EKS. As partes em negrito estão relacionadas ao recebimento da mensagem de commit e ao uso dela em etapas condicionais:

$ cat /.github/workflows/workflow.yaml
name: Provision EKS cluster and deploy Samples BI there
on:
  push:
    branches:
    - master

# Environment variables.
# ${{ secrets }} are taken from GitHub -> Settings -> Secrets
# ${{ github.sha }} is the commit hash
env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  AWS_REGION: ${{ secrets.AWS_REGION }}
  CLUSTER_NAME: dev-cluster
  DEPLOYMENT_NAME: samples-bi

jobs:
  eks-provisioner:
    # Inspired by:
    ## https://www.terraform.io/docs/github-actions/getting-started.html
    ## https://github.com/hashicorp/terraform-github-actions
    name: Provision EKS cluster
    runs-on: ubuntu-18.04
    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Get commit message
      run: |
        echo ::set-env name=commit_msg::$(git log --format=%B -n 1 ${{ github.event.after }})

    - name: Show commit message
      run: echo $commit_msg

    - name: Terraform init
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: 0.12.20
        tf_actions_subcommand: 'init'
        tf_actions_working_dir: 'terraform'

    - name: Terraform validate
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: 0.12.20
        tf_actions_subcommand: 'validate'
        tf_actions_working_dir: 'terraform'

    - name: Terraform plan
      if: "!contains(env.commit_msg, '[destroy eks]')"
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: 0.12.20
        tf_actions_subcommand: 'plan'
        tf_actions_working_dir: 'terraform'

    - name: Terraform plan for destroy
      if: "contains(env.commit_msg, '[destroy eks]')"
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: 0.12.20
        tf_actions_subcommand: 'plan'
        args: '-destroy -out=./destroy-plan'
        tf_actions_working_dir: 'terraform'

    - name: Terraform apply
      if: "!contains(env.commit_msg, '[destroy eks]')"
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: 0.12.20
        tf_actions_subcommand: 'apply'
        tf_actions_working_dir: 'terraform'

    - name: Terraform apply for destroy
      if: "contains(env.commit_msg, '[destroy eks]')"
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: 0.12.20
        tf_actions_subcommand: 'apply'
        args: './destroy-plan'
        tf_actions_working_dir: 'terraform'

  kubernetes-deploy:
    name: Deploy Kubernetes manifests to EKS
    needs:
    - eks-provisioner
    runs-on: ubuntu-18.04
    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Get commit message
      run: |
        echo ::set-env name=commit_msg::$(git log --format=%B -n 1 ${{ github.event.after }})

    - name: Show commit message
      run: echo $commit_msg

    - name: Configure AWS Credentials
      if: "!contains(env.commit_msg, '[destroy eks]')"
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ secrets.AWS_REGION }}

    - name: Apply Kubernetes manifests
      if: "!contains(env.commit_msg, '[destroy eks]')"
      working-directory: ./k8s/
      run: |
        aws eks update-kubeconfig --name ${CLUSTER_NAME}
        kubectl apply -f samples-bi-service.yaml
        kubectl apply -f samples-bi-deployment.yaml
        kubectl rollout status deployment/${DEPLOYMENT_NAME}

É claro que, precisamos definir as credenciais do usuário "terraform" (retirá-las do arquivo ~/.aws/credentials), permitindo que o GitHub use seus segredos:

Observe as partes destacadas do fluxo de trabalho. Elas nos permitirão destruir um cluster EKS empurrando uma mensagem de commit que contém a frase “[destroy eks]”. Observe que não executaremos "kubernetes apply" com essa mensagem de commit.
Execute um pipeline, mas primeiro crie um arquivo .gitignore:

$ cat /.gitignore
.DS_Store
terraform/.terraform/
terraform/*.plan
terraform/*.json

$ cd
$ git add .github/ k8s/ terraform/ .gitignore
$ git commit -m "GitHub on EKS"
$ git push

Monitore o processo de implantação na aba "Actions" da página do repositório GitHub. Aguarde a conclusão com sucesso.

Quando você executa um fluxo de trabalho pela primeira vez, leva cerca de 15 minutos na etapa “Terraform apply”, aproximadamente o mesmo tempo necessário para criar o cluster. Na próxima inicialização (se você não excluiu o cluster), o fluxo de trabalho será muito mais rápido. Você pode verificar isso:

$ cd
$ git commit -m "Trigger" --allow-empty
$ git push

É claro que seria bom verificar o que fizemos. Desta vez, você pode usar as credenciais do IAM “my-user” em seu computador:

$ export AWS_PROFILE=my-user
$ export AWS_REGION=eu-west-1
$ aws sts get-caller-identity
$ aws eks update-kubeconfig --region=eu-west-1 --name=dev-cluster --alias=dev-cluster
$ kubectl config current-context
dev-cluster

$ kubectl get nodes
NAME                                                                               STATUS   ROLES      AGE          VERSION
ip-10-42-1-125.eu-west-1.compute.internal   Ready          6m20s     v1.14.8-eks-b8860f

$ kubectl get po
NAME                                                       READY        STATUS      RESTARTS   AGE
samples-bi-756dddffdb-zd9nw    1/1               Running    0                      6m16s

$ kubectl get svc
NAME                   TYPE                        CLUSTER-IP        EXTERNAL-IP                                                                                                                                                         PORT(S)                    AGE
kubernetes        ClusterIP               172.20.0.1                                                                                                                                                                                443/TCP                    11m
samples-bi         LoadBalancer     172.20.33.235    a2c6f6733557511eab3c302618b2fae2-622862917.eu-west-1.elb.amazonaws.com    52773:31047/TCP  6m33s

Vá para _http://a2c6f6733557511eab3c302618b2fae2-622862917.eu-west-1.elb.amazonaws.com:52773/csp/user/_DeepSee.UserPortal.Home.zen?$NAMESPACE=USER _(substitua o link pelo seu IP externo), então, digite “_system”, “SYS” e altere a senha padrão. Você deve ver vários painéis de Inteligência Empresarial (BI):

Clique na seta de cada um para um mergulho mais profundo:

Lembre-se, se você reiniciar um pod samples-bi, todas as suas alterações serão perdidas. Este é um comportamento intencional, pois esta é uma demonstração. Se você precisar de persistência, criei um exemplo no repositório github-gke-zpm-registry/k8s/statefulset.tpl.

Quando terminar, basta remover tudo que você criou:

$ git commit -m "Mr Proper [destroy eks]" --allow-empty
$ git push

Conclusão

Neste artigo, substituímos o utilitário eksctl pelo Terraform para criar um cluster EKS. É um passo à frente para “codificar” toda a sua infraestrutura AWS.
Mostramos como você pode facilmente implantar uma aplicação de demonstração com git push usando GitHub Actions e Terraform.
Também adicionamos o kompose e um postStart hook de um pod à nossa caixa de ferramentas.
Não mostramos a ativação do TLS neste momento. Essa é uma tarefa que realizaremos em um futuro próximo.

0
0 301