Não foi possível carregar o Diqus. Se você é o moderador, por favor veja o nosso guia de problemas.

Marco • 6 anos atrás

I have published another tutorial about deploying a Rails app on Kubernetes. The topics and services used are slightly different, so check it out:
http://kubernetes-rails.com
Hope it helps!

Ilya • 7 anos atrás

Thanks for the article! However, I'm confused by these points:

1) How did you bind "type: LoadBalancer" Service to a global static IP address? Google Cloud Docs (and every other source I've seen, including own experience) claim you can only use a **regional** static IP address for that.

2) "kubernetes.io/ingress.globa... ip-web-production" annotation is not supported by nginx-ingress, only by gce-ingress

3) What's the reasoning behind using "type: NodePort" for web-svc and not "type: ClusterIP"?

Paulo • 7 anos atrás

Great article! I am very curious about how you are dealing with CPU requests and limits. From what I have observed in my deployments rails/PUMA applications tend to be limited by CPU requests and never actually go up to the limit. Is that always the case?

AkitaOnRails • 7 anos atrás

I probably added more cpu resources to my instances than my app currently needs so I am still far from testing upper limits. I will report as soon as I have more stress on the system. But so far it's very adequate.

Paulo • 7 anos atrás

Interesting! Our issue is that rails apps seem to be limited by the request rather than the limit. Therefore, we end up having to allocate higher requests than we really need for out applications which ends up limiting the number of pods we can run. I would prefer to be able to give very low requests and let them burst up to the limit. Which seems to me, should be how it is meant to work.

moos3 • 7 anos atrás

How are you handling logging ? I'm trying to solve how to get my application to send the env.log file entries to the console or to stack driver. Have you solved this yet?

AkitaOnRails • 7 anos atrás

I have been trying the stackdriver gem for Rails apps, but it seems like it's poorly implemented. It adds itself in as a middleware and sometimes it hangs for no apparent reason. And when it does that the entire middleware pipeline stops responding. Puma is online but unresponsive. I have to restart the pods when it does that.

The Google Cloud Log section of the console is stupid terrible to use and filters stuff out.

I will probably add a separated node just for fluentd and route the logs to that instead. Didn't have time to do it yet though.

Jon • 7 anos atrás

Great post! Check out https://github.com/jonmosco... as well. Another prompt helper that works for both zsh and bash.

Dan Benjamin • 7 anos atrás

I am wondering how we both have Rails implementations on Kubernetes and our solutions look so different :)

Anyhow, great read!

I would have loved to see how you implemented your HPA policies especially when running sidekiq servers (and not your web servers which can scale based on CPU easily).

If you are interested, would love to chat (dan@erated.co)

Rodrigo Rosenfeld Rosas • 7 anos atrás

Eu esqueci de mencionar que raramente uma migration exige downtime. Pelo menos, eu não lembro a última vez em que uma migration precisou de uma janela de downtime no meu projeto. Eu sempre rodo as migrations antes de rodar a aplicação no entry point dos containers da aplicação principal.

Rodrigo Rosenfeld Rosas • 7 anos atrás

Uau, acho que é por isso que toda vez que eu começo a estudar um pouco de Kubernetes e tecnologias relacionadas eu acabo desistindo. Eu também implanto com Docker, não apenas as aplicações mas praticamente todos os serviços, incluindo PostgreSQL, Redis, Nginx, MongoDB, Solr/ElasticSearch, sidekiq, etc, só que eu mesmo gerencio os serviços, encapsulando-os em unidades de serviço systemd. E também hospedo no GCP faz bastante tempo, embora mantenha os serviços SES e CDN da Amazon (não achei alternativas interessantes da Google). Meu setup está automatizado, em uma infra-estrutura própria em Ruby usando capistrano/ssh-kit, e suporta blue-green deployment para downtime zero. Só que essa aplicação que mantenho não precisa ser realmente escalável, então eu possuo apenas um cluster Puma para cada configuração (blue ou green) e registrei um domínio para cada configuração, de modo que eu possa testá-las independentementes antes de virar a chave global da aplicação para apontar para a próxima configuração (blue, se a atual for green, por exemplo).

Claramente é um setup muito mais modesto que o seu, já que nós não suportamos ainda alta disponibilidade, ou múltiplas regiões (exceto para o CDN), ou replicação dos bancos de dados, auto-snapshots (embora tenhamos nosso próprio procedimento de backup, tanto in-site quanto off-site), entre outras configurações avançadas comentadas nesse artigo. Mas pelo menos eu tenho uma ideia clara de tudo que está acontecendo, caso alguma coisa dê errada (até hoje ainda não deu, felizmente :) ). Eu mesmo preciso cuidar dos upgrades de releases do PostgreSQL e demais bancos de dados por exemplo, enquanto eu certamente preferiria usar uma solução gerenciadas, mas por outro lado eu estou no controle. Eu pude migrar para o PG 10 na mesma semana em que ele foi lançado e com um downtime de apenas poucos segundos com a ajuda do pglogical. Esse tipo de coisa é possível justamente porque eu me sinto no controle total da situação com esse tipo de setup.

Além disso, é importante pra mim ser capaz de realizar um novo deploy com um fix o quanto antes. Se eu usasse um Dockerfile pra gerar os containers eu teria muito menos flexibilidade no processo. Com um Dockerfile você não consegue se beneficiar de certas técnicas para acelerar o build, como evitar de rodar o Bundler se o Gemfile.lock e a versão do Ruby não mudaram, por exemplo. Ou não re-gerar os assets se as últimas mudanças ocorreram somente no código Ruby. Ou aproveitar caches permanentes na geração dos assets. Daí fica muito custoso implantar um bug fix, podendo levar vários minutos provavelmente.

Eu uso um container de build que fica sempre rodando. Mais ou menos assim: "docker run -d -n myapp-production-build -v $(pwd)/build-utils:/build-utils myapp-base-image sleep infinity". E depois executo as etapas do build mais ou menos assim: "docker exec -t myapp-base-image /build-utils/prepare-app". Em vez de variáveis de ambiente, eu configuro toda a aplicação através de um único arquivo Ruby que é gerado pelo sistema de deployment, e que só existe no repositório do projeto de implantação, o qual não está localizado em lugar algum no servidor no qual a aplicação é implantada. O sistema de deployment é que contém todos os segredos e configurações. Daí você tem a opção de armazenar os segredos diretamente na imagem ou através de um volume montado. De qualquer forma, os segredos serão sempre visíveis para alguém com acesso ao host Docker, independentemente de armazenar na imagem, passar em um volume ou via ENV. O mesmo vale se alguém for capaz de executar código remotamente na aplicação.

Para os logs, eu sugeriria usar uma solução tipo Fluentd ou qualquer outro sistema distribuído, em vez de simplesmente direcionar o log do Rails para o STDOUT. Você consegue fazer milagres ao buscar os arquivos de log depois ao combinar o Fluentd (ou Logstash, etc) ao ElasticSearch e Kibana, por exemplo. Eu sempre armazeno nos logs uma abreviação do id da requisição, do id da sessão e do usuário ativo para ajudar nas inspeções. Você pode acompanhar todos os logs relacionados a um id de requisição ou de sessão por exemplo, ao tentar entender algum comportamento atípico.

Infelizmente, implantar uma aplicação production-ready é ainda super complicado até os dias de hoje. Ferramentas como Heroku ajudam, mas ainda não são uma solução viável pra mim porque Heroku ainda não suporta blue-green deployment ou outra estratégia com zero downtime, que eu saiba. Ainda assim, eu precisaria ser capaz de testar a configuração blue independentemente após uma implantação antes de chavear a aplicação de green pra blue, por exemplo. Não consegui descobrir como fazer esse tipo de coisa com Heroku.

Fora a questão de custos, que são especialmente importantes durante a fase inicial de uma start-up.

Faz pelo menos um mês que tenho investido um tempo significativo em tentar minimizar o tempo que eu precisaria levar para começar um novo projeto do zero em nível de produção. E a parte de deployment é apenas uma pequena fração do esforço, embora seja bastante trabalhosa. Minha ideia é lançar um repositório com um esqueleto no futuro, quando eu estiver mais confortável com o formato que ficar. Ainda falta muita coisa, já que a aplicação em si também entra no esqueleto, incluindo Webpack com as melhores práticas, rotas client-side, logging apropriado, segurança apropriada, etc. É muita coisa.

Fico feliz que você tenha conseguido se entender com o Kubernetes e essa sopa de configurações :) Boa sorte na jornada :)

Uma curiosidade: um setup como esse que você descreveu custa quanto mensalmente para manter a aplicação?

AkitaOnRails • 7 anos atrás

Opa, hehe, bom report. Você devia fazer um post do seu deploy.

Duas coisas: o STDOUT é porque o Google pega os logs dos pods e guarda no StackDriver então eu posso pesquisar de lá. Basta direcionar pro STDOUT que ele se vira.

O custo total dessa minha versão inicial tá pra casa dos USD 2.000 (não listei tudo nesse post, incluindo serviços externos).

Também não gostei das soluções de DNS e CDN do Google, achei muito pobre. Eu teria ficado no AWS mas agora que testei CloudFlare nunca mais quero sair de lá. Sério, pelo menos fuça ele. Não é barato, mas o que ele oferece é muito bom.

Rodrigo Rosenfeld Rosas • 7 anos atrás

Em relação à parte do upgrade do PostgreSQL com pglogical eu cheguei a escrever um artigo sobre isso e ele chegou a ser mencionado duas vezes na Postgre Weekly, já que a última edição do ano passado não teve artigos novos, e em vez disso os melhores artigos do ano foram selecionados e o meu foi um deles :) Por falar em artigos, meu último artigo foi mencionado hoje pela Ruby Weekly, sobre o sequel_tools. Este seu artigo inclusive também foi mencionado :)

Rodrigo Rosenfeld Rosas • 7 anos atrás

Sim, minha ideia é em algum momento reunir essas informações todas, não apenas referentes ao deploy em si, mas à própria aplicação, mencionando a infra-estrutura de logging, envio de e-mails, jobs, desempenho (especialmente client-side), organização do código front-end, configuração do webpack, monitoramento do desempenho da aplicação (server-side e front-end), relatório/submissão de erros (server-side e front-end), monitoramento e alertas do funcionamento de partes críticas da aplicação e serviços, backup, autenticação, autorização, sobre tratamento de rotas no lado do cliente de modo apropriado, sobre a forma de escrever código client-side, enfim, o tópico é muito extenso e o tempo muito curto no momento. Eu estou trabalhando intensivamente nesses tópicos ultimamente já que provavelmente eu devo iniciar o desenvolvimento de alguma aplicação nova, já que o cliente maior da nossa aplicação atual nos deixou mês passado. Algumas coisas que aprendi nos últimos anos eu pretendo levar para esse projeto, e aperfeiçoar em outras. A ideia é minimizar o tempo necessário para desenvolver uma aplicação production-ready, digamos assim.

Atualmente nossa aplicação atual conta com 7 VMs no GCP e se não me engano a conta fica na casa dos USD 600 mensais, sendo que mais de 200 vão só para capacidade de armazenamento SSD se não me engano.

Em relação ao CloudFlare, na época em que avaliei, o problema em si nem era o custo, mas o fato de eu ter que alterar os registros DNS pelo que entendi para apontar para os da CloudFlare. Isso pode ser simples quando você está iniciando um novo projeto, mas pode não ser tão simples em um projeto existente, quando você não quer correr o risco de quebrar nada, que era o meu caso. Com o CloudFront eu não preciso mexer nos registros DNS, e isso me dava mais segurança. Nos projetos novos é certamente algo a se considerar. Como o CloudFront usa um domínio diferente, automaticamente os cookies da aplicação não são enviados para o CDN. Com o CloudFlare eu imagino que teria que criar meu próprio subdomínio, tipo static-production.mydomain.com para ter um efeito parecido. Por um lado, é possível economizar um pouco na largura de banda e melhorar a latência ao não enviar os cookies para o CDN, mas por outro lado, não se leva muito vantagem da multiplexação HTTP2 ao usar um domínio diferente. Eu acredito que uma solução CloudFlare provavelmente traria um desempenho muito melhor, ao fazer uso da multiplexação, apesar dos bytes extras enviados com o cookie. Uma forma de ter o melhor dos dois mundos seria se a aplicação fosse montada em um path, tipo "/app/" de modo que o cookie poderia ser gerado para esse caminho apenas, não sendo enviado para "/static" ou "/assets", por exemplo. Mas não tenho certeza se essa é uma boa estratégia, de qualquer forma. Mas certamente, no novo projeto, eu pretendo testar essa abordagem com o CloudFlare.

Em relação ao StackDriver, eu entendo como ele funciona, que aliás, não é muito diferente da maioria das soluções de logging distribuído, que permitem converter diversos tipos de log estruturados de modo a extrair diversos tipos de informação. O próprio Fluentd e LogStash permitem isso se não me engano. Mas isso é diferente da aplicação abraçar sistemas assim. Sem a limitação de ter que usar algo como "logger.info 'text message'", seria possível fazer logging de objetos, usando notação de hash, que seria convertida para JSON, e seriam indexadas dessa forma. Digamos que você faça um logging dos params, com filtros aplicados para remover informações sensíveis. Ao depurar o que aconteceu em um certo horário ou transação, seria possível pesquisar por exemplo por request.params.transaction_id ou algo assim, devido a forma como esses objetos são normalmente indexados no ElasticSearch, por exemplo, caso se integre o ElasticSearch ao Fluentd. Daí com o Kibana você tem um poder fantástico para filtrar as requisições. Com muito mais poder do que teria com a conversão de texto usada pelo StackDriver e serviços similares. Vale a pena pensar bem sobre isso, especialmente em um projeto green field.