<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cloud | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/cloud/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/cloud/index.xml" rel="self" type="application/rss+xml"/><item><title>Servindo IA na Nuvem: Meu TTS pessoal | Bastidores do The M.Akita Chronicles</title><link>https://akitaonrails.github.io/2026/02/18/servindo-ia-na-nuvem-meu-tts-pessoal-bastidores-do-the-m-akita-chronicles/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/18/servindo-ia-na-nuvem-meu-tts-pessoal-bastidores-do-the-m-akita-chronicles/</guid><pubDate>Wed, 18 Feb 2026 15:28:46 +0000</pubDate><description>&lt;p&gt;Este post vai fazer parte de uma série; acompanhe pela tag &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;/themakitachronicles&lt;/a&gt;. Esta é a parte 3.&lt;/p&gt;
&lt;p&gt;E não deixe de assinar minha nova newsletter &lt;a href="https://themakitachronicles.com/"target="_blank" rel="noopener"&gt;The M.Akita Chronicles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Já vou falar do podcast hoje; lembrem-se de que o &lt;a href="https://blog.themakitachronicles.com/podcast-transcripts/"target="_blank" rel="noopener"&gt;episódio piloto já está no ar&lt;/a&gt; (transcripts e RSS pra assinar) e no canal no Spotify.&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Todo tutorial de IA começa com &lt;em&gt;&amp;ldquo;instale o modelo, rode inference, pronto!&amp;rdquo;&lt;/em&gt;. Nenhum te conta o que acontece quando você precisa rodar isso em produção com uptime real, custo controlado, e qualidade consistente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post vai fazer parte de uma série; acompanhe pela tag <a href="/tags/the-m-akita-chronicles/">/themakitachronicles</a>. Esta é a parte 3.</p>
<p>E não deixe de assinar minha nova newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>!</p>
<p>Já vou falar do podcast hoje; lembrem-se de que o <a href="https://blog.themakitachronicles.com/podcast-transcripts/"target="_blank" rel="noopener">episódio piloto já está no ar</a> (transcripts e RSS pra assinar) e no canal no Spotify.</p>
<p>&ndash;</p>
<p>Todo tutorial de IA começa com <em>&ldquo;instale o modelo, rode inference, pronto!&rdquo;</em>. Nenhum te conta o que acontece quando você precisa rodar isso em produção com uptime real, custo controlado, e qualidade consistente.</p>
<p>Vou contar como foi colocar um modelo de TTS (Text-to-Speech) em produção numa GPU na nuvem. Porque se você está pensando em servir modelos de IA, precisa saber no que está se metendo.</p>
<h2>O Cenário<span class="hx:absolute hx:-mt-20" id="o-cenário"></span>
    <a href="#o-cen%c3%a1rio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><blockquote>
  <p>O objetivo: gerar um podcast de ~30 minutos a partir de texto, com duas vozes distintas, qualidade de broadcast, pronto pra Spotify. Toda semana, automaticamente, sem intervenção humana.</p>

</blockquote>
<p>O modelo escolhido: <a href="https://qwen.ai/blog?id=qwen3tts-0115"target="_blank" rel="noopener"><strong>Qwen3-TTS</strong></a>, um modelo de text-to-speech que suporta voice cloning e fine-tuning. Roda em GPU — precisa de pelo menos 6GB de VRAM pra inference.</p>
<p>A plataforma: <a href="https://runpod.io/"target="_blank" rel="noopener"><strong>RunPod</strong></a>, que oferece GPUs serverless com cobrança por segundo de uso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-18_12-33-45.jpg" alt="runpod"  loading="lazy" /></p>
<p>Parece simples, né? Pois aguarde.</p>
<h2>Problema 1: Cold Start — O Assassino Silencioso<span class="hx:absolute hx:-mt-20" id="problema-1-cold-start--o-assassino-silencioso"></span>
    <a href="#problema-1-cold-start--o-assassino-silencioso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>GPU serverless funciona assim: quando não tem requisição, o worker desliga. Quando chega uma requisição, ele liga, carrega o modelo na VRAM, e processa.</p>
<p>O tempo de &ldquo;carregar o modelo na VRAM&rdquo; é o <em>cold start</em>. Pra um modelo de ~4GB, estamos falando de <strong>30 a 90 segundos</strong>. Sua requisição HTTP fica pendurada, esperando. Se seu timeout é de 30 segundos, você nem chega a ver o modelo funcionar — só vê timeout.</p>
<p>A solução ingênua: <em>&ldquo;aumenta o timeout!&rdquo;</em>. A solução real: <strong>health check com polling</strong>.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/health&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">health</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">model_loaded</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">JSONResponse</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">status_code</span><span class="o">=</span><span class="mi">503</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;loading&#34;</span><span class="p">,</span> <span class="s2">&#34;detail&#34;</span><span class="p">:</span> <span class="s2">&#34;Model loading...&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;ready&#34;</span><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O cliente (no caso, um job Rails) faz polling no <code>/health</code> antes de mandar a requisição real:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">wait_for_server</span><span class="p">(</span><span class="ss">max_wait</span><span class="p">:</span> <span class="mi">180</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">deadline</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">current</span> <span class="o">+</span> <span class="n">max_wait</span>
</span></span><span class="line"><span class="cl">  <span class="kp">loop</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">http_get</span><span class="p">(</span><span class="s2">&#34;/health&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">code</span> <span class="o">==</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="s2">&#34;Server não iniciou a tempo&#34;</span> <span class="k">if</span> <span class="no">Time</span><span class="o">.</span><span class="n">current</span> <span class="o">&gt;</span> <span class="n">deadline</span>
</span></span><span class="line"><span class="cl">    <span class="nb">sleep</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Parece primitivo? É. Mas funciona melhor do que qualquer solução sofisticada de &ldquo;warm pool&rdquo; que custa 10x mais.</p>
<h2>Problema 2: Onde Ficam os Pesos do Modelo?<span class="hx:absolute hx:-mt-20" id="problema-2-onde-ficam-os-pesos-do-modelo"></span>
    <a href="#problema-2-onde-ficam-os-pesos-do-modelo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um modelo de 4GB precisa estar disponível quando o worker inicia. Três opções:</p>
<ol>
<li><strong>Bake no Docker</strong>: Imagem de 4GB+. Build lento. Push lento. Pull lento.</li>
<li><strong>Download do HuggingFace no boot</strong>: Depende de rede. Pode falhar. Adiciona minutos ao cold start.</li>
<li><strong>Network Volume</strong>: Disco persistente montado no worker.</li>
</ol>
<p>A opção 3 é a correta. O RunPod oferece Network Volumes que persistem entre execuções. O modelo fica lá, sempre pronto. O cold start é só o tempo de carregar da disk pra VRAM, sem download.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-18_12-35-43.jpg" alt="network storage"  loading="lazy" /></p>
<p>Mas tem uma pegadinha: o volume pode estar montado ou não, dependendo de como o worker é configurado. Então o código de setup precisa ser resiliente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">NETWORK_VOLUME</span> <span class="o">=</span> <span class="s2">&#34;/runpod-volume&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">LOCAL_FALLBACK</span> <span class="o">=</span> <span class="s2">&#34;/workspace/models&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">resolve_model_path</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">NETWORK_VOLUME</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">NETWORK_VOLUME</span><span class="p">,</span> <span class="s2">&#34;huggingface&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;HF_HOME&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">base</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s2">&#34;hub&#34;</span><span class="p">,</span> <span class="s2">&#34;models--Qwen--Qwen3-TTS&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Fallback: download local</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">LOCAL_FALLBACK</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Auto-detecção. Se o volume existe, usa. Se não, funciona do mesmo jeito (mais lento no primeiro boot). <strong>Nunca assuma que a infraestrutura está perfeita.</strong></p>
<h2>Problema 3: Dois Modos de Voz, Dois Conjuntos de Parâmetros<span class="hx:absolute hx:-mt-20" id="problema-3-dois-modos-de-voz-dois-conjuntos-de-parâmetros"></span>
    <a href="#problema-3-dois-modos-de-voz-dois-conjuntos-de-par%c3%a2metros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui está algo que nenhuma documentação vai te dizer: modelos de TTS com voice cloning e fine-tuning precisam de <strong>parâmetros de sampling completamente diferentes</strong> dependendo do modo.</p>
<p>Com uma voz fine-tuned (treinada especificamente no modelo), você quer parâmetros conservadores:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>temperature: 0.5
top_p: 0.8
repetition_penalty: 1.1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com voice cloning (voz base + áudio de referência), os mesmos parâmetros produzem <strong>ruído branco</strong>. Literalmente. White noise. Porque o modelo de clone precisa de mais &ldquo;criatividade&rdquo; pra reconstruir as características vocais:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>temperature: 0.9
top_p: 0.95
repetition_penalty: 1.05</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Descobri isso depois de horas gerando áudio inutilizável. A documentação do modelo não menciona. Os papers acadêmicos não mencionam. É o tipo de conhecimento que só vem de <strong>experimentação em produção</strong>.</p>
<h2>Problema 4: O Áudio de Referência Importa Mais que o Modelo<span class="hx:absolute hx:-mt-20" id="problema-4-o-áudio-de-referência-importa-mais-que-o-modelo"></span>
    <a href="#problema-4-o-%c3%a1udio-de-refer%c3%aancia-importa-mais-que-o-modelo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Voice cloning usa um trecho de áudio como referência pra replicar a voz. Parece simples: grava 10 segundos, passa pro modelo e pronto.</p>
<p>Exceto que se o áudio de referência começa com a fala imediatamente — sem nem 200 milissegundos de silêncio no início — o modelo aprende que &ldquo;o áudio começa sem onset&rdquo;. E <strong>toda geração subsequente</strong> vai cortar a primeira sílaba.</p>
<p>Isso não é um bug do modelo. É o modelo fazendo exatamente o que você pediu: <em>&ldquo;gere áudio que começa igual a essa referência&rdquo;</em>. Se a referência começa cortada, a geração começa cortada.</p>
<p>A correção não é pós-processamento (o áudio já foi gerado sem a sílaba). É <strong>corrigir a referência</strong>: garantir pelo menos 300ms de silêncio antes da fala.</p>
<p>Pra quem vem do mundo de software, isso é contra-intuitivo. Estamos acostumados a tratar dados de entrada como imutáveis e consertar na saída. Com modelos generativos, a qualidade da entrada é <strong>tudo</strong>.</p>
<h2>Problema 5: Timeout e Limites de Geração<span class="hx:absolute hx:-mt-20" id="problema-5-timeout-e-limites-de-geração"></span>
    <a href="#problema-5-timeout-e-limites-de-gera%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um modelo de TTS gera áudio token por token. Se você não limitar, ele pode gerar indefinidamente — e no RunPod, você paga por segundo de GPU. Um turno de diálogo de 30 palavras não deveria gerar 3 minutos de áudio, mas sem limite, pode.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">MAX_NEW_TOKENS</span> <span class="o">=</span> <span class="mi">720</span>  <span class="c1"># ~60 segundos de áudio a 12Hz</span>
</span></span><span class="line"><span class="cl"><span class="n">GENERATION_TIMEOUT</span> <span class="o">=</span> <span class="mi">180</span>  <span class="c1"># 3 minutos absolutos</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">,</span> <span class="o">**</span><span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">GENERATION_TIMEOUT</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dupla proteção: limite de tokens na geração e timeout absoluto no processo. Se qualquer um disparar, o turno falha e pode ser re-tentado com parâmetros ajustados.</p>
<h2>Problema 6: Pós-Processamento é Metade do Trabalho<span class="hx:absolute hx:-mt-20" id="problema-6-pós-processamento-é-metade-do-trabalho"></span>
    <a href="#problema-6-p%c3%b3s-processamento-%c3%a9-metade-do-trabalho" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O modelo gera WAV cru. Pra chegar em áudio de podcast quality, o caminho é longo:</p>
<ol>
<li><strong>Normalização de loudness</strong>: Spotify exige -14 LUFS. Sem normalização, cada turno tem volume diferente.</li>
<li><strong>Re-encoding</strong>: Os WAVs do modelo podem ter formatos sutilmente diferentes (sample rate, bit depth). Concatenar com <code>-c copy</code> causa <strong>segmentos silenciosos</strong>. Precisa re-encodar tudo pro mesmo formato.</li>
<li><strong>Conversão pra MP3</strong>: CBR 192kbps, 44.1kHz, com metadata ID3.</li>
</ol>
<p>E a normalização de loudness precisa ser <strong>two-pass</strong>: primeiro mede, depois aplica. Single-pass é chute — e chute em produção gera áudio com volume inconsistente entre episódios.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Pass 1: análise</span>
</span></span><span class="line"><span class="cl">ffmpeg -i input.wav -af <span class="nv">loudnorm</span><span class="o">=</span><span class="nv">I</span><span class="o">=</span>-14:print_format<span class="o">=</span>json -f null -
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Pass 2: aplicação dos valores medidos</span>
</span></span><span class="line"><span class="cl">ffmpeg -i input.wav -af <span class="nv">loudnorm</span><span class="o">=</span><span class="nv">I</span><span class="o">=</span>-14:measured_I<span class="o">=</span>-18.5:measured_TP<span class="o">=</span>-2.1:... output.wav</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tudo isso roda no servidor, depois da geração do TTS. E precisa de <code>ffmpeg</code> instalado no container — mais uma dependência pra gerenciar.</p>
<h2>Problema 7: O Script Importa Mais que o Modelo de Voz<span class="hx:absolute hx:-mt-20" id="problema-7-o-script-importa-mais-que-o-modelo-de-voz"></span>
    <a href="#problema-7-o-script-importa-mais-que-o-modelo-de-voz" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de resolver GPU, cold start, voice cloning, sampling, normalização — o podcast sai. E soa&hellip; robótico. Não por causa do TTS. O TTS é excelente. Soa robótico porque o <strong>texto</strong> é robótico.</p>
<p>Esse é o problema que ninguém menciona em pipelines de TTS: a qualidade do áudio começa no script, não no modelo de voz. Um TTS perfeito lendo um texto seco e formal produz áudio seco e formal. Garbage in, garbage out — mas num nível que você não espera.</p>
<h3>Duas Passadas: Draft e Refine<span class="hx:absolute hx:-mt-20" id="duas-passadas-draft-e-refine"></span>
    <a href="#duas-passadas-draft-e-refine" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A solução é gerar o script em duas passadas de LLM:</p>
<p><strong>Passada 1 (Draft)</strong>: Transforma a newsletter em diálogo. Aqui o LLM recebe o conteúdo completo e cria a estrutura — quem fala o quê, em que ordem, com quais transições. <strong>Sem mudar o conteúdo, só a forma!</strong></p>
<p><strong>Passada 2 (Refine)</strong>: Pega o draft e torna natural. Adiciona hesitações, reações, interrupções, auto-correções. Transforma &ldquo;script lido&rdquo; em &ldquo;conversa real&rdquo;.</p>
<p>Por que não fazer tudo numa passada? Porque são tarefas cognitivamente diferentes. Estruturar conteúdo e adicionar naturalidade ao mesmo tempo resulta num texto que não faz nem um nem outro direito. Duas passadas especializadas produzem resultado consistentemente melhor.</p>
<h3>Personalidade Precisa de Instruções Brutalmente Específicas<span class="hx:absolute hx:-mt-20" id="personalidade-precisa-de-instruções-brutalmente-específicas"></span>
    <a href="#personalidade-precisa-de-instru%c3%a7%c3%b5es-brutalmente-espec%c3%adficas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>&ldquo;Faça o Akita soar confiante&rdquo; não funciona. O LLM vai produzir confiança genérica — frases motivacionais, tom positivo. Não é isso. Akita é confiante porque é <strong>assertivo e impaciente</strong>. A diferença está nos detalhes do prompt:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Akita Delivery Style
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Declarative, not tentative**: &#34;Isso é óbvio&#34; not &#34;Eu acho que talvez isso seja...&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Dismissive of nonsense**: &#34;Isso é bobagem&#34;, &#34;Não tem nenhum mistério aqui&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Owns his track record**: &#34;Eu avisei&#34;, &#34;Quem assistiu meu canal já sabe&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Never hedges**: No &#34;talvez&#34;, &#34;quem sabe&#34;, &#34;pode ser que&#34;
</span></span><span class="line"><span class="cl">- <span class="gs">**Impatient with the obvious**</span>: &#34;Vocês já sabem como funciona&#34;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pro Marvin (o co-host robô):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">-</span> **Filler words** (Marvin only): &#34;bom...&#34;, &#34;olha...&#34;, &#34;enfim&#34;, &#34;pois é&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Self-corrections** (Marvin only): &#34;quer dizer...&#34;, &#34;ou melhor...&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> Akita doesn&#39;t second-guess himself — if he corrects, it&#39;s
</span></span><span class="line"><span class="cl">  &#34;Não, peraí&#34; and he restates with even MORE conviction</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sem esse nível de especificidade, os dois personagens soam iguais. O LLM precisa de exemplos concretos de <strong>como</strong> cada um fala, não de descrições abstratas de personalidade.</p>
<h3>Normalização Pra Fala: O Que Parece Certo no Texto Soa Errado no Áudio<span class="hx:absolute hx:-mt-20" id="normalização-pra-fala-o-que-parece-certo-no-texto-soa-errado-no-áudio"></span>
    <a href="#normaliza%c3%a7%c3%a3o-pra-fala-o-que-parece-certo-no-texto-soa-errado-no-%c3%a1udio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Siglas. O inimigo invisível do TTS. &ldquo;EUA&rdquo; no texto é perfeitamente legível. Falado pelo TTS, sai &ldquo;éu-á&rdquo; — incompreensível. O prompt precisa de regras explícitas de normalização:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Spoken Text Normalization (MANDATORY)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`EUA`</span> → <span class="sb">`Estados Unidos`</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`UE`</span> → <span class="sb">`União Europeia`</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`IA`</span> → <span class="sb">`inteligência artificial`</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`LLM`</span> → <span class="sb">`modelo de linguagem`</span>
</span></span><span class="line"><span class="cl">- <span class="sb">`API`</span> → <span class="sb">`interface de programação`</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso precisa estar no prompt de <strong>ambas</strong> as passadas (draft e refine), porque o LLM tem tendência a reintroduzir siglas que removeu na passada anterior.</p>
<h3>O LLM Vai Reescrever Suas Opiniões (Se Você Deixar)<span class="hx:absolute hx:-mt-20" id="o-llm-vai-reescrever-suas-opiniões-se-você-deixar"></span>
    <a href="#o-llm-vai-reescrever-suas-opini%c3%b5es-se-voc%c3%aa-deixar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Essa foi a lição mais sutil. A newsletter tem comentários opinativos do Akita e do Marvin em cada seção. Quando o LLM transforma em diálogo, ele <strong>substitui</strong> as opiniões por versões mais &ldquo;balanceadas&rdquo; e &ldquo;nuançadas&rdquo;. Akita que dizia &ldquo;isso é bobagem&rdquo; vira Akita dizendo &ldquo;isso tem seus méritos, mas&hellip;&rdquo;. Marvin pessimista vira Marvin &ldquo;levemente cético&rdquo;.</p>
<p>O LLM faz isso porque foi treinado pra ser helpful e balanced. Opiniões fortes parecem &ldquo;unhelpful&rdquo; pro modelo, então ele suaviza. A instrução original — &ldquo;use os comentários da newsletter como sementes pra conversa&rdquo; — era vaga demais. Dava liberdade pro modelo reinterpretar.</p>
<p>A correção é ser explícito e inflexível:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">**PRESERVE THE ORIGINAL OPINIONS**: The newsletter contains Akita and
</span></span><span class="line"><span class="cl">Marvin commentary on each topic. These opinions are the author&#39;s real
</span></span><span class="line"><span class="cl">voice — you MUST preserve their substance, stance, and conclusions.
</span></span><span class="line"><span class="cl">Make the delivery conversational, but do NOT change what they actually
</span></span><span class="line"><span class="cl">think. If Akita&#39;s comment says &#34;isso é bobagem&#34;, the podcast version
</span></span><span class="line"><span class="cl">must convey the same dismissal. Rephrase for spoken delivery, but
</span></span><span class="line"><span class="cl">never substitute a different opinion.</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É a diferença entre &ldquo;adapte pra conversa&rdquo; (LLM entende: &ldquo;reescreva como quiser&rdquo;) e &ldquo;mude o COMO, nunca o QUÊ&rdquo; (LLM entende: &ldquo;mantenha a opinião, troque a embalagem&rdquo;).</p>
<p>E isso vale pra qualquer prompt engineering com conteúdo autoral: <strong>o LLM vai homogeneizar sua voz se você não for explícito sobre preservá-la</strong>. E num podcast onde a personalidade dos hosts É o produto, perder a voz é perder tudo.</p>
<h2>O Custo Real de IA em Produção<span class="hx:absolute hx:-mt-20" id="o-custo-real-de-ia-em-produção"></span>
    <a href="#o-custo-real-de-ia-em-produ%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos ser práticos sobre custos no RunPod:</p>
<table>
  <thead>
      <tr>
          <th>Recurso</th>
          <th>Custo</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPU L40 (serverless)</td>
          <td>~$0.39/hora</td>
      </tr>
      <tr>
          <td>Network Volume 20GB</td>
          <td>$1.40/mês</td>
      </tr>
      <tr>
          <td>Geração de 1 episódio (~30 min áudio)</td>
          <td>~$0.50–1.50</td>
      </tr>
  </tbody>
</table>
<p>O custo por episódio é baixo. O custo de <strong>descobrir como fazer funcionar</strong> é alto. Foram dezenas de horas de experimentação, logs incompreensíveis de CUDA, gerações falhadas, e áudio com qualidade inutilizável antes de chegar numa pipeline estável.</p>
<p>E o custo cognitivo: manter dois ecossistemas (Python pra ML, Ruby pra aplicação) sincronizados, com interfaces HTTP entre eles, e debugging que cruza fronteiras de linguagem.</p>
<p>De qualquer forma, pra quem pensou <em>&ldquo;Por que não Elevenlabs?&rdquo;</em>, bom, eu consegui qualidade similar, numa fração do custo, e aprendi muito no processo (e vocês também, lendo aqui).</p>
<h2>Lições<span class="hx:absolute hx:-mt-20" id="lições"></span>
    <a href="#li%c3%a7%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol>
<li>
<p><strong>Cold start é um requisito, não um bug.</strong> Design seu sistema esperando que o modelo demore pra carregar.</p>
</li>
<li>
<p><strong>Network volumes são obrigatórios</strong> pra modelos grandes. Não bake pesos no Docker, não faça download no boot.</p>
</li>
<li>
<p><strong>Parâmetros de sampling não são universais.</strong> Cada modo de operação do mesmo modelo pode precisar de configuração completamente diferente.</p>
</li>
<li>
<p><strong>A qualidade da entrada domina a qualidade da saída.</strong> Tanto pro áudio de referência do TTS quanto pro script. Se a referência corta a primeira sílaba, toda geração corta. Se o script é genérico, o áudio é genérico.</p>
</li>
<li>
<p><strong>Timeouts em todas as camadas.</strong> Token limit, process timeout, HTTP timeout. Se não limitar, o custo escala e a geração trava.</p>
</li>
<li>
<p><strong>Pós-processamento é metade do pipeline.</strong> O modelo gera matéria-prima. Normalização, encoding, metadata — tudo isso é trabalho real.</p>
</li>
<li>
<p><strong>Separe o servidor de ML da aplicação.</strong> Python pra inference, Ruby pra orquestração. Tentar fazer tudo numa linguagem é receita pra frustração.</p>
</li>
<li>
<p><strong>Prompts precisam de duas passadas pra conteúdo complexo.</strong> Estruturar e naturalizar são tarefas diferentes. Forçar as duas numa passada produz resultado medíocre em ambas.</p>
</li>
<li>
<p><strong>O LLM vai homogeneizar sua voz.</strong> Opiniões fortes viram &ldquo;nuanced takes&rdquo; se você não for explícito sobre preservá-las. Instrua o modelo a mudar a embalagem, nunca o conteúdo.</p>
</li>
</ol>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>IA em produção não é um tutorial de HuggingFace com 10 linhas. É infraestrutura, é experimentação, é debugging de problemas que não existem em software tradicional. Mas quando funciona — quando seu sistema gera um podcast inteiro automaticamente toda semana, com qualidade broadcast, sem intervenção humana — a sensação é que valeu cada hora de dor de cabeça.</p>
<p>O truque é não subestimar a complexidade. E ter um sistema robusto (como os jobs do Rails que mencionei no primeiro post) orquestrando tudo — porque IA sem orquestração confiável é só um script caro que trava.</p>
]]></content:encoded><category>the-m-akita-chronicles</category><category>midia-generativa</category><category>cloud</category></item><item><title>Protegendo seu Home Server com Cloudflare Zero Trust</title><link>https://akitaonrails.github.io/2025/09/10/protegendo-seu-home-server-com-cloudflare-zero-trust/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/10/protegendo-seu-home-server-com-cloudflare-zero-trust/</guid><pubDate>Wed, 10 Sep 2025 02:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.com/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; explico como eu registrei um domínio de verdade na Cloudflare e criei túneis seguros encriptados que me permitem acessar até de fora de casa, sem precisar expor portas no roteador ou qualquer coisa assim.&lt;/p&gt;
&lt;p&gt;Desta forma posso acessar &lt;code&gt;https://portainer.fabioakita.dev&lt;/code&gt; (domínio falso de exemplo) do meu celular, por 5G, e vai acessar o container Docker do portainer, que está na minha LAN em &lt;code&gt;http://192.168.0.200:9000&lt;/code&gt; e só era acessível em casa. Tudo via um túnel seguro.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="https://akitaonrails.com/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/"target="_blank" rel="noopener">post anterior</a> explico como eu registrei um domínio de verdade na Cloudflare e criei túneis seguros encriptados que me permitem acessar até de fora de casa, sem precisar expor portas no roteador ou qualquer coisa assim.</p>
<p>Desta forma posso acessar <code>https://portainer.fabioakita.dev</code> (domínio falso de exemplo) do meu celular, por 5G, e vai acessar o container Docker do portainer, que está na minha LAN em <code>http://192.168.0.200:9000</code> e só era acessível em casa. Tudo via um túnel seguro.</p>
<p>Notem que estou o tempo todo falando que estou usando domínio e IPs falsos de exemplo nestes posts, porque não quero expor o domínio de verdade.</p>
<p>Só de estar atrás de um túnel seguro da Cloudflare, significa que estou embaixo de várias camadas de segurança como proteção contra ataques DDoS, e vários outros tipos de ataques conhecidos. E embora eu tome o cuidado de configurar cada serviço com uma senha forte e aleatória, diferente pra cada serviço, o risco não é zero que alguém possa encontrar meus serviços e tente quebrar a senha, ou use alguma vulnerabilidade zero-day em um dos diversos serviços, como Prowlarr ou Sonarr da vida.</p>
<p>Então eu fico à mercê desses bugs ainda não encontrados que podem surgir do nada, em particular na tela de login de cada um deles, ou endpoints de APIs.</p>
<p>Pra evitar esse risco, existe outro produto da Cloudflare que vale a pena configurar: o <a href="https://www.cloudflare.com/zero-trust/products/access/"target="_blank" rel="noopener"><strong>Zero Trust</strong></a>. A idéia é simples: <strong>adicionar um novo login por cima de tudo</strong>, da própria Cloudflare, pra proteger cada um dos serviços. Dessa forma, alguém precisa quebrar a própria Cloudflare antes de conseguir tentar quebrar um dos meus serviços.</p>
<h3>1. Contratar o serviço<span class="hx:absolute hx:-mt-20" id="1-contratar-o-serviço"></span>
    <a href="#1-contratar-o-servi%c3%a7o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Essa é a parte fácil. Pra poucos serviços e poucos usuários (somente eu), existe um plano <strong>gratuito</strong>, só contratar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_00-35-57.png" alt="Free Plan"  loading="lazy" /></p>
<h3>2. Criar Credenciais OAuth no Google Cloud<span class="hx:absolute hx:-mt-20" id="2-criar-credenciais-oauth-no-google-cloud"></span>
    <a href="#2-criar-credenciais-oauth-no-google-cloud" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como eu pago Google Workspace, decidi usar login integrado com OAuth do Google, mas pode ser qualquer outro, você escolhe o que preferir. Pra isso preciso acessar meu <a href="https://console.cloud.google.com"target="_blank" rel="noopener">console do Google Cloud</a> e entrar na seção <strong>API &amp; Services</strong>. Aqui criamos credenciais pra <strong>OAuth client ID</strong> e configuramos assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_00-58-02.png" alt="Oauth client config"  loading="lazy" /></p>
<p>O nome tanto faz, é o que vai aparecer na tela de login, o importante são os campos de <strong>Javascript Origins</strong> que tem que ser neste formato:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>https://&lt;your-team-name&gt;.cloudflareaccess.com</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E o campo de <strong>redirect URIs</strong> que é pra onde a tela de login do Google vai redirecionar, e tem que devolver pro nosso Cloudflare:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>https://&lt;your-team-name&gt;.cloudflareaccess.com/cdn-cgi/access/callback</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Feito isso, vai te dar a opção de baixar um arquivo <strong>JSON</strong> com os metadados e segredo. O importante é que no final ele vai te dar um <strong>OAuth Client ID</strong>, que é uma URL no formato <code>xxxxxx.apps.googleusercontent.com</code> e o mais importante, um <strong>Client Secret</strong>, que é bom guardar num BitWarden ou outro meio seguro.</p>
<h3>4. Criando Login Method no Zero Trust<span class="hx:absolute hx:-mt-20" id="4-criando-login-method-no-zero-trust"></span>
    <a href="#4-criando-login-method-no-zero-trust" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora voltamos pro console do Zero Trust na Cloudflare e clicamos em &ldquo;Settings&rdquo; no fim do menu, onde precisamos entrar em &ldquo;Authentication&rdquo; e criar um novo <strong>Login Method</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-04-35.png" alt="Login Method"  loading="lazy" /></p>
<p>Depois de clicar em &ldquo;Add new&rdquo; é só preencher com os dados que trouxemos do Google Cloud:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-07-17.png" alt="New Google Login Method"  loading="lazy" /></p>
<p>Novamente, coloque o nome que quiser e cole o OAuth Client ID como &ldquo;App ID&rdquo; e o Client Secret, e pronto, com isso temos Google OAuth integrado como método de login no nosso Zero Trust.</p>
<h3>5. Criando Policies<span class="hx:absolute hx:-mt-20" id="5-criando-policies"></span>
    <a href="#5-criando-policies" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora vem uma parte que eu não sei se fiz totalmente correto. Se não fiz, expliquem nos comentários abaixo!</p>
<p>Entendi que precisamos criar, pelo menos, duas <strong>Policies</strong> uma pra Service Auth e outra pro Email que quero permitir vindo do Google.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-09-06.png" alt="Policies"  loading="lazy" /></p>
<p>Pra continuar, só clicar em &ldquo;Add a Policy&rdquo; e editar a primeira policy:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-10-42.png" alt="Service Token"  loading="lazy" /></p>
<p>O nome, pode ser o qualquer coisa, o importante é que em &ldquo;Action&rdquo; escolha <strong>&ldquo;Service Auth&rdquo;</strong> e em &ldquo;Add Rules&rdquo; escolha <strong>Any Access Service Token</strong> (esta é a parte que eu acho que está errada, mas não achei outra opção melhor - não entendi bem como funciona Service Token).</p>
<p>Agora precisamos criar a segunda policy assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-20-47.png" alt="Email Policy"  loading="lazy" /></p>
<p>Desta vez o importante é term &ldquo;Action&rdquo; como &ldquo;Allow&rdquo; e adicionar a Rule &ldquo;Emails&rdquo; e escolher o email que você quer autenticar. No meu caso, somente eu.</p>
<h3>6. Criando nova Applications<span class="hx:absolute hx:-mt-20" id="6-criando-nova-applications"></span>
    <a href="#6-criando-nova-applications" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora podemos criar uma nova application no Zero Trust:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-22-51.png" alt="Applications"  loading="lazy" /></p>
<p>Clique em &ldquo;Add an Application&rdquo; e escolha type sendo <strong>&ldquo;Self-Hosted&rdquo;</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-26-20.png" alt="Application Basic Information"  loading="lazy" /></p>
<p>Entendi que cada application suporta no máximo 5 hostnames. Eu tenho bem mais que isso então vou ter que criar mais de uma application. A configuração em todas é a mesma. Em &ldquo;Basic Information&rdquo; só cadastrei os subdomínios de cada container que quero proteger.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-28-37.png" alt="Policies"  loading="lazy" /></p>
<p>Indo pra aba <strong>&ldquo;Policies&rdquo;</strong> basta clicar em &ldquo;Select existing policies&rdquo; e adicionar as duas policies que acabamos de criar. Só isso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-29-43.png" alt="Login Method"  loading="lazy" /></p>
<p>Finalmente, em <strong>&ldquo;Login methods&rdquo;</strong> só escolher a do Google que acabamos de configurar também. E sim, estas são as telas de edição de uma application que eu já tinha criado. A tela de nova application tem tudo isso numa tela só, mas vocês entenderam.</p>
<p>E eu entendi que é só isso. Teoricamente já está tudo funcionando!</p>
<h3>7. Testando o novo Login<span class="hx:absolute hx:-mt-20" id="7-testando-o-novo-login"></span>
    <a href="#7-testando-o-novo-login" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se nada der errado, basta acessar um dos hostnames que cadastramos na application do Zero Trust, por exemplo <code>https://immich.fabioakita.dev</code> e temos que receber esta tela primeiro:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-33-41.png" alt="Zero Trust Login"  loading="lazy" /></p>
<p>Se isso aparecer, sucesso! O Zero Trust impediu de acessar minha aplicação sem antes eu me logar nele.</p>
<p>Depois pode configurar o texto pra ficar como quiser, mas como é pra mim mesmo, tanto faz o texto de &ldquo;bem vindo&rdquo;, na verdade, <strong>ninguém é bem vindo aqui, somente EU!</strong></p>
<p>Clicando em &ldquo;Google&rdquo; vai abrir o Pop-up que todo mundo conhece de login do Google e só depois disso que eu consigo entrar no login da minha aplicação por baixo. E isso vale pra todos os hostnames que adicionei nas applications do Zero Trust.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/userinter.png" alt="Google Sign In"  loading="lazy" /></p>
<p>Uma vez que fizer login no Google com o usuário correto, a sessão vai ficar válida por 24 horas (que é o que configuramos nas policies e application). Nesse período não precisa mais fazer login no Zero Trust em nenhuma das outras aplicações do meu home server. A sessão é a mesma pra todas as applications.</p>
<h3>8. Deslogando Sessões<span class="hx:absolute hx:-mt-20" id="8-deslogando-sessões"></span>
    <a href="#8-deslogando-sess%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Depois que fizer o primeiro login com seu usuário, dá pra controlar essa sessão indo no menu &ldquo;My team&rdquo; e &ldquo;Users&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-38-38.png" alt="Users"  loading="lazy" /></p>
<p>Clicando no usuário, temos várias informações e opções, em particular veja <strong>&ldquo;Revoke Sessions&rdquo;</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-40-31.png" alt="Revoke"  loading="lazy" /></p>
<p>Desta forma podemos testar o login sem precisar esperar 24 horas até o token de autenticação expirar com o tempo. É uma forma de forçar o logout dos seus usuários. No plano gratuito do Zero Trust, entendi que dá pra ter até 10 usuários na sua organização.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu nunca tinha usado Zero Trust, então pode ser que tenha cometido algum erro no procedimento, então leiam a documentação também. Em princípio, parece que está funcionando. Já criei mais applications e cadastrei o resto dos meus hostnames,</p>
<p>Só de usar CloudflareD pra túneis encriptados, todo tráfego que chega no meu home server passa por uma série de proteções da Cloudflare que incluem: DDoS protection, URL normalization, Origin Rules, IP Address Rules, WAF (Web Application Firewall), Bots Mitigation, Rate Limiting, Cache, Compression e mais.</p>
<p>Essas camadas protegem contra os principais tipos de ataques conhecidos e eles estão sempre adicionando novas regras a cada nova vulnerabilidade que fica conhecida. Mesmo que você, como desenvolvedor, não saiba como se proteger de tudo na sua aplicação, existe a chance da Cloudflare te proteger antes. Por isso recomendo deixar seus domínios sempre sob controle da Cloudflare.</p>
<p>Em cima disso tudo, ainda adicionamos <strong>Zero Trust</strong>, que garante que só depois de um login com minha conta do Google é que ele vai começar a permitir acesso pra baixo. E no final disso ainda tem a última barreira que são os logins de cada aplicação independente rodando no meu home server.</p>
<p>Além disso, no dashboard da Cloudflare tem monitoramento do tráfego, analytics e muito mais. Eu configurei só o básico, mas se quiser ir mais a fundo, tem várias policies e regras extras que podem ser adicionadas.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-53-58.png" alt="Analytics"  loading="lazy" /></p>
<p>Pra alguém conseguir atravessar tudo isso e conseguir invadir meu home server e minha LAN, realmente precisa ser alguém muito dedicado e muito inteligente - não acredito que exista alguém assim perdendo tanto tempo pra acessar um servidor de videos 🤣 mas podem tentar.</p>
]]></content:encoded><category>homelab</category><category>seguranca</category><category>cloud</category></item><item><title>[Akitando] #143 - Discutindo sobre Banco de Dados - Dos primórdios a Big Data</title><link>https://akitaonrails.github.io/2023/07/22/akitando-143-discutindo-sobre-banco-de-dados-dos-primordios-a-big-data/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/07/22/akitando-143-discutindo-sobre-banco-de-dados-dos-primordios-a-big-data/</guid><pubDate>Sat, 22 Jul 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/Bfm3Ms2cTg0"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Não se pode ser um bom programador sem entender de verdade como bancos de dados funcionam. E não se pode entender banco de dados sem entender algoritmos e estruturas de dados. E hoje vou mostrar como dos primeiros bancos de dados dos anos 80 chegamos aos SQL e NoSQL atuais.&lt;/p&gt;
&lt;p&gt;Este video complementa meu video anterior &amp;ldquo;Fiz um Servidor de SQL?&amp;rdquo; e outros videos que vou referenciando ao longo deste. Anote tudo e assistam depois.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Bfm3Ms2cTg0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Não se pode ser um bom programador sem entender de verdade como bancos de dados funcionam. E não se pode entender banco de dados sem entender algoritmos e estruturas de dados. E hoje vou mostrar como dos primeiros bancos de dados dos anos 80 chegamos aos SQL e NoSQL atuais.</p>
<p>Este video complementa meu video anterior &ldquo;Fiz um Servidor de SQL?&rdquo; e outros videos que vou referenciando ao longo deste. Anote tudo e assistam depois.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:01:15 - CAP 01 - O que é um Banco de Dados? A era do dBase</li>
<li>00:10:04 - CAP 02 - Procurando com Índices - NDX e B-Tree</li>
<li>00:18:53 - CAP 03 - Do DBF ao SQLite3 - O banco mais usado do Mundo</li>
<li>00:23:42 - CAP 04 - Tudo são Árvores B - A estrutura mais importante da computaçãot</li>
<li>00:28:51 - CAP 05 - JSON no SQL?? Você não está entendendo</li>
<li>00:31:45 - CAP 06 - Proteção e Consistência - As garantias dos bancos</li>
<li>00:35:38 - CAP 07 - Do xBase a Cliente-Servidor - As 3 Camadas</li>
<li>00:38:59 - CAP 08 - Programando Errado - Você não sabe usar o servidor</li>
<li>00:45:26 - CAP 09 - Constraints - Usando do jeito certo</li>
<li>00:48:13 - CAP 10 - Monitorando - Sempre monitore tudo!</li>
<li>00:50:23 - CAP 11 - Big Data - Virando gente grande</li>
<li>00:53:40 - CAP 12 - Dividindo pra Conquistar - Acelerando seu App Web</li>
<li>00:56:28 - CAP 13 - Big Data Parte 2 - Por que NoSQL?</li>
<li>01:00:39 - CAP 14 - Teorema CAP - As partes importantes</li>
<li>01:07:12 - CAP 15 - Estratégias de Chaves Primárias - Chaves Surrogadas</li>
<li>01:11:25 - CAP 16 - Hashing Consistente - Sharding</li>
<li>01:18:42 - CAP 17 - O Problema de Cloud - Calculando Precificação</li>
<li>01:24:51 - CAP 18 - Recomendação pra Iniciantes - Arroz com Feijão</li>
<li>01:27:39 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://www.heroku.com/postgres"target="_blank" rel="noopener">Heroku</a></li>
<li><a href="https://aws.amazon.com/elasticache/"target="_blank" rel="noopener">Amazon ElastiCache</a></li>
<li><a href="https://aws.amazon.com/rds/"target="_blank" rel="noopener">Amazon RDS</a></li>
<li><a href="https://aws.amazon.com/pt/rds/aurora/"target="_blank" rel="noopener">Amazon Aurora</a></li>
<li><a href="https://aws.amazon.com/s3/storage-classes/glacier/"target="_blank" rel="noopener">Amazon S3 Glacier Storage Classes | AWS</a></li>
<li><a href="https://www.datastax.com/products/datastax-astra"target="_blank" rel="noopener">Astra DB | DataStax</a></li>
<li><a href="https://www.scylladb.com/product/scylla-cloud/"target="_blank" rel="noopener">Scylla Cloud</a></li>
<li><a href="https://www.sap.com/products/technology-platform/hana.html"target="_blank" rel="noopener">SAP HANA Cloud</a></li>
<li><a href="https://www.oracle.com/big-data/big-data-sql/"target="_blank" rel="noopener">Big Data SQL Cloud Service | Oracle</a></li>
<li><a href="https://azure.microsoft.com/en-us/products/azure-sql/database"target="_blank" rel="noopener">Azure SQL Database</a></li>
<li><a href="https://cloud.google.com/sql"target="_blank" rel="noopener">Google Cloud</a></li>
<li><a href="https://www.mongodb.com/pricing"target="_blank" rel="noopener">Pricing | MongoDB</a></li>
<li><a href="https://valentina-db.com/en/valentina-sqlite-server"target="_blank" rel="noopener">Valentina SQLite Server</a></li>
<li><a href="https://calculator.aws/#/addService/DynamoDB"target="_blank" rel="noopener">Configure Amazon DynamoDB (calculator.aws)</a></li>
<li><a href="https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/"target="_blank" rel="noopener">CAP Twelve Years Later: How the &ldquo;Rules&rdquo; Have Changed (infoq.com)</a></li>
<li>[Dynamo: Amazon’s highly available key-value store - Amazon Science](<a href="https://www.amazon.science/publications/dynamo-amazons-highly-available-key-value-store"target="_blank" rel="noopener">https://www.amazon.science/publications/dynamo-amazons-highly-available-key-value-store</a></li>
<li><a href="https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-json/"target="_blank" rel="noopener">PostgreSQL JSON Tutorial (postgresqltutorial.com)</a></li>
<li><a href="https://github.com/azadkuh/sqlite-amalgamation"target="_blank" rel="noopener">GitHub - azadkuh/sqlite-amalgamation: The SQLite amalgamation mirror with cmake</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Desde que comecei o canal, banco de dados é um dos temas que eu mais queria discutir, mas vim postergando pra poder explicar fundamentos antes. O objetivo não é ensinar SQL ou MongoDB nem nenhum outro em detalhes, é dar perspectiva pra quem está iniciando entender porque diabos existem tantos tipos diferentes de bancos de dados e quando se deve considerar usar um em vez do outro. Vai ser um video mais alto nível, sem tanto código em si, mas sim dar insights pra vocês pesquisarem nas respectivas documentações depois. Vou começar nos primórdios e só na metade vamos começar a cair em SQL e NoSQL. Então, vamos lá.</p>
<p>(&hellip;)</p>
<p>Infelizmente, se você estava esperando um video sobre modelagem, modelo entidade-relacional, ORMs, design patterns e coisas assim, ainda não é este video. Objetivo é falar mais o que acontece por baixo dos panos, as tecnologias que formam um banco de dados. Eu acho que modelagem só é efetiva se você sabe como a plataforma por baixo realmente funciona, senão modelagem vira só superstição e você só acha que sua modelagem é boa sem realmente saber o que ela causa por baixo. Tudo que você não sabe como realmente funciona vira superstição.</p>
<p>Sempre falo que uma das vantagens que eu tive, foi conseguir testemunhar as versões 1.0 de quase tudo que surgiu nos últimos 40 anos. Se você pegar um banco de dados moderno, como um DynamoDB da AWS, ou um Postgres, eles tem dezenas de funcionalidades que não são imediatamente óbvios pra que foram criados. Sem saber isso, você acaba tentando usar nos lugares errados ou acha que são ruins, ou acha que você que é burro e não consegue usar.</p>
<p>Pense por um segundo, o que é um banco de dados? À primeira vista poderíamos dizer que é um programa que facilita e gerencia o armazenamento de dados estruturados. Pense um armário com gavetas, cada gaveta com um tipo de formulário. As gavetas seriam nossas tabelas e o armário seria o banco de dados. Acho que é parecido com o que a maioria de vocês deve imaginar.</p>
<p>Pra iniciantes, acho que vale a pena retornar aos fundamentos. Por isso sempre falo tanto que é importante aprender a fundação, como algoritmos e estruturas de dados. O problema é que a estrutura mais simples de aprender e por causa disso comum de usar são listas. Essas seriam as gavetas. Em particular listas ligadas ou arrays puros. E se escolher partir desse ponto, vai ter problemas. Vamos entender.</p>
<p>O banco de dados mais simples de todos, um armário só com uma gaveta, é um mero array em memória com registros, como um struct ou uma instância de uma classe, um objeto. Lembram como um array é estruturado em memória? Um array de inteiros com 10 elementos, provavelmente tem 10 vezes 64 bits ou 8 bytes, ocupando 80 bytes no total. Todo array tem elementos de mesmo tamanho fixo, sempre.</p>
<p>Se eu quiser pegar o 5o elemento desse array, basta contar que tenho 4 elementos antes, vezes 8 bytes, então 32 bytes pra frente, vou achar o 5o elemento. Se eu souber qual posição do array quero encontrar, independe se está no começo ou no fim, o tempo de acesso é o mesmo, tempo constante. O array pode ter 10 elementos ou 10 mil elementos. Leva mais ou menos o mesmo tempo pra acessar qualquer posição.</p>
<p>Quando todos os meus dados cabem na memória RAM, toda operação é simples. Mesmo se eu não souber a posição, na pior das hipóteses faço força bruta via um loop do primeiro até o último elemento, até encontrar. E isso seria de longe, o jeito mais tosco de se procurar alguma coisa numa lista, mas pra conjuntos de dados pequenos, quase ninguém nota a diferença. Mas deixa eu repetir: procurar dados numa lista com um grande loop e if dentro é o jeito mais absurdamente horrendo e inaceitável. Vou repetir de novo pra ficar claro: se você está fazendo um loop pra procurar itens numa lista, você é insano!</p>
<p>Isso porque todo programador, especialmente iniciantes, sempre lidam com poucos dados. É muito difícil entender complexidade de algoritmos e diferença nos tempos de processamentos se só tem cem, mil elementos. Só quando começar a lidar com centenas de milhares, milhões de elementos, que vai começar a entender a diferença. Já vou chegar nisso.</p>
<p>Enfim. Imagine que lá no começo da computação, todo mundo lida com poucos dados comparado com hoje. Por isso as primeiras soluções são simples. Um array com elementos de tamanho fixo é a forma mais fácil de pensar. E quando acaba a memória? Digamos, uma lista de produtos ou lista de clientes num ecommerce? Obviamente não cabe tudo em RAM. Daí temos que pensar em gravar esses dados em disco. E a forma mais simples de fazer isso é o bom e velho arquivo append only, onde vamos adicionando novos elementos no fim do arquivo.</p>
<p>É fácil. Pense num registro como uma struct de C, ou uma classe de Javascript ou Python, só que garantindo que todos os elementos tenham tamanho fixo. Determino uma struct com o primeiro campo sendo o nome completo, com umas 100 letras, onde cada uma é representada por um código UTF-8, de 8 bits,  100 bytes no total. Depois temos um número de telefone, com DDD de 3 caracteres, um prefixo de 3 números como era antigamente e um sufixo de 4 números, mais parênteses e hífen, total de 13 letras ou 13 bytes.</p>
<p>Temos um total de 100 bytes mais 13, ou 113 bytes. O primeiro registro vai ocupar o começo do arquivo começando no endereço zero e indo até o endereço 112 bytes. O segundo registro começa no byte 113 e vai até 225 e assim por diante. Posso abrir o arquivo, ir até o final e gravar mais um registro. E assim fica fácil sair cadastrando novos registros.</p>
<p>De curiosidade, queria aproveitar pra mostrar a segunda linguagem e ambiente de programação que aprendi depois do Basic, quando eu era pré-adolescente. Acho que era 1990 e tinha uns 13 ou 14 anos. Meu pai tinha pedido pro meu tio trazer um PC do Paraguai, era um PC-AT 286 com 1 megabyte de RAM, 10 ou 20 mega de HD. Veio instalado com MS-DOS 3.0 e meu tio tinha deixado também o programa que ele usava pra trabalhar: o dBase III Plus, ainda da antiga empresa Ashton-Tate.</p>
<p>E era só isso que tinha instalado. Não tinha internet naquela época. Eu não tinha livro nem nada. Por acaso um outro primo meu tava estudando isso e me emprestou uma apostila do curso que  tava fazendo. Foi meu primeiro PC, então só de ver ligado e ter um teclado que podia usar, em vez de uma máquina mecânica de datilografia, já achava incrível. Minha primeira ideia foi copiar a apostila do meu primo. Eu redigitei tudo do zero acho que no Wordstar.</p>
<p>Este é o Wordstar. Ele tem um aspecto esquisito porque tá configurado no modo de 50 linhas que o VGA permite, em vez de 25 linhas de CGA. Essa versão é uma das últimas que saiu pra DOS, acho que a 7.0, daí tem ajudas que as versões iniciais não tinham, com um menu completo com todas as opções. Senão a única forma de usar era lembrar dos atalhos como control mais P e B pra iniciar modo negrito, daí repetir control P e B pra voltar pro modo normal. Control P e Y pra itálico e assim por diante.</p>
<p>Era tão moderno que tinha até um Preview em modo gráfico pra mostrar como ficaria na impressora. Essa época estávamos na transição do modo texto de DOS pro modo gráfico de Windows 3. Muitos editores de texto atuais como o próprio VS Code ou Obsidian da vida tem modo de ficar focado que deixa o texto no centro e esconde toda distração. São inspirados em editores antigos como o Wordstar. Esse é o editor que o George R. R. Martin diz que usa pra escrever Game of Thrones.</p>
<p>Enfim, pequena tangente pra mostrar como eu copiei a apostila de dBase. Mais pra frente consegui comprar livros de verdade. Acho que nenhuma linguagem comprei tantos livros pra aprender quanto dBase e seu sucessor, o Clipper. Olha só os que ainda guardei. A razão de tanto livro é que antigamente não tinha Google nem Stackoverflow, lembrem-se disso. Depois que surgiu a Web, já não precisei de tanto livro assim.</p>
<p>Abrindo o dBase, tem um REPL, um interpretador de comandos, que nem quando se digita <code>node</code> ou <code>python</code> e dá pra começar a digitar comandos. Mas também tem um sistema de menus aqui pra facilitar. Olha só, posso escolher a opção de criar um novo banco de dados. Digito o campo &ldquo;nome&rdquo;, tipo &ldquo;character&rdquo; com 100 letras. Depois o campo &ldquo;telefone&rdquo;, tipo &ldquo;character&rdquo; também, com 13 letras. Vou aproveitar e adicionar um campo de &ldquo;empresa&rdquo; com 100 letras. Pronto. Daí quando salvo já me deixa começar a cadastrar.</p>
<p>Vamos lá, quero me cadastrar aqui, &ldquo;Fabio Akita&rdquo;, telefone bla bla bla, empresa Codeminer 42. Agora vou cadastrar &ldquo;Cecil Wayne&rdquo;, o criador do dBase que depois se tornaria vice-presidente da Ashton-Tate. Finalmente, vamos cadastrar Brian Russell, que criou o compilador Clipper, pra Nantucket Corporation. Nenhuma dessas empresas existe mais.</p>
<p>A Ashton Tate foi adquirida pela Borland que depois viraria Inprise, depois Embarcadero e a divisão CodeGear que ainda mantém o Delphi, se não me engano. A Nantucket iria pra CA ou Computer Associates, que desde 2018 faz parte da Broadcom. Todo o sucesso que eles tiveram nos anos 80 e 90 desapareceram ao ponto que hoje ninguém mais lembra. É um bom lembrete que nada dura pra sempre.</p>
<p>Enfim. Terminei de cadastrar meus contatos. Posso salvar e sair com control + end. Agora estou na tal linha de comando do interpretador. Posso abrir o arquivo do banco fazendo &ldquo;use contatos&rdquo;, digitar o comando LIST e olha só, lista o que acabei de registrar. Esse é o avô de todos os CRUDs, o CRUD original no mundo dos microcomputadores. Não o primeiro, mas possivelmente um dos mais populares.</p>
<p>O formato de arquivo que Cecil inventou foi o famoso DBF, literalmente database file. Quando eu era novo, antes de pular pra tecnologias mais novas como SQL Server da Microsoft, na minha cabeça &ldquo;banco de dados&rdquo; é o que você chama de tabela. O dBase gravava uma tabela por arquivo e chamava isso de banco de dados. Hoje, banco de dados é um servidor com conjuntos de tabelas num único arquivão. No fim dos anos 80, banco de dados, pra mim, era um arquivo com uma tabela.</p>
<p>Esse arquivo tem um espaço reservado nos primeiros endereços pra metadados, informações como o total de registros e coisas assim. Mas os registros em si tem todos o mesmo tamanho fixo. No exemplo dos contatos, são 100 bytes pro nome, 13 pro telefone e 100 pra empresa, então cada registro sempre vai ocupar 213 bytes. Basta ir pulando de 213 em 213 bytes pra ir avançando pro próximo registro.</p>
<p>Como programador você precisa entender essa estrutura. Digamos que eu queira encontrar o telefone do Brian. Como faz? Como é anos 80, vamos começar do jeito mais simples, ou seja, tosco. Podemos escrever um programinha besta, que vou chamar de &ldquo;list.prg&rdquo;. Abro o arquivo com &ldquo;USE&rdquo;, limpo a tela e começo a fazer um loop checando que não estamos no fim do arquivo, end-of-file ou EOF como tá aqui.</p>
<p>Daí fico comparando o campo nome do registro atual com &ldquo;Brian&rdquo;. E não acharia nada. Isso porque preciso procurar pelo nome exato, então &ldquo;Brian Russell&rdquo;. Claro, posso só pesquisar as primeiras letras também, mas é uma operação a mais, por agora vou simplificar. Da linha de comando do DOS podemos chamar o dbase passando esse programa.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>USE contatos
CLEAR
DO WHILE .NOT. EOF()
	IF NOME = &#39;Brian Russell&#39;
		@ 10,10 SAY &#39;Empresa:&#39; GET EMPRESA
		EXIT
	ENDIF
	SKIP
ENDDO</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesse exemplo idiota, começa pesquisando o primeiro registro, que é Fabio. Não achou e não é fim de arquivo, então vai pro próximo com o comando <code>SKIP</code>, que é Cecil, ainda não é fim de arquivo, então vai pro próximo e acha o Brian, daí é só pegar o campo empresa, imprimir na tela com esse comando <code>SAY</code> e sair do loop com <code>EXIT</code>.</p>
<p>Esse é o pior caso: onde o que quero achar é o último registro. Imagine um arquivo muito maior, com mil registros. Precisaria ir um a um mil vezes. Todas as vezes. Por isso, esse é jeito mais tosco e mais primitivo de procura num banco de dados. Agora que entendeu isso, prometa que nunca vai fazer desse jeito.</p>
<p>Num computador moderno, esse programa vasculhando milhares de registros ia demorar menos de um segundo. Mas nos anos 80 poderia levar alguns segundos, dependendo de quantos registros tenho. Mas claro, nem nos anos 80 a gente era ruim de fazer desse jeito tosco.</p>
<p>Em dBase mesmo dava pra criar um segundo arquivo, um índice. Basta fazer &ldquo;INDEX ON NOME TO INDNOME&rdquo; e vai criar um arquivo separado de índice com extensão &ldquo;NDX&rdquo;. E podemos abrir o banco de dados com &ldquo;USE contatos INDEX INDNOME&rdquo;. Como já tá aberto podemos só fazer &ldquo;SET INDEX TO INDNOME&rdquo;. É assim que podemos ficar mudando de índices dependendo de que campo queremos procurar.</p>
<p>Eu digitei errado o nome da empresa do Cecil, vamos usar <code>SEEK 'Cecil Wayne'</code> e fazer <code>REPLACE EMPRESA WITH 'Ashton Tate'</code>. Pronto, é assim que procuramos e depois modificamos um registro. O comando Seek vai usar o índice, em vez de fazer o loop com comparação como fiz antes. Aliás, esse comando REPLACE serve pra mudar dados dos campos do registro atual. Com SEEK podemos encontrar um registro, mas pra criar um novo, teríamos que fazer &ldquo;APPEND BLANK&rdquo; antes, pra &ldquo;apendar&rdquo;, anexar um registro vazio no fim do arquivo, daí o REPLACE gravaria os dados nesse registro novo. Esse é o básico.</p>
<p>Esse tipo de índice eu já expliquei no episódio de Árvores e também nos de armazenamento. É uma variante de uma B-Tree. Lembram que falei que árvores são as estruturas mais importantes na computação? O arquivo DBF de banco de dados é mais ou menos como se fosse um array de structs em disco, mas um arquivo NDX de índice é uma estrutura de árvore B. Daí, em vez do tempo de procura ser linear em força bruta com loop um a um, com o índice passa a ser logarítmico, que é uma ordem de grandeza mais rápido e eficiente.</p>
<p>Ou seja, no meu programa de procura tosca via loop e comparação, se cada comparação e skip levasse, digamos, 1 milissegundo, e eu tivesse 10 mil registros e o que eu procuro estivesse no último registro, então no pior caso levaria 10 mil milissegundos, 10 segundo inteiro. Mas se eu usar um índice em B-Tree, com complexidade logarítmica, levaria talvez pouco mais de 10 milissegundos.</p>
<p>Mesmo se eu dobrasse a quantidade de registros, o tempo de pesquisa não iria aumentar muito mais que isso, dado a natureza logarítmica. Dependendo do que estou fazendo, o ganho de performance poderia ser 100 vezes ou mais usando o índice correto. Por isso falamos tanto no mundo de bancos pra sempre garantir que tem um índice correto preparado.</p>
<p>Mas e se eu quisesse agora procurar pelo telefone? Ou pelo nome da empresa? Bom, podemos criar um índice pra cada campo, como <code>INDEX ON TELEFONE TO INDFONE</code> e <code>INDEX ON EMPRESA TO INDEMPR</code>. Note como o nome do índice eu tive que truncar, porque em MS-DOS nomes de arquivos podiam ter no máximo 8 letras. Era uma limitação do FAT16 antigo. O sistema de arquivos FAT que você usa pra formatar SD Cards pra câmera é ou FAT32 ou o mais moderno ExFAT, e não o FAT16 antigo. São compatíveis, mas com limites muito maiores, por isso dá pra ter nomes de arquivos longos normalmente.</p>
<p>Voltando pro DOS podemos listar os arquivos criados. Como disse antes, cada registro vai ocupar aproximadamente 213 bytes. Tenho 3 registros, então teríamos pelo menos 639 bytes. No caso o formato DBF grava mais coisas como cabeçalho de metadados, por isso faz sentido ser 986 bytes. Mas veja cada arquivo de índice. 1 kilobyte cada. Acho que é o tamanho mínimo pra um índice.</p>
<p>Eu fiz um programa besta pra adicionar mais registros falsos só pra aumentar o tamanho dos arquivos. Olha a listagem aqui. A chatice de uma linguagem tão rudimentar quanto dBase III é que ele sequer tem função pra gerar números aleatórios nem tem coisas triviais como calcular o módulo de um número. Então tive que fazer um gerador de números usando só segundos. Não tem nem função pra pegar milissegundos então tenho muitas letras repetidas, mas tudo bem. Olha só como tive que fazer pra calcular o módulo na mão. Daí vou pegando letras da tabela ASCII e concatenando. Super rudimentar.</p>
<p>Gerando 40 novos registros e reindexando, vamos ver o tamanho dos arquivos. O banco de dados, a tabela de contatos em si, com 43 registros agora ficou com quase 10 kilobytes, Pouco mais que 240 bytes por registro, em média. Já os índices, o de nome ficou com quase 8 kilobytes e telefone que é um campo menor, ficou com pouco mais de 2 kilobytes. Empresa é o mesmo tamanho de Nome então ficou com uns 8 kilobytes também.</p>
<p>Pense por um segundo. Os arquivos de índice estão ocupando quase o dobro do espaço da tabela em si. Muita gente pode não entender isso, mas índices ocupam espaço, e não é pouco espaço. Mais importante que isso. Num banco moderno, digamos que a operação de inserção na tabela de um novo registro leve 1 milissegundo. Mas agora digamos que temos esses 3 índices. E atualizar cada um dos índices leve mais 1 milissegundo cada. Agora é um total de 4 milissegundos pra inserir um novo registro e atualizar os índices. O tempo de inserção ficaria 4 vezes maior.</p>
<p>Índices é uma faca de dois gumes. Por um lado podemos acelerar a pesquisa em 100 vezes, talvez mais, mas por outro lado, operações como inserção, atualização ou deleção vão demorar duas vezes mais, quatro vezes mais, dependendo da quantidade de índices que tivermos. Entenderam?</p>
<p>Aliás, índices em dBase, ou mesmo em Clipper, eram super instáveis. Vira e mexe ficavam desatualizados e por isso todo programa que fazíamos naquela época colocávamos no menu uma opção de manutenção. Ensinávamos o operador do cliente rodar de vez em quando. Era a operação pra reindexar tudo do zero, pra garantir que a pesquisa ia funcionar. Quanto maior a tabela, mais demorado ficava reindexar. Felizmente bancos de dados modernos são bem mais estáveis pra atualizar os índices e fazem isso sem precisar de intervenção manual, na maior parte do tempo.</p>
<p>Mas não é só isso. Até agora só falei sobre adicionar novos registros nesse arquivo. Isso é fácil, é só ir apendando, concatenando os bytes do registro no fim do arquivo. Atualizar, também não é tão difícil. Só dar &ldquo;SEEK&rdquo; pra encontrar o registro pelo índice e dar &ldquo;REPLACE&rdquo; pra substituir com o dado novo por cima do campo desejado. Mas e pra apagar? O que acontece?</p>
<p>Pensa um Array em memória. É uma sequência contínua. Se você quiser deletar um elemento no meio, não tem outro jeito. Precisa criar um novo array e copiar todos os elementos de novo, pra não ficar buraco no meio. Por isso que em memória usamos outras estruturas como listas ligadas. Daí é só mudar o ponteiro do elemento anterior pra apontar pro elemento seguinte. Mas listas ligadas são super ineficientes comparados com arrays, são mais difíceis de navegar, porque não tem endereços contínuos, então não tem como só calcular o offset e ir direto pro elemento no fim, tem que ir navegando ponteiro a ponteiro. É muito lento.</p>
<p>Um arquivo DBF é parecido com um array: não tem como apagar um registro no meio. O que acontece é que cada registro tem um bit separado só pra dizer se é válido ou não. Quando manda apagar, ele só grava esse bit de zero pra um, daí quando tentamos dar SEEK ou navegar pra frente com SKIP, ele vai ignorar se esse bit estiver marcado como apagado. Isso torna a operação simples com um trade-off: o espaço no disco vai continuar sendo ocupado, o arquivo não diminui de tamanho.</p>
<p>Por isso que além de reindexação, que é regerar os arquivos de índice, na tal opção de manutenção, também tinha outro comando: &ldquo;PACK&rdquo;. O que o PACK vai fazer é a mesma coisa que com um array: gerar um novo arquivo DBF do zero e copiar todos os registros não apagados pra lá, e no final apagar o arquivo antigo. Esse é o único jeito de recuperar espaço do disco. Mas pra isso precisava ter quase o tamanho do arquivo original de espaço livre sobrando pra fazer a operação.</p>
<p>Isso existe até hoje em bancos de dados modernos. Quem já mexeu com SQLite3 ou Postgres deve ter esbarrado no comando &ldquo;VACUUM&rdquo;, tem até a opção de AUTO VACUUM pra ele fazer isso sozinho sem termos que enviar o comando manualmente, mas em essência é a mesma ideia do comando PACK de dBase: recuperar espaço em disco e desfragmentar os dados no disco pra ficar mais eficiente e ocupando menos espaço.</p>
<p>Falando em SQLite3, ele seria o sucessor espiritual do dBase e Clipper. É provavelmente o banco de dados relacional mais usado no mundo. Está em todo lugar. Se você tem dispositivos smart com Android da vida, os aplicativos rodando dentro certamente usam SQLite3. O roteador da sua casa, sua Smart TV, sua Alexa, seu smart watch, tudo tem SQLite3. Qualquer aplicativo que precise armazenar qualquer tipo de dados, normalmente usa SQLite3.</p>
<p>A razão disso é por ser extremamente pequeno, altamente eficiente, flexível e portável. O código fonte dele é em C super limpo, que pode ser facilmente portado pra rodar em qualquer sistema operacional, seja Linux, seja Mac e pra qualquer arquitetura, seja x86 no seu PC, seja ARM na sua SmartTV ou console de videogame. Cabe tudo num único arquivo de C com poucos page down e oferece uma linguagem compatível com SQL. É muito bem feito, um dos melhores e mais úteis projetos de código aberto de todos os tempos. Se você é estudante de C, vale a pena explorar o código fonte, não é tão difícil de entender.</p>
<p>Isso me leva ao episódio onde mostrei o que seria uma introdução de como fazer uma linguagem similar a SQL e eu usei justamente um trecho da gramática de SQL do SQLite3. Só que por baixo fiz ele acessar uma estrutura em memória de Javascript. Poderia ser um arquivo DBF como de dBase e Clipper que acabei de mostrar. A diferença é que em vez de comandos como APPEND, REPLACE, USE, SKIP que mostrei até agora, poderíamos oferecer uma versão com SELECT, INSERT ou UPDATE.</p>
<p>O formato DBF é usado até hoje em muitos lugares. Um Pandas de Python tem capacidade de carregar e manipular DBF. A linguagem R pra cálculo numérico também consegue entender DBF. Linguagens como Javascript, Java, C#, todos tem bibliotecas que conseguem carregar DBF. É um formato legado que ainda é usado, por ser super simples, por ter sistemas legados em indústrias antigas. Obviamente, ninguém deveria iniciar um novo sistema dependente desse formato, pra isso existem coisas mais modernas, como o próprio SQLite3.</p>
<p>SQLite3, como dBase ou Clipper, não depende de ter um servidor de rede pra servir os dados, nem clientes complicados. Ele abre o arquivo e você manipula diretamente. Por isso é tão pequeno e eficiente. Existem opções pra oferecer SQLite3 em rede, mas o foco dele sempre foi rodar localmente. Acho que exige menos de 1 megabyte de RAM pra rodar, talvez faixa de meio megabyte, o que é ridiculamente pequeno. Muitos programas de Clipper dos anos 90 precisava mais que isso.</p>
<p>dBase, Clipper, FoxPro é o que chamamos da família de linguagens xBase, que suportam o formato DBF. Existe uma versão mais moderna de Clipper de código aberto que é o Harbour e seu fork, o xHarbour. Não sei se alguém usa, mas serviria pra portar antigos programas xBase pra rodar em plataformas mais modernas além de adicionar funcionalidades modernas como orientação a objetos e capacidade de se conectar em bancos SQL modernos. De novo, não recomendo começar nenhum novo projeto neles. Qualquer Python da vida, conectando num SQLite3 ou Postgres vai ser melhor.</p>
<p>Tecnologias dos anos 80 eram limitados pelos poucos recursos que a gente tinha. Os processadores eram super lentos comparados com hoje. Estamos falando de milhares de vezes mais lento. Os programas precisavam ser super eficientes, porque qualquer operação fora do lugar roubava recursos escassos, seja de CPU, seja de RAM. Eles precisavam rodar em menos de 640 kilobytes de RAM, em 15 megahertz ou menos. Qualquer processador porcaria de hoje em dia tem 2 ou mais núcleos, cada um com pelo menos 1 gigahertz. Olha a diferença.</p>
<p>Eu mencionei que o sistema de arquivos FAT16 do MS-DOS da época só suportava arquivos de 8 letras mais 3 de extensão. Não tinha memória suficiente pra suportar estruturas de dados maiores que isso. Mais importante: não tinha processamento sobrando pra garantir a integridade dos dados. Coisas como calcular o checksum ou fazer hash dos arquivos. Pros anos 80 seria muito pesado. Por isso disquetes em FAT16 costumavam falhar, arquivos ficavam corrompidos, dados se perdiam.</p>
<p>Mesma coisa pro formato DBF. Era comum esses arquivos se corromperem. Backup era essencial. E backup naquela época era mandar fazer uma cópia num disquete no fim do dia. Toda lojinha da esquina, que tinha um programa em Clipper pra controlar estoque, fornecedores, contas a pagar e a receber e coisas assim, sabia que precisava fazer isso. Fiz alguns programas desse tipo no começo dos anos 90.</p>
<p>No SQLite3, o formato de arquivos reflete essa diferença. Pra começar não é um arquivo append only como DBF, onde vai simplesmente gravando um registro na frente do outro e navegando via offset de endereços. Ele adota uma estrutura de árvore B, uma B-Tree. Não só no índice mas no banco de dados em si. Aliás, no caso do SQLite3, tem um único arquivo que contém várias tabelas, em vez de cada tabela ser um arquivo separado.</p>
<p>Internamente não vai alocando registros e sim páginas. Páginas são como ranges, ou intervalos de endereços. Por exemplo, a página 1 pode ser do endereço 1 até 500, página 2 do endereço 501 até 1001. Páginas contém registros, ou linhas de uma tabela. As páginas são organizadas numa árvore B e os registros são nós folhas, leaf nodes, dentro das páginas.</p>
<p>Nos episódios de árvore, de sistemas de arquivos e de SQL eu falei sobre B-Trees e suas variantes como B+Trees, que sistemas de arquivos modernos como ZFS ou BTRFS utilizam. Tudo no seu sistema operacional é alguma variante de árvores, em particular árvores B, por isso falei que era importante saber como funcionam. No caso do SQLite3 tanto o banco de dados propriamente dito, quanto seus índices, são variantes de árvores B. Elas são muito eficientes. Vamos recapitular.</p>
<p>Primeiro de tudo, o B não é de binário. Árvore binária é um tipo de árvore mas não é b-tree. O B não tem uma definição definitiva mas pense como &ldquo;balanceada&rdquo;. Uma característica importante é se auto-balancear. Todos os nós folha tem a mesma profundidade. Isso significa que não importa qual chave tá procurando, leva mais ou menos o mesmo tempo pra acessar qualquer nó. Isso é importante porque o tempo de &ldquo;seek&rdquo; ou procura num HD mecânico custa tempo. Uma árvore B minimiza a quantidade de operações de procura.</p>
<p>Árvores B tem alto fan-out, ou seja, são feitas pra cada nó interno poder ter um número grande de nós filhos, que é a ordem da árvore. Isso ajuda armazenamento em disco porque permite uma grande porção dos dados ser escrito tudo numa única página. O jeito antigo do DBF de ir alocando um registro de cada vez no fim é bem ineficiente e gera muita fragmentação. Alto fan-out significa que muitas chaves podem ser acessadas de uma só vez numa única operação de procura em disco. É melhor pegar uma chave que não precisava junto do que ficar procurando aleatoriamente por todo o disco.</p>
<p>Operações de inserção e deleção são muito eficientes. O algoritmo que mantém a árvore balanceada, e eu mostro uma versão disso no video de árvore, é super eficiente. Quando uma nova chave é inserida ou apagada a árvore é re-balanceada, pra garantir a propriedade dos nós folha terem sempre a mesma profundidade. E é muito estável mesmo com a árvore crescendo muito. Um banco de dados de SQLite3, que é super pequeno, aguenta um arquivão que pode teoricamente atingir até 280 terabytes.</p>
<p>Operações que pegam vários registros de uma só vez, tipo um SELECT WHERE da vida ficam mais eficientes, pela propriedade de vários registros estarem juntos numa mesma página. Com poucas operações de disco dá pra pegar todas elas ordenadas de uma só vez. Então, coisas como listagens e montar relatórios, tende a ser muito mais eficiente do que num DBF antigo. Tudo que você pedir que seja ordenado e sequencial, tende a ser eficiente pelas propriedades de páginas que expliquei.</p>
<p>Eu sempre ouvi que ninguém deveria tentar fazer um banco de dados do zero sem ter visto como um de verdade funciona por baixo, e esse é um dos motivos: operações em disco, especialmente mecânico, custam caro. Hoje custam caro, mas nos anos 80 ou 90 eram bem mais rudimentares. Um HD moderno, como meu IronWolf de 12 terabytes vem embutido com 256 megabytes de memória RAM. Não tinha PC nos anos 90 com tudo isso de RAM. Sem cache, as operações são mais custosas, porque realmente precisa colocar a cabeça em cima do disco de verdade.</p>
<p>Aqui estou especulando porque realmente não me lembro se era esse o caso, mas por exemplo, eu reservaria espaço no começo do disco pra colocar coisas como metadados, índices e os dados em si começariam a ser gravados depois. Como se eu tivesse uma partição C:, mais perto do centro do disco físico, onde as operações são mais rápidas do que perto das bordas dos discos. Daí numa partição D: ficariam os dados em si.</p>
<p>Aliás, acho que é por isso que antigamente a gente particionava o HD, no C: ficava o sistema operacional, pra garantir que no boot o disco trabalhe menos e seja mais rápido, daí os dados e programas ficam no D:, mais longe do centro do disco. Mesma razão de hoje em dia deixarmos o boot num SSD ou NVME, que é muito mais rápido, mas sua biblioteca do Steam colocamos num HD mecânico separado. Sempre é um trade off entre mais performance ou mais espaço.</p>
<p>E não só SQLite3, claro, qualquer banco de dados moderno, como Postgres, Oracle, SQL Server, e até bancos não relacionais, como MongoDB, Firebase, Dynamo, Cassandra. E não só bancos de dados como sistemas de arquivos como ZFS ou BTRFS que já mencionei, todos usam alguma variante de árvore B, exatamente por todas essas propriedades. Tudo que lida com gerenciamento de dados, usa árvores B.</p>
<p>Porra Akita, mas um MongoDB não grava tudo em JSON? Eu mando JSON e ele me devolve JSON, portanto JSON deve ser o formato mais eficiente, não? Sério, se você for bem iniciante eu deixo isso passar, mas se algum programador pensa assim, realmente não entende nada de programação. JSON é um formato de consumo, não um formato de armazenamento. É um formato de tradução pra facilitar a vida mas é altamente ineficiente. Eu isso no episódio que falo do código do Twitter.</p>
<p>Primeiro, não é JSON, internamente os nós filhos são gravados em BSON que ele chama de Binary JSON, é um formato binário, como Protobufs do Google. Segundo, é internamente organizado numa B+Tree, ou árvore B+, onde os valores propriamente ditos são gravados direto nos nós folha no fim da árvore, diferente de uma árvore B pura que só tem os ponteiros.</p>
<p>O objetivo todo do episódio onde eu fiz um SQL em cima de um array de javascript, foi pra demonstrar que ter SQL não descreve nada de como é o banco de dados por baixo. Mesma coisa no MongoDB: a linguagem e formato de dados, como JSON, que se usa por cima, não diz nada sobre como é a arquitetura por baixo. Tanto que um Postgres, via SQL, pode devolver os dados em formato JSON se você quiser. É só uma tradução. JSON em si não significa nada pra nenhum banco de dados. Eles só precisam escolher exportar ou importar dados nesse formato. É só um formato, como XML ou CSV ou Excel.</p>
<p>Todo formato de dados de consumo como esse vai exigir um passo de importação pro formato interno binário de verdade, ou exportação pra esse formato. Então se você faz um &ldquo;SELECT *&rdquo;
da vida num banco, primeiro ele vai puxar todos os registros no formato binário dele. Isso vai ocupar um X de memória. Daí você escolhe que quer em formato JSON, então ele vai precisar de mais um tanto Y de memória, maior do que X, pra fazer essa tradução. É um processo de serialização e desserialização, e sempre vai exigir o dobro de memória pra ter essa conveniência.</p>
<p>Por isso que comunicação de um serviço com outro serviço, deveria ser sempre utilizando protocolos binários compatíveis entre eles, por exemplo, protobufs. Evitando ao máximo fazer tradução de dados, especialmente em formato texto. O formato de dados do SQLite3 não tem nenhuma relação com ele ter capacidade de receber comandos em SQL. Não existe um banco de dados &ldquo;SQL&rdquo;, existem bancos de dados que entendem a linguagem SQL, só isso. Internamente todos são implementados de formas diferentes pra determinados cenários e casos de uso.</p>
<p>Um SQLite3 é ótimo pra usar numa SmartTV, já um Postgres ou MongoDB da vida, nem tanto. Eles foram feitos pra rodar em servidores. Existem até projetos pra rodar SQLite3 em servidores, mas de novo, não foi feito pra isso. Use a ferramenta certa no lugar certo. Só porque você consegue arrancar um parafuso da parede com um alicate, não quer dizer que deveria, acha a porra da chave correta.</p>
<p>Internamente, todo banco de dados vai ter um formato de arquivos de armazenamento como um DBF, ou mais corretamente como o formato de um SQLite3, e formato de arquivos de índice. Todos vão ser alguma variante de árvore B ou árvore B+. Além disso temos mais coisas modernas, no caso do SQLite3 vai ter um outro arquivo com nome terminando em &ldquo;wal&rdquo;, de write ahead log. Isso é outra coisa que sistemas de arquivos como FAT não tinham, mas todo sistema moderno como NTFS ou ext4 tem: um journal.</p>
<p>Pra economizar recursos, aquele comando REPLACE que eu mostrei lá no começo com dBase, escreve por cima do registro antigo no arquivo DBF diretamente. Isso é péssimo. Porque digamos que estamos fazendo uma atualização em massa em todos os registros do arquivo. Colocando o prefixo 9 em todo telefone que é de São Paulo, lá em 2012. Agora digamos que no meio dessa atualização acabou a luz e o computador reboota. Fodeu.</p>
<p>Isso era comum antigamente e significava que muito provavelmente o arquivo ficou num estado corrompido e eu perdi algum dado. Hoje em dia não se faz mais isso. Existem diversas estratégias, mas o SQLite3 escolheu um log de escrita pra frente ou write ahead log. Ele primeiro escreve as modificações nesse arquivo separado de log, depois que termina, troca um pelo outro. Bem simplificado é como se eu tivesse um arquivo chamado &ldquo;contatos&rdquo; daí tenho uma cópia dele chamado &ldquo;contatos-wal&rdquo; e as atualizações eu faço nesse segundo arquivo. Se tudo correu bem até o fim, agora troco um arquivo pelo outro.</p>
<p>É um pouco mais avançado que isso, mas só pra ter na cabeça a idéia. Se acabar a luz e o segundo arquivo estragar, ainda tem o original intacto. Além disso, se precisar fazer pesquisas, posso pesquisar no original enquanto a transação não acaba no segundo arquivo, em vez de ficar travado tendo que esperar até todas as operações acabarem. Todo banco de dados tem algum mecanismo desse tipo, mais sofisticado pra ter o menor tempo de trava quanto possível e garantir que nunca nenhum dado vai ser corrompido.</p>
<p>É um tipo de mecanismo de COW ou Copy on Write, como o sistema de arquivos BTRFS também tem em Linux. Por isso que é bem incomum hoje em dia perder dados. Mesmo o Windows tem NTFS, que protege razoavelmente bem. Qualquer Linux mais vagabundo usa pelo menos ext4, que tem suporte a journaling. Pra perder dados hoje, você precisa estar usando um hardware muito do vagabundo, como um SD card de câmera.</p>
<p>SD card é um hardware de armazenamento que foi feito pra ter grande capacidade e ser super barato. Ele foi feito pra tirar fotos, gravar videos e logo em seguida transferir pra um computador. Não foi feito pra deixar lá guardado pra sempre. Não é confiável e podemos perder dados do nada, especialmente nos modelos mais baratos. SSDs baratos de Aliexpress, mesma coisa, mas nesse caso porque são fraudes mesmo, fabricados pra falhar. Cuidado com isso.</p>
<p>Outra característica que bancos relacionais trouxeram foi esse conceito de transação e ACID, de atomicidade na operações. Ou todas acabam com sucesso ou nenhuma acaba. De consistência, por exemplo, regras de unicidade, chaves estrangeiras, e tudo mais. Os dados precisam sempre seguir essas regras, sem exceção. Isolamento, ou seja, se um conjunto de transações roda em paralelo, o resultado tem que ser o mesmo se eles tivessem rodado sequencialmente. O banco não pode ficar num estado inconsistente. E durabilidade, se a transação diz que gravou o dado, podemos acreditar que realmente está fisicamente no disco.</p>
<p>Caso um erro aconteça, como a famosa falta de luz, o banco precisa voltar pro último estado consistente conhecido. Nunca pode continuar operando num estado inconsistente. Cada banco implementa isso de formas diferentes, mas no geral significa que podemos confiar nos dados num banco de dados relacional. Seja um MySQL ou Posgres ou SQL Server, no geral, implementam bem essas características. Mesmo o pequeno SQLite3 faz isso. Um dos jeitos mais toscos é simplesmente bloquear o arquivo todo pra cada transação, esperar completar com sucesso e só aí liberar pra outras operações. Mas claro, isso seria altamente ineficiente.</p>
<p>Mas era assim que bancos de dados xBase, DBF funcionavam em rede nos anos 90 quando começamos a usar placas de rede em MS-DOS e fazer pequenas redes usando ferramentas da antiga Novell ou o Windows 3.1 for Workgroups ou o venerado OS/2 da IBM. Na prática, um computador era elegido como um servidor de arquivos, esses arquivos DBF e NDX eram compartilhados em rede, e todos os outros computadores na rede enxergavam os mesmos arquivos.</p>
<p>Cada computador tinha a mesma versão do programa em dBase ou Clipper ou FoxPro instalado. Se algum tivesse uma versão mais antiga, era caos. A consistência, as regras, dependiam totalmente da versão do programa instalado em cada máquina. A atomicidade era mais ou menos garantida por um sistema de lock. Quando um programa quisesse inserir um registro novo, ele travava o acesso àquele arquivo. Se um segundo computador tentasse ler, recebia um erro que o arquivo tava ocupado, e precisava esperar.</p>
<p>Isso nem sempre funcionava. E se o arquivo DBF, sozinho numa única máquina tendia a se corromper, porque não existia nenhum sistema de proteçõ como journals, imagine em rede. Corromper arquivos era rotina. Experimenta esquecer de fazer backup no fim do dia, certeza que no dia seguinte ia dar pau e corromper alguma coisa. Sistemas de arquivos em rede como Novell era uma puta gambiarra feita em cima do DOS, com tecnologias super imaturas.</p>
<p>Hoje usamos protocolos de internet como TCP/IP, mas naquela época usávamos protocolos mais rudimentares como IPX/SPX e NetBIOS da Microsoft. Verdadeiras porcarias. E mesmo a tecnologia de redes em si, hoje usamos Ethernet de 1 gigabit pra cima, antigamente eram cabos coaxiais que nem de TV a cabo, que male male, faziam 10 megabits. Perdiam pacotes, corrompiam dados e assim vai.</p>
<p>Com a migração de MS-DOS pra Windows e da arquitetura de 16 bits pra 32 bits, ganhamos até 4 gigabytes de RAM pra trabalhar, sem precisar fazer gambiarras de memória em DOS como eu explico no episódio dos 640 kilobytes. E com isso, em vez de cada programa em cada máquina da rede, tentar acessar diretamente os arquivos de dados, eles passaram a conversar com um software servidor. Dessa forma, só um software tinha acesso direto aos arquivos e todos os outros, programas clientes, conversavam com esse servidor usando algum protocolo, com alguma linguagem, no caso o SQL.</p>
<p>Essa foi a era de programas feitos em Visual Basic ou Delphi, conversando com bancos de dados servidores mais modernos como SQL Server, que tinha T-SQL, ou Oracle, que tinha PL/SQL ou alguns mais de nicho como o Interbase da Borland, que era o padrão pra quem usava Delphi. Em vez de cada programa cliente ter que controlar a integridade dos arquivos, melhor terceirizar isso pra um servidor. Os clientes só falam &ldquo;ow, grava isso pra mim&rdquo; ou &ldquo;ow, pesquisa isso pra mim&rdquo; e o servidor se vira. Isso aumentou muito não só a escalabilidade mas também a estabilidade. Se um cliente manda uma operação não suportada, o servidor pode só recusar, em vez de tentar executar e corromper arquivos.</p>
<p>SQLite3, oficialmente não tem um servidor. Mas tem gente tentando fazer um sei lá porque. Tem um projeto chamado ValentinaDB que faz isso. Não vejo nenhuma vantagem. Se você quer um banco de dados SQL, formato cliente-servidor, use um de verdade, que é maduro, como MySQL ou Postgres. Use SQLite3 pra programas que precisam de dados localmente, como seu roteador de internet que já dei de exemplo.</p>
<p>Quando temos o formato de um servidor, temos algumas vantagens e algumas desvantagens. A primeira é que a velocidade vai depender da qualidade da rede. Tudo que roda localmente sempre é mais rápido. Em rede você precisa pegar o resultado, quebrar em pacotes, rotear esses pacotes, ir recebendo, processando um buffer, e só aí vai ter os dados.</p>
<p>Podemos escolher ir recebendo e já processando os dados localmente, ou podemos escolher esperar receber a resposta toda e só depois fazer alguma coisa. É a diferença de receber tudo de uma vez ou usar técnicas de streaming. E aí depende da sua pesquisa. Se for tosca e mal feita, e mandar tipo um &ldquo;SELECT *&rdquo; pra uma tabela de 10 gigabytes, sem usar streaming, boa sorte, vai ocupar toda a memória da sua máquina e mais o swap em disco.</p>
<p>Isso é o que iniciantes tem mais dificuldade de entender: os dados que pedem precisam ir pra algum lugar. Nada é de graça em computação. O trabalho principal de um programador é justamente administrar os recursos da máquina da maneira mais eficiente possível. Por exemplo, digamos que preciso montar um relatório em PDF de todas as vendas do mês. Vou fazer tipo um &ldquo;SELECT * FROM ORDERS WHERE created_at &gt; date() - 30&rdquo;. Essa é uma pesquisa rudimentar que filtra todas as ordens criadas nos últimos 30 dias. Faz de conta que vai devolver 500 ordens.</p>
<p>Digamos que cada uma dessas linhas consuma 500 bytes, só meio kilobyte. Se eu puxar tudo de uma vez do banco e colocar num array em memória, vai ocupar 250 megabytes. É bastante coisa. Daí abrimos uma biblioteca qualquer pra montar PDF e converter essas linhas em um documento PDF em memória. Pra simplificar o exemplo, digamos que cada linha no PDF também use 500 bytes. Significa que no fim da operação, vou estar consumindo 500 megabytes na memória, meio gigabyte, metade com os dados que vieram do banco e metade com o PDF.</p>
<p>Qual o jeito certo? Depende, talvez seja esse mesmo. Existem técnicas se quiser otimizar. Um dos jeitos é escolhendo uma biblioteca de PDF que suporte escrever direto pro disco em vez de acumular tudo em memória. Por exemplo, em Java tem a biblioteca PDFBox da Apache. Podemos ir salvando incrementalmente uma página de cada vez pro disco. Digamos que cada página tenha 20 ordens, então só vou precisar de aproximadamente 10 megabytes na memória por vez.</p>
<p>Além disso podemos ir recebendo uma linha de cada vez do banco, e montar uma linha no PDF e descartar essa memória, daí recebo a próxima linha e faço a mesma coisa, sem acumular tudo. Em resumo, significa que de memória de trabalho precisaria, no máximo, ter uns 10 megabytes pra montar uma página de PDF e mais meio megabyte por linha que recebo do servidor de banco de dados, um total de 10 mega e meio.</p>
<p>Aí não importa se o relatório vai ter 100 ordens ou 1000 ordens, sempre vou usar o máximo de 10 mega e meio de cada vez, e não 500 megabytes pra 500 ordens ou 1 gigabyte se for mês de natal e tiver 1000 ordens. Esse é seu trabalho como programador: encontrar o teto máximo que realmente precisa e limitar o uso de recursos. Só assim é possível escalar. O jeito amador é ir usando cada vez mais memória à medida que os dados vão aumentando de volume. Sempre pense em jeitos de usar um teto fixo de memória e processamento, independente de quantos dados vai processar.</p>
<p>Quando falamos que é importante entender SQL direito, primeiro é pra aprender a fazer queries ou pesquisas, SELECTs que devolvam a menor quantidade de dados quanto possível, porque cada dado extra que vier e não usarmos, vai desperdiçando memória e processamento. Mas também precisamos entender algumas noções como, se vamos escrever ou atualizar dados, é melhor mandar os comandos todos de uma só vez do que mandar um de cada vez, esperar um por um terminar e confirmar antes de mandar o próximo.</p>
<p>Aquele exemplo que falei de adicionar o número 9 na frente de todo número de telefone de São Paulo em 2012. Digamos que tenha 100 mil usuários na minha tabela. O jeito mais tosco possível é fazer um programa que conecta no banco e primeiro faz um &ldquo;SELECT *&rdquo; e devolve todo mundo pro seu programa. De novo, se cada registro ocupa meio kilobyte e você deu asterisco, mandou voltar todos os campos da tabela, são 50 mil megabytes, 50 gigabytes de dados. Estão entendendo porque isso de puxar tudo pra memória é ruim? É o jeito mais fácil de pensar primeiro, mas é o pior jeito.</p>
<p>Digamos que você tá num servidor parrudo na Amazon, ou num PC igual o meu com 64 gigabytes, e depois de um bom tempo,  carregou a tabela inteira em memória. Agora você faz um loop nessa lista e manda dar um UPDATE no servidor pra cada registro. Um UPDATE em uma transação separada, 100 mil vezes. São 100 mil updates, 100 mil alterações individuais de índice, 100 mil commits, e 100 mil esperas de retorno OK do servidor. Lá se vão mais algumas horas esperando.</p>
<p>Qual o jeito certo? Se não ficou óbvio, não precisa dar SELECT de nada. Só fazer um único comando de UPDATE. Algo como &ldquo;UPDATE SET TELEFONE = &ldquo;9&rdquo; + TELEFONE WHERE CITY = &lsquo;Sao Paulo&rsquo; AND bla bla&rdquo; . Um único comando que vai mexer em todos os milhares de registros tudo de uma só vez. Com uma única conexão de rede, um único commit, uma única espera. Não precisa puxar todo mundo pra memória, depois fazer update um a um separado. Entenda esse conceito. A melhor operação trás zero dados do servidor e opera tudo lá.</p>
<p>Do jeito tosco, o programa cliente, digamos sua aplicação web ou mobile, mandou individualmente um comando de update pra cada usuários, esperou a resposta e mandou o próximo, 100 mil vezes. Do jeito certo, seu programa só se conectou uma vez e só mandou um comando, e afetou os mesmos 100 mil registros. A diferença de tempo aqui vai ser na ordem de centenas ou milhares de vezes mais rápido e usando ordens de grandeza menos recursos de memória e processamento. Isso é entender SQL.</p>
<p>Programadores iniciantes aprendendo hoje não tem desculpa. O pessoal das antigas que fazia dBase, até consigo entender, porque naquele formato rudimentar de DBF, não tinha outra forma. Tinha que iniciar um loop &ldquo;DO WHILE&rdquo; fazer uma condição com &ldquo;IF&rdquo; e aí fazer &ldquo;REPLACE&rdquo; no registro atual, dar &ldquo;SKIP&rdquo; pra pular pro próximo e repetir 100 mil vezes. Não tinha um servidor que fazia isso sozinho pra gente.</p>
<p>Mas pelo menos naquela época estava tudo rodando localmente na mesma máquina, modificando diretamente o arquivo. É o que o servidor de SQL vai ter que fazer no lado dele. Mas um cliente de banco de dados não tem que fazer loop nenhum se comunicando com servidor registro a registro, quase nunca. Se você tem um loop pra processar dados vindos do servidor de banco de dados, provavelmente está errado. No mínimo é um red flag que precisa ser revisto. Pelo menos lembre disso como regra: se tem um loop, está errado, pense de novo.</p>
<p>Outra coisa que o formato antigo não permitia fazer e você tem que aprender como fazer no SQL são constraints. As tais regras de consistência. Por exemplo, um campo que nunca pode ter valor vazio. Ou um campo que é uma chave estrangeira, um campo numa tabela com o ID primário de outro registro em outra tabela. Chaves estrangeiras garantem que você só está escrevendo IDs que realmente existem. No formato DBF não existia isso, você como programador precisava garantir essas regras manualmente, no código, com um monte de IFs pra lá e pra cá. Em SQL, se tem um monte de IFs, provavelmente deveriam ser constraints.</p>
<p>No mundo moderno de cliente e servidor, significa que muitas vezes essas regras vão ser duplicadas, no cliente pra poder mostrar pro usuário que ele digitou dados inválidos, por exemplo, e no servidor, pra garantir que nas tabelas só tenha dados realmente corretos. Muita gente acha essa redundância ruim, mas eu acho o contrário. Não existe nada pior que dado inconsistente. É melhor checar duas vezes do que não checar. Checagens de consistência que só existem no front-end, e no lado do banco de dados aceita tudo e não checa nada, é a maior porcaria de todas as porcarias. Sempre esses bancos de dados estão em estado inconsistente e sempre vai dar problema.</p>
<p>Crie a tabela no schema com as constraints e validações todas, validação de tipos de dados, validação de tamanho, validação se pode ser vazio ou não, validação de chaves, tudo. Daí faça uma classe do lado cliente que reflete as tabelas usando um ORM, como Hibernate em Java ou um model em Sequelize no Javascript ou model de Django em Python, não importa, repita as mesmas validações. Daí faça um componente em React pro front-end.</p>
<p>Repita as mesmas validações. Puuuts, mas vai repetir 3 vezes? Sim, repita 3 vezes, e faça testes unitários pra cada uma delas e teste de integração que cheque todas ao mesmo tempo. Esse é o seu trabalho. É que nem médico achar que lavar mão toda hora antes da cirurgia é perda de tempo. &ldquo;Ah, de novo? Eu já lavei de manhã, não precisa de novo&rdquo;. Lave de novo.</p>
<p>Beleza, tem que estudar SQL. Não só saber sintaxe de SQL, mas o  uso de recursos e processamento no servidor. E como otimizar. Nenhum ORM, biblioteca ou framework vai fazer isso pra você. Na verdade, é muito fácil fazer o jeito errado assumindo que o framework vai cuidar de tudo sozinho. Muitos tutoriais, no intuito de serem didáticos, vão te ensinar o jeito menos eficiente. Porque o jeito mais eficiente costuma ser mais difícil de explicar e demonstrar.</p>
<p>E quando você testa com poucos registros, 10, 100, mil, não faz  nenhuma diferença. Só quando realmente coloca em produção que vai entender o impacto. Essa é a diferença de crianças e adultos: quem já teve a experiência de perder dados de cliente ou ficar tudo lento que a loja pára de funcionar. Só aí que entende &ldquo;ahhhh, eu tava entendendo errado&rdquo;. Pois é. Tente evitar ter que chegar nesse ponto. Nem todo mundo precisa enfiar o dedo na tomada pra saber que toma choque.</p>
<p>Num sistema grande de verdade, numa equipe com vários desenvolvedores, é difícil de checar se todo código foi bem feito ou não. E mesmo sendo experiente, na pressão alguém deixa passar um código que vai usar memória sem precisar ou que vai deixar tudo lento porque botou um loop num lugar que não deveria. Por isso que todo sistema web em produção precisa ser monitorado. Existem diversas ferramentas de monitoramento, seja usando os antigos Nagios ou Zabbix ou serviços mais modernos como Splunk, Datadog, AppOptics, AppDynamics, Dynatrace, Sentry.io, Instana, Azure Monitor e vários outros. Não tem desculpa não usar pelo menos um.</p>
<p>Honestamente eu não sei dizer qual é o melhor hoje em dia, mas na dúvida, sempre usei e continuo recomendando no mínimo instalar o New Relic RPM. Alguns deles são passivos, com os antigos Nagios ou Logtail, porque medem coisas fora da aplicação, como uso de CPU, memória, análise dos arquivos de log. Mas os melhores exigem que você instale uma biblioteca que age como um agente dentro da aplicação. Como tem acesso à memória da aplicação, dá pra fazer análises muito melhores.</p>
<p>Sem entrar em detalhes, podemos avaliar logo depois de um deployment em produção, e ver se uso de memória aumentou e fica constante, por exemplo, ou se continua lentamente subindo até uma hora crashear. Ou podemos monitorar quando uma campanha de marketing entra no ar e vem um volume maior de tráfego. O aumento do uso de recursos está proporcional? Mais importante, podemos avaliar quais partes do código costumam consumir mais recursos.</p>
<p>Com isso podemos focar em melhorias bem mais precisas só nas partes que realmente precisa. Em vez de ficar no achismo de mexer no código sem saber quais reais impactos está causando. Depois que uma aplicação entra em produção, toda manutenção deve ser feita ao redor de métricas. Sempre medir o antes e o depois. Se o objetivo for consumir menos infraestrutura, sempre atacar os principais ofensores segundo as métricas. Se você não consegue medir, certamente não consegue consertar.</p>
<p>No lado do banco de dados, nenhum consegue crescer infinitamente. Como disse, pro caso geral de empresas pequenas ou médias, leva muito tempo pra começar a entrar no território de Big Data. Dezenas ou centenas de terabytes de dados. Um banco de dados relacional tradicional costuma ter os seguintes problemas de escalabilidade:</p>
<p>Primeiro, as tabelas simplesmente estão grandes demais. E quando eu digo grandes, quero dizer centenas de gigabytes, ou terabytes. Por mais que você tenha índices, lembra o que mostrei no caso do DBF e seus índices NDX? O arquivo de índice vai crescendo também. Quanto mais dados for inserindo, maior vai ficando os seus índices também. E vai chegar uma hora que pesquisar índices tão pesados vai ser lento.</p>
<p>A primeira estratégia pra lidar com esse problema se chama &ldquo;arquivamento&rdquo;. O certo é ter ou um segundo servidor de banco de dados usado só pra pesquisas e relatórios, mas fora do acesso pra usuários. Digamos que você tenha um ecommerce. Se tiver sucesso e vender muito, sua tabela de ordens vai ter milhões de vendas. Mas pensa: pra que serve dados de ordens antigas? Ordens de usuários que cancelaram contas ou que não entram faz muito tempo? Mova pra um segundo banco de dados.</p>
<p>Assim você tem um banco de dados com dados que realmente seus usuários precisam, mas sem os dados que ninguém nunca acessa. Digamos que sua empresa é uma multi-nacional com dezenas de filiais, dezenas de milhares de funcionários e seu sistema controla tudo de RH: folha de pagamento, benefícios, férias, documentação, e tudo mais. Pra que tem dados de funcionários de 5 anos atrás? Arquive. Mova pra um segundo banco de dados, ou mova pra um backup frio mesmo, como fita ou pra um AWS Glacier da vida, que é tipo um S3 mais barato.</p>
<p>Em qualquer empresa média ou grande, com sistemas de controle internos, vai ter tabelas com dados antigos que não precisam estar lá. É uma prática que de tempos em tempos, todo ano, talvez todo semestre, se faça essa manutenção de limpeza: arquivar, mover dados mortos, pra outro lugar que se acessa menos. Não é apagar. É mover. Nunca é uma boa idéia apagar nada.</p>
<p>Nunca se sabe quando vai precisar, especialmente sua área jurídica. Mantenha dados no mínimo em backup, por no mínimo 5 anos, por causa da lei. A grande maioria das pequenas empresas peca em sequer ter um backup que funciona, quanto mais se preocupar com arquivamento. De novo, não é pra pequenos isso. Pequenos tem que se preocupar com o básico: ter um backup, pra começar.</p>
<p>Mas aí começamos a entrar no território de unicórnios. Uber, Netflix, iFood, MercadoLivre, e várias tech startups que geram toneladas de dados. Digamos que é um iFood da vida. Não só vai ter informações básicas como cadastro do usuário e tabela de pedidos simples, mas vai ter mais dados como quem foi o motoqueiro que fez cada entrega, que rotas usou, que contatos foram realizados durante o percurso, o usuário abriu chat, o que ele falou? É uma granularidade e volume muito maior de dados. Cada pequeno pedido gera megabytes de dados pra depois serem usados pra inteligência, métricas de como melhorar a experiência e tudo mais.</p>
<p>Empresas assim geram gigabytes de dados por dia. O problema não é só o volume de dados, mas a velocidade em que são gerados. Aqui é o território de Data Warehouses, Data Lakes e coisas assim. Em nenhuma dessas empresas vai ter um único servidor de banco de dados com tudo. Sempre vai ter múltiplos bancos de dados, e certamente de vários tipos diferentes pra cada caso de uso diferente.</p>
<p>Simplificando bastante. Do lado do usuários que usa o app, faz pedidos, acompanha a entrega e tudo mais, cada usuário vai gerar dezenas de requisições pros servidores de um iFood ou Uber da vida. As pesquisas sendo feitas precisam ser rápidas. Estamos falando de centenas de milhares de usuários acessando a plataforma ao mesmo tempo. Por mais parrudo que seja um servidor, nenhum aguentaria. Precisa distribuir essa carga em múltiplos servidores.</p>
<p>Quando se instala múltiplos servidores de aplicação, eles não podem todos acessar um único servidor de banco de dados. Iria gerar uma fila enorme e muita espera, muita latência entre requisições. O problema de bancos de dados relacionais como Posgres ou SQL Server é que foram originalmente feitos pra funcionar num único servidor parrudo. A gente escalava o serviço melhorando o hardware da máquina, colocando mais RAM ou colocando uma CPU maior. Eles não foram pensados pra funcionar em múltiplos servidores.</p>
<p>Hoje em dia fazemos um meio do caminho. O maior volume de operações costuma ser pesquisa. Como pesquisa de cardápio. Enquanto faz isso não está gravando nada, só pesquisando. Todos os serviços de bancos de dados SaaS modernos conseguem criar servidores réplica, cópia da original, automaticamente. Assim os servidores de aplicação web conseguem fazer a pesquisa em qualquer uma das réplicas, evitando que se crie gargalo só em um.</p>
<p>Mais do que isso, hoje em dia, um servidor de aplicação vai primeiro fazer a pesquisa num servidor de cache, como um Memcache ou Redis. Se usar uma AWS temos Amazon ElastiCache, que é basicamente Memcache ou Redis como serviço. A aplicação primeiro checa se tem o cardápio do restaurante no cache, se não tiver, aí pesquisa via SQL no banco de dados, grava no cache e serve de volta pro usuário. Muito da carga de pesquisa cai em cima de alguma solução de cache. É impossível criar serviços grandes como um iFood ou MercadoLivre sem ter um volume grande de cache.</p>
<p>Temos dois problemas com isso. O primeiro é que apesar de ser razoavelmente simples de criar servidores de réplica, é difícil de ter múltiplos servidores que aguentem escritas. Operações como INSERT, UPDATE ou DELETE acontecem num único servidor principal. Todas as outras réplicas costumam ser read-only, somente de leitura. Existe um pequeno delay, uma pequena latência, entre a escrita completar no servidor principal e as réplicas serem atualizadas, mas isso costuma valer a pena.</p>
<p>Todo ecommerce da vida, tem essa arquitetura de servidor de banco de dados mestre principal pra escritas, várias réplicas somente de leitura e muito cache, aliado à uma estratégia de jobs assíncronos, com Apache Kafka da vida. Estratégias que já expliquei no episódio do Ingresso.com, do Twitch e do Twitter, depois revejam. Mas mesmo assim, ainda tem casos especiais.</p>
<p>Digamos que temos um serviço de analytics, como um Google Analytics, capturando cada visualização, cada clique, toda operação que usuários estão fazendo em diversos apps, diversos sites, pra gerar monitoramento em tempo real, relatórios periódicos com informações de cada usuário.</p>
<p>Ou imagine que fazemos o software que captura informações vindas de Apple Watch ou Galaxy Watch. Informações de batimentos cardíacos, que cada usuário te manda 1 vez a cada 2 minutos. Apple e Samsung tem milhões de usuários, consegue imaginar esse volume de dados?</p>
<p>Ou imagine uma corretora de criptomoedas como uma Binance, com milhares de transações acontecendo a cada minuto, milhares de pessoas comprando e vendendo sem parar. Ou imagine um servidor de games com Call of Duty ou Fortnite, onde se captura informações de cada ação de cada jogador, cada movimento, cada tiro, cada pontuação. Jogadores do mundo inteiro.</p>
<p>Estamos falando de gigabytes, sendo gerados por minuto. É muito dado. E em cima desses dados, queremos gerar compilados, como o relatório da sua saúde na última hora, ou nas últimas 24 horas, ou do mês. Pra isso tem que ordenar, agrupar, processar e sumarizar gigabytes de dados recebidos por cada usuário. É muito pesado. E não por acaso essa discussão começou a surgir no fim dos anos 2000. Justamente quando redes sociais como Facebook, Twitter, estavam atingindo proporções que nenhuma outra plataforma nunca tinha atingido até aquele ponto. Sem contar o advento de Mobile e da popularização de games online.</p>
<p>Nessas circunstâncias, de fato, o esquema de bancos de dados relacionais atinge seus limites. As pesquisas ficam lentas pelo volume absurdo de dados. Pior, as escritas ficam absurdamente lentas, em parte por causa das garantias que estávamos acostumados a ter, como durabilidade. A idéia de que se mandamos o servidor gravar uma linha e ele diz &ldquo;ok&rdquo; significa que aquela linha garantidamente está no disco. Nesses cenários de Big Data, essa garantia se torna um problema.</p>
<p>Quando falamos em Big Data, precisamos lidar com premissas diferentes. Num banco, se sumir uma linha do seu extrato, é um grande problema. Literalmente a conta não vai fechar. Vai faltar um depósito. Vai faltar um pagamento. Dinheiro vai sumir. Pior, se uma linha for duplicada. Mesma coisa num ecommerce. Mesma coisa num sistema com da Receita Federal. Todos precisam de garantias na consistência e integridade dos dados. Por isso existe e sempre vai existir um lugar pra bancos de dados com características que um Oracle, SQL Server ou Postgres oferecem hoje.</p>
<p>Mas pra certos tipos de dados, como analytics, como dados de batimentos cardíacos, como dados de jogos online, mesmo se você perder ou duplicar uma linha dessas, qual o impacto? É baixo ou, no caso geral, nenhum impacto. Estando dentro de uma margem de erro, no fim do dia, você não tá interessado em cada clique individual no seu site, estamos interessados na quantidade geral por dia, por semana, por mês. Não estamos interessados no batimento exato de 5 minutos atrás, mas sim na média por hora, na média durante um exercício ou na média durante o sono. E assim por diante.</p>
<p>É por isso que a partir do começo dos anos 2010 começaram a surgir bancos de dados não relacionais, os NoSQL. Bancos de dados criados pra responder situações diferentes, em particular de Big Data, onde queremos lidar com séries de dados consolidados. Onde velocidade de escrita e baixa latência tem prioridade sobre consistência. Queremos poder escrever em múltiplos servidores ao mesmo tempo, mesmo que durante algum período de tempo, os dados fiquem ligeiramente inconsistentes.</p>
<p>O problema é como distribuir a carga, tanto de processamento pra pesquisa, quanto pra escrita, e como dividir os dados entre múltiplos servidores diferentes, pra permitir escalabilidade? Recapitulando: bancos de dados relacionais funcionam muito bem num único servidor, oferecendo as garantias ACID de atomicidade, consistência, isolamento e durabilidade que todos nós gostamos. Tem opções pra escalar via réplicas somente de leitura e, até certo ponto, replicação bi-direcional, que é sempre complicado. Mas pra situações de Big Data e volumes gigantes de escritas, começa a se tornar uma tarefa complicada, chata e com poucas opções.</p>
<p>Não tem como falar de NoSQL sem mencionar o famoso teorema CAP, proposto por Eric Brewer no ano 2000. Foi um divisor de águas no estudo de sistemas distribuídos e provavelmente você vai aprender no curso de ciências da computação, mas em resumo ele propõe que bancos de dados verdadeiramente distribuídos, ou seja, que rodam em múltiplos servidores, ou múltiplos nós de um cluster, no máximo satisfazem duas de três propriedades: consistência, availability ou disponibilidade e partition tolerance ou tolerância a partição.</p>
<p>Ou temos consistência e durabilidade, mas o sistema não tolera partições, ou seja, alguns nós do cluster podem ficar offline do nada. Isso não é legal porque não é impossível um servidor dar problema, ou mesmo termos que tirar um deles do ar pra manutenção. Então queremos consistência e tolerância a partição, mas aí temos baixa disponibilidade, ou vamos ter o ideal, disponibilidade e tolerância a partição, mas teremos baixa consistência, ou o que chamamos de &ldquo;consistência eventual&rdquo;, onde um servidor, por um ligeiro período de tempo, pode não ter os mesmos dados que os outros servidores.</p>
<p>CAP é um teorema e não uma teoria, ou seja, não é uma lei. Quando estudar vai encontrar que nem todo mundo concorda com todos os argumentos, em particular, porque os termos consistência, disponibilidade e tolerância a partição podem ser facilmente mal interpretados. Não tem uma definição muito exata pra cada coisa, e todo mundo sabe o que acontece quando termos ficam abertos à interpretação: ninguém se entende. E isso acontece com frequência, porque na minha cabeça eu quero falar de consistência mas você tá entendendo como integridade. E uma coisa é diferente da outra.</p>
<p>Daí tem o problema da natureza binária. É um dos poucos lugares que de fato não podemos levar ao pé da letra que só podemos escolher 2 propriedades das 3. No caso de termos disponibilidade e tolerância, não significa que não vamos ter nenhuma consistência. Depende se precisamos de consistência forte, onde é obrigatório que todas as réplicas sempre tenham exatamente o mesmo dado correto, ou se podemos ter consistência fraca, eventual, onde durante alguns segundos, os dados diferem entre os servidores. De novo, num banco, não podemos ter um saldo, recarregar o navegador e aparecer outro saldo, mas no exemplo dos batimentos cardíacos, se faltar um batimento durante 1 minuto, não vai ser um problema, não quer dizer que você teve uma parada cardíaca nem nada.</p>
<p>A outra coisa é tolerância a partição. De novo, pense num data center, onde um grande serviço pode ter um banco distribuído em múltiplos data centers, uma dúzia de servidores em Nova Iorque, outra dúzia de servidores em Londres, todos sincronizados, mas aí temos uma pane na internet entreeles. Um link submarino deu pane, caiu a fibra ótica. É raro acontecer, mas acontece. E o sistema tem que saber o que fazer quando acontecer, ou seja, ser tolerante a essa partição da rede.</p>
<p>O problema é que isso realmente é muito raro de acontecer. Nossa infraestrutura dos anos 2020 é mil vezes melhor que nos anos 2000, hardware, redes, infra em geral é muito mais estável. Então nossos sistemas não precisam ser tão agressivos na preparação pra uma situação tão rara. Talvez seja melhor ser otimizado considerando o caso que não vai acontecer, e colocar estratégias de degradação graciosa caso aconteça, como simplesmente uma página de &ldquo;aguarde alguns segundos&rdquo;. Mesmo se tiver pane na rede, hoje em dia, se recupera em poucos minutos. Antigamente poderia levar horas ou dias.</p>
<p>Essas são algumas das considerações ao se discutir o teorema CAP. Na prática vamos lidar com alguma estratégia de sharding e alguma estratégia de replicação de dados. Replicação é como acontece com um banco de dados relacional, onde podemos adicionar nós na rede, e eles vão receber uma cópia dos dados, daí cada servidor vai ficar idêntico ao outro, fornecendo redundância e possibilitando dividir a carga dos usuários pra mais servidores.</p>
<p>No caso de banco de dados relacional, por causa da característica de consistência forte, ficamos limitados na quantidade de servidores que podemos ter pra escrita. Num NoSQL, com consistência eventual, qualquer dos nós pode ser usado pra escrita, mas vai levar um tempo pra replicar os dados pros outros servidores. A consistência vai existir, mas vai ser eventual, ou seja, eventualmente vai estar tudo consistente.</p>
<p>Na real, bancos de dados NoSQL como Cassandra ou Dynamo tem como configurar que nível de consistência queremos ter. Mais forte ou mais fraco. Podemos configurar isso dependendo das necessidades. Uma coisa é um pequeno cluster de 3 servidores de Cassandra tudo no mesmo data center, na mesma rede. Outra coisa são múltiplos clusters de dúzias de servidores em vários de data center diferentes, como é a operação do tamanho de uma Netflix.</p>
<p>Bancos como Cassandra ou Dynamo foram feitos mais pra situações onde precisamos funcionar em múltiplos data centers. Dividido em regiões geográficas diferentes. Um no Japão, outro na Europa, outra na América e todos os servidores de todos os clusters sincronizando entre si. Não é uma situação que vamos encontrar todo dia.</p>
<p>Pra maioria das empresas médias, em vez de lidar com uma infraestrutura dessas do zero, o mais prático seria usar um DynamoDB da AWS. Como tudo da AWS, eles gerenciam a maior parte dessa complexidade e podemos só usar. Claro que, por causa disso, o custo por transação é bem mais caro. Mas é esse o trade-off que precisa ser estudado. Ou vamos ter que investir em contratar pessoal especializado em infraestrutura, pra ficar 24 por 7 lidando com hardware, bare metal, num data center.</p>
<p>Ambos Cassandra e Dynamo oferecem capacidades de particionamento e replicação. MongoDB também tem isso, mas ele chama de sharding e replica sets. O que buscamos é a capacidade de conseguir particionar os dados em múltiplos servidores de forma balanceada, e de ter replicação entre alguns desses servidores pra ter redundância. Se um deles tiver uma pane e cair, tem outra cópia idêntica pra continuar servindo os mesmos dados. É muito complexo pra tentar detalhar em um video, mas eu queria que vocês pelo menos soubessem que isso existe pra saber onde procurar depois.</p>
<p>Pra agora é interessante conhecer um último conceito: IDs. Voltando pro exemplo lá do começo, a tabela de contatos feito em DBF com dBase. Lembram que falei que mesmo se eu fizer um índice de nome, precisaria procurar pelo nome completo pra achar? Hoje em dia, em qualquer banco de dados, todos oferecem alguma forma de pesquisar parcialmente ou palavras que soam parecido. É o que um Elasticsearch, Solr ou os recursos de full-text search de um Postgres e outros bancos de dados oferecem. É um índice diferente pra esse tipo de pesquisa em particular. Expliquei um isso no episódio do Twitter, vejam lá depois.</p>
<p>Antigamente, nos anos 80, não tínhamos o poder de processamento pra fazer coisas desse tipo. Em vez de tentar lembrar se &ldquo;Brian Russell&rdquo; tem dois &ldquo;S&rdquo; ou dois &ldquo;L&rdquo;, em muitos casos era mais fácil lembrar o código dele, o ID. Por isso toda tabela a gente se acostumou a colocar esse código. Assim como todo pedido de compra tem um código. Assim como todo indivíduo nesse país é um CPF. Toda linha numa tabela é um código numérico, que usamos como uma chave primária ou, no caso de bancos relacionais, usamos pra fazer uma linha em uma tabela referenciar uma linha em outra tabela, e chamamos esses IDs de chaves estrangeiras, porque ela é estrangeira à tabela.</p>
<p>O jeito considerado errado hoje, principalmente em códigos que vamos divulgar pros usuários, é fazer a chave primária ser um número auto-incremental. Ou seja, começando de 1 e incrementando, próxima linha sendo 2, linha seguinte sendo 3 e assim por diante. Um erro de segurança que foi muito comum antigamente era ter uma aplicação web onde a URL mostra esse código, por exemplo, digamos que tivesse <code>mercadolivre.com/users/1</code> que é a URL pra página com os dados cadastrais de um usuário.</p>
<p>Num sistema bem feito, quando o usuário faz login, eu guardo o ID dele na sessão. Quando ele navega pra outras páginas privadas, checamos com o ID na sessão antes de mostrar a informação. Se ele tentar acessar a URL que seria de outro usuário, conseguimos invalidar o pedido e devolver um erro 403 não permitido. Esse seria o certo, mas num sistema grande, alguém pode deixar passar um bug e não checar.</p>
<p>Daí alguém de fora, percebendo que estão usando IDs auto incrementais, e o dele é ID 1000, óbvio que significa que tem usuários abaixo de 1000 e alguns acima, e ele vai começar a tentar forçar IDs até alguma página abrir na conta de outro usuário. O certo é não ter esse bug. Mas também não é legal oferecer informação de graça pra estranhos, como a existência de outros usuários com IDs fáceis de achar.</p>
<p>O ideal é usar um número aleatório gigante. Um campo de inteiro de 32 bits pode conter números de 0 até mais de 4 bilhões. Mas se for um inteiro de 64 bits, estamos na ordem de 18 milhões de trilhões. É muito número. Por isso não precisamos usar números sequenciais auto incrementais, use números aleatórios. Vai ser perto de impossível tentar adivinhar o ID de alguém.</p>
<p>Pra isso usamos funções geradores de números como UUIDs. Nem precisamos usar o número inteiro que ele gera, basta truncar e pegar um pedaço e as chances de colisão, ou seja, de escolher um número que já existe na sua tabela, vai ser hiper baixo. E como o certo é criar campos de chave primária com constraint de unicidade, ele não vai deixar inserir um ID duplicado. Existem dezenas de estratégias pra gerar números aleatórios pra IDs, vale a pena parar pra pesquisar e evitar números sequenciais. Chamamos isso de chaves surrogadas. Não é nenhuma lei, mas é uma boa prática. Pra tabelas internas, cujos IDs você nunca vai mostrar em público, aí tanto faz.</p>
<p>Expliquei isso porque em sistemas distribuídos, temos o problema de balancear dados entre diferentes servidores. Quando replicamos dados, estamos fazendo uma cópia dos dados de um servidor pra outro, pra servir de redundância, mas num sistema grande distribuído, queremos dividir o processamento, e isso exige que alguns servidores vão ter dados que outros servidores não vão ter. Deixa eu dar um exemplo besta.</p>
<p>Digamos que trabalho na Receita Federal. Eca. Fazer um único banco de dados com todos os impostos de renda, de todas as pessoas e empresas, num único banco de dados, é bem pesado. Ainda mais se tiver a informação histórica de todo mundo das últimas décadas. Um jeito ingênuo de lidar com isso é manualmente dividir os dados. Por exemplo, posso fazer um servidor com dados da região Sudeste, outro servidor só pra região Sul, outro só pra região Nordeste e assim por diante.</p>
<p>Problema que um servidor vai continuar tendo mais volume de dados que outros. Não vai ser balanceado. Rapidamente o da região Sudeste vai precisar dividir. Daí teríamos que fazer um servidor só pra São Paulo separado. Mas mesmo assim, o volume só de São Paulo sempre vai ser muito maior que tudo, vamos dividir pelo menos a cidade de São Paulo num único servidor sozinho. Tá vendo o trampo? Ficar tentando dividir os dados assim manualmente vai ser um parto. Pior ainda pro software. Teríamos que fazer algo como &ldquo;if sao paulo, procura no servidor X&rdquo;, ou &ldquo;if regiao nordeste, procura no servidor Y&rdquo;. Isso rapidamente fica caótico.</p>
<p>Existe uma solução melhor. Antes, vamos ver um outro jeito ingênuo de balanceamento. Alguém poderia pensar &ldquo;hum, e se não olharmos pros dados, esquece isso de região ou estado, vamos olhar só pro ID&rdquo;. Nesse caso digamos que os IDs são numéricos e sequenciais, exatamente como eu disse pra não fazer. Daí temos 3 servidores que vamos chamar de A, B e C e 12 itens na tabela, com ID de 1 a 12, pra simplificar. Como eu divido esses 12 itens entre os 3 servidores?</p>
<p>Fácil, que tal usarmos o módulo do ID? Lembra a idéia de módulo? No caso faríamos módulo 3. Módulo é quando você pega o restante da divisão de um número por outro. Por exemplo. 1 módulo de 3 seria 1, 2 módulo de 3 seria 2, 3 módulo de 3 é 0, por que 3 dividido por 3 dá um e sobra zero. Interessante é acima disso. 4 dividido por 3 é 1 e sobra 1, então o módulo é 1.</p>
<p>5 dividido por 3 é 1 e sobra 2, então o módulo é 2, mas 6 dividido por 3 é exato 2 e sobra zero, o módulo é zero. Qualquer número módulo 3 vai ser um número de 0 a 2. Sempre vai rotacionando. Estabelecendo arbitrariamente que o servidor A é zero, B é 1 e C é 2, sabemos como dividir cada uma das 12 linhas em cada um dos 3 servidores, sacaram?</p>
<p>Então no servidor A teremos os itens com IDs, 1, 4, 7 e 10. No servidor B teremos os itens com IDs 2, 5, 8 e 11 e no servidor C teremos os itens com IDs 3, 6, 9 e 12, todos os múltiplos de 3. E toda vez que quisermos inserir uma nova linha, fazemos o módulo do ID e vemos pra qual servidor mandar. Isso garante que todos  vão estar balanceados, ou seja, todos vão ter exatamente o mesmo número de linhas. Resolvido então?</p>
<p>Mas é a Receita Federal, eles crescem infinitamente. Muito em breve só 3 servidores não vai dar conta. Precisamos adicionar mais um novo servidor. Agora fodeu. A conta passa de módulo 3 pra módulo 4. Ou seja, precisamos rebalancear todos os dados em todos os servidores. Na nova configuração, cada ID vai ficar num servidor diferente do que tava antes. Essa estratégia funciona pra balancear se nunca mexermos na quantidade de servidores, mas  não escala se precisarmos adicionar novos, porque precisa mover todos os dados de lugar pra rebalancear.</p>
<p>Por isso foi inventado o algoritmo de Consistent Hashing em computação distribuída. É um esquema de organização de IDs que permite adicionar ou remover servidores sem precisar mover todos os dados de lugar toda vez. Ele consegue isso organizando todos os IDs num anel, um círculo de hashes. Pra entender hashes recomendo que assistam o primeiro episódio de criptografia.</p>
<p>Em resumo bem resumido, hash é uma função que pega qualquer informação, por exemplo uma string, e devolve um número de tamanho fixo. Mas é uma função não reversível, ou seja, a partir do número não tem como encontrar a string original, porque diversas strings podem chegar no mesmo número. A função só garante que se você passar a mesma string, vai chegar no mesmo número.</p>
<p>Dependendo da função ela devolve um número até um determinado tamanho, por exemplo, um SHA512, vai devolver um número de até 512 bits. O número máximo de 512 bits é um absurdo maior que a quantidade de átomos no universo visível. Obviamente é exagerado demais pra um campo de ID. Um Cassandra usa outra função de hash não criptográfico, ou seja, que não deve ser usado pra criptografia, mas é bom o suficiente pra IDs, o Murmur3 que devolve hashes de 64 bits, onde o maior número é da ordem de milhões de trilhões, que ainda é um número gigante, mas mais gerenciável.</p>
<p>Enfim, o principal é que no sistema de consistent hashing consideramos um anel que vai de 0 até esse número máximo de 64 bits. Daí fazemos o hash dos IDs dos servidores. Digamos que temos 4 servidores. Eles se distribuem pelo anel. O lance de uma função de hash é que ela tende a ter uma distribuição uniforme, o oposto de sequencial. Podemos gerar o hash dos dados do registro, com a combinação de diversos dados como o endereço IP do usuário mais o horário, ou qualquer coisa assim.</p>
<p>Por exemplo, imagine um anel onde cada ponto desse anel começa em 0 no topo e tem todos os números até 2 elevado a 64, no final do anel, encostado no zero, pra dar uma volta. Entenderam, todos os IDs possíveis estão no anel. Daí aplicamos a função de hash pros nomes ou endereços IP de cada servidor, e aí eles caem arbitrariamente pelo anel, só pro exemplo, o servidor A lá em cima, depois o B mais pra direita lá embaixo e o C mais pra esquerda embaixo.</p>
<p>Vamos inserir alguns dados, digamos que é o hash do nome do registro. O primeiro registro tem o nome de John e digamos que o hash gerado caia entre o Nó A e o Nó B. A regra é pra ele escolher o servidor com o hash maior ou igual ao do ID olhando na direção de relógio, da esquerda pra direita. Nesse caso vai ser o Nó B. O próximo registro tem nome de Fred e o hash por acaso tem ponto aqui entre o B e o C, então ele cai no Nó C. E o último registro com nome de Susan cai aqui entre o C e o A, então vai no A. Sempre na direção de relógio.</p>
<p>Beleza e qual a vantagem disso? Bom, vamos voltar pro cenário onde queremos adicionar um novo servidor, o nó D e o hash dele vai aqui entre o C e o A, por acaso. Mas ele cai depois da Susan. O que acontece é que todos os dados com IDs depois do C que estavam caindo no Nó A, agora precisam ser movidos pro Nó D. É assim que rebalanceia. Mas prestem atenção, no exemplo de usar a função simples de módulo, precisaria mover todos os dados de todos os servidores quase. Aqui, os dados dos servidores B e C não precisam ser mexidos, só os do A que precisam ser divididos. Os registros com IDs maiores que D e menores que A, continuam no A.</p>
<p>Digamos que o servidor A tenha uma pane qualquer e precise ser removido. E agora? Todo registro que antes cairia no A, pela regra de olhar o próximo servidor em sentido de relógio no anel, então vai passar a cair no B. Só isso. E nenhum dado de nenhum outro servidor precisa ser mexido. Falando simplificadamente é assim que garantimos ter a menor quantidade possível de dados sendo movidos quando a infraestrutura muda. A idéia é mover a menor quantidade de dados quanto possível quando precisamos adicionar novos servidores.</p>
<p>Tem várias nuances nessa estratégia. Por exemplo, quando da remoção de um Nó, pode acontecer de outro Nó acabar acumulando dados demais. Pra evitar isso, cada Nó pode ter múltiplos hashes aleatórios que o colocariam em múltiplas posições no anel, daí os dados poderiam ser melhor rebalanceados. Se ficou interessado, depois leia a documentação a respeito em sites como do Cassandra ou no paper do DynamoDB da Amazon.</p>
<p>Dizendo assim parece fácil, mas em DynamoDB por exemplo, você tem que entender o conceito de chave de partição e chave opcional de ordenamento. Dados com a mesma chave de partição caem na mesma partição, obviamente. O problema é se quisermos fazer uma pesquisa que precisaria buscar dados em múltiplas partições. Pra isso tem comandos com o &ldquo;Scan&rdquo;, mas isso é uma operação bem mais custosa do que pesquisar dentro de uma mesma partição.</p>
<p>Portanto a estratégia de partição precisa estar muito bem definida no começo, senão vai ter obstáculos de não conseguir fazer pesquisas rápidas quando precisar. As coisas não são automáticas, trocamos conveniência por performance. Num banco relacional, onde normalmente fica tudo junto, é só rodar um comando SQL e temos o que queremos rápido. Num sistema distribuído as coisas estão, duh, distribuídas, e precisamos levar isso em consideração.</p>
<p>No DynamoDB, que é oferecido com um serviço da AWS, precisamos entender bem o comportamento da nossa aplicação, não só pra não dificultar operações depois, mas porque a cobrança também não é simples. Não se paga por gigabyte armazenado, por exemplo. Precisa calcular a quantidade de escritas por segundo. A quantidade de escritas por segundo durante um pico. A duração de cada operação de leitura, porcentagem de capacidade de leitura que quer deixar reservado, o nível de consistência das leituras, quantas quer que sejam fortemente consistemente, quantas fracamente consistentes.</p>
<p>Por exemplo, na calculadora online deles, o preço por gigabyte, na região US east, é de meros 25 centavos por mês. Sucesso então? Não, com 10 escritas não transacionais, 100 escritas por segundo em horário fora de pico, 400 escritas por segundo em horário de pico, 72 horas operando em pico durante o mês, com capacidade reservada de escrita pra um ano, pode somar mais 23 dólares por mês e 150 dólares que precisa pagar a vista logo no começo pra reserva.</p>
<p>Daí continuando com os valores padrão pra leitura, só pra não entediar, mais 3 dólares por mês e mais 30 dólares upfront à vista. Então, por mês nessa configuração vai pagar uns 26 dólares, mais 180 dólares à vista na entrada. E essa configuração é a mais basiquinha, pra um app pequeno.</p>
<p>Na categoria de bancos de dados NoSQL oferecidos como serviço, ou seja, que não precisamos saber como configurar e dar manutenção na infraestrutura, tem diversas outras opções, como MongoDB Atlas ou Google Firebase. Firebase é bem popular pra aplicações pequenas, porque até 1 gigabyte de mensagens, até 1 gigabyte de armazenamento, 5 gigabytes de cloud storage, o custo é gratuito. Já um MongoDB Atlas você paga até que &ldquo;barato&rdquo; a 10 centavos por milhão de operações de leitura, ou o recomendado 57 dólares por mês pra solução dedicada de 10 giga até 4 terabytes de armazenamento.</p>
<p>Não é simples calcular o real valor que vai pagar, tem dezenas de parâmetros pra considerar, como quantidade de invocação de funções e gigabytes por segundo ou minutos de cloud build ou gigabytes transferidos pela rede e mais. O preço vai variar de acordo com o sucesso ou fracasso da sua aplicação. Quanto mais &ldquo;barato&rdquo; ficar, mais fracassado é seu app, quanto mais sucesso fizer, mais caro vai ficar. No começo parece uma grande vantagem, mas justo quando fizer sucesso e achar que vai ganhar bastante, é também quando eles vão te cobrar mais caro. Essa é a faca de dois gumes de serviços que você não gerencia. Uma hora a conta chega.</p>
<p>Isso sem contar o custo do desenvolvimento e manutenção da sua aplicação. Quanto mais exótico for um banco de dados, mais difícil vai ser encontrar profissionais qualificados ou treinar em casa. Só porque um banco é exótico, não significa que resolve seus problemas. Em muitos casos, alguém pode escolher o Google Firebase pelo hype mas na realidade o desenvolvimento teria ficado mais barato se tivesse escolhido, digamos um MongoDB Atlas.</p>
<p>Ambos Firebase e MongoDB tem a flexibilidade de lidar com documentos estilo JSON, sem precisar se prender à estrutura mais rígida de um schema de banco de dados relacional. Mas só dizer JSON não significa que são iguais. MongoDB permite lidar com documentos mais complexos, embedded arrays, sub-documentos. O modelo do Firebase é bem mais simples.</p>
<p>Mas o Firebase tem atualizações em tempo real, que é importante pra quem desenvolve aplicações mobile. Mudanças no banco podem ser empurradas como notificações direto pros usuários em tempo real. MongoDB não tem isso nativo. Dá pra colocar mas dá mais trabalho, portanto é mais custo de desenvolvimento.</p>
<p>Por outro lado, se quisermos fazer pesquisas mais complexas, MongoDB tem uma linguagem de queries mais avançada, incluindo coisas como joins e índices secundários. Firebase tem capacidades de query, mas limitadas. Da mesma forma, transações. Todo mundo foge de banco relacional, mas todos adoram ter transações e dados fortemente consistentes. MongoDB tem mais suporte a transações do que Firebase. De novo, se quiser algo semelhante a transações no Firebase, o custo de desenvolvimento sobe. E se transações é realmente importante, talvez um banco relacional como AWS RDS tivesse sido melhor, em primeiro lugar.</p>
<p>Mas a vantagem de serem &ldquo;as a service&rdquo; quer dizer que tanto MongoDB Atlas quanto o Filestore do Firebase tem escalabilidade e replicação automáticas em múltiplas regiões pra alta disponibilidade. Ou seja, se throughput de operações e garantir que nunca fique fora do ar seja primordial, ambos podem ajudar com isso. Mas você vai pagar por isso, e não vai ser barato. Como expliquei, o modelo de precificação é complexo. Precisa seriamente gastar tempo pesquisando e avaliando muito bem o que acontece não só agora, que sua aplicação tá no começo, mas daqui seis meses se fizer sucesso.</p>
<p>Não tem como eu explicar todos os bancos de dados oferecidos como serviços hoje. A Amazon é a mais famosa. Todo mundo escolhe o AWS RDS pra soluções de MySQL ou Postgres como serviço, mas o Google também tem o Cloud SQL como concorrente. Vai depende de precificação e funcionalidades que pra você podem ser importantes. Além deles tem o Azure SQL Database e acho que até a Oracle oferece o banco de dados SQL deles como serviço.</p>
<p>Pra complicar a vida, a própria Amazon oferece produtos similares na mesma plataforma, como o Apache Aurora que parece com RDS. Aurora é um produto superior de banco de dados MariaDB, Postgres, SQL Server, Oracle, tem maior garantia de disponibilidade, dobro da performance no mesmo perfil de hardware, mais facilidade de escalabilidade, opções de backup, replicação, porém, obviamente custa mais caro. Ou seja, tem SQL pra todos as necessidades e capacidade de pagamento.</p>
<p>Digamos que você é um apologista de NoSQL, não quer mesmo SQL. Daí tem o MongoDB Atlas e Google Firestore que já falei, mas temos mais opções. Pro mundo enterprise, a SAP oferece o HEC que é o HANA Enterprise Cloud. Tem a ScyllaDB que oferece uma versão de Cassandra deles e tem o AstraDB da DataStax que também é Cassandra como serviço. Isso sem contar o Heroku que oferece Postgres como serviço, Redis como serviço. E em termos de cache, Memcache e Redis, a AWS tem o Elasticache como já falei. Tem serviços pra todo mundo, é só conseguir pagar.</p>
<p>Mas, pra você que é iniciante em programação, não se deixe enganar por hype de NoSQL. A gente vem discutindo esse assunto desde pelo menos 2008. E eu posso te garantir algumas coisas: não existe consenso, pelo simples fato que cada banco de dados tem pontos fortes e pontos fracos, cada um serve pra situações diferentes. Pra aplicações bem pequenas, meio que tanto faz se vai escolher um banco relacional SQL como um MariaDB ou Postgres ou se vai direto pra um FireStore da vida. Você tem poucos dados, tem poucos problemas pra lidar.</p>
<p>A primeira versão de qualquer coisa que fizer sempre vai ser isso: versão 1.0. Só quando tiver usuários de verdade, dados de verdade, receita e faturamento de verdade, só aí vai realmente entender o que precisa. E se ficar limitado a usar só uma ferramenta pra tudo, em breve vai ter tanto problema, e vai ignorar que existem já soluções pra cada problema, que corre o risco de fracassar por ignorância.</p>
<p>Na dúvida, a solução mais comum é a mesma: AWS RDS com Postgres ou MariaDB, que é o que a grande maioria dos frameworks web usam, como Ruby on Rails. Django, Laravel, Spring. Daí uma solução de cache usando Redis ou Memcache, e pra isso tem AWS ElastiCache. E finalmente jobs assíncronos, seja com Kafka, seja com AWS SQS. Isso resolve 90% dos seus problemas. Se precisar de alguma coisa de documentos schemaless em JSON, um Postgres já suporta isso nativamente. Quer só fazer um protótipo, vai de Firebase.</p>
<p>Se por acaso estiver na rara categoria de Big Data, significa que você não é uma micro empresa, ou um freelancer. Já é uma empresa média. Aí é outra categoria, já vai olhar pra Aurora, DataStax, e soluções pra Data Science como Apache Spark, Hadoop e muita coisa customizada. Vai ter profissionais experientes de infraestrutura de devops. Essa categoria não é pra amadores. Por isso, se for iniciante, não se preocupe com isso tão cedo. Você não é o Netflix.</p>
<p>No fim do dia, entender bancos de dados, sejam relacionais, sejam NoSQL, significa entender o problema que se quer resolver primeiro. Daí aplicar fundamentos de ciências da computação. Árvores, estruturas de dados, redes, sistemas de arquivos, sistemas operacionais, fundamentos de criptografia. Ficar acompanhando discussões inúteis em fios de Twitter da bolha dev, só vai te confundir, nunca tem nada de útil e você vai ser mais um maria-vai-com-as-outras. Se atenha a pesquisar documentação oficial de cada coisa.</p>
<p>A grande maioria dos programadores iniciantes hoje tem o mesmo problema de ignorar fundamentos, ignorar os problemas que se quer resolver, e escolher baseado em likes. Influencer nenhum sabe o seu cenário particular. Óbvio que vai dar ruim. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal, e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>bancos-de-dados</category><category>sistemas-distribuidos</category><category>cloud</category><category>akitando</category></item><item><title>[Akitando] #112 - Subindo Aplicações Web em Produção | Aprendendo HEROKU</title><link>https://akitaonrails.github.io/2022/01/10/akitando-112-subindo-aplicacoes-web-em-producao-aprendendo-heroku/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/01/10/akitando-112-subindo-aplicacoes-web-em-producao-aprendendo-heroku/</guid><pubDate>Mon, 10 Jan 2022 12:15:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/TLRW_xTnQwY"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente vou mostrar o que é o Heroku e como é o fluxo de trabalho mínimo de um projeto web ideal. Se você já usa Heroku, aproveite pra compartilhar o video com conhecidos que ainda não usam. Se nunca viu Heroku, prepare-se pra ficar surpreso!&lt;/p&gt;
&lt;h2&gt;Conteúdo&lt;span class="hx:absolute hx:-mt-20" id="conteúdo"&gt;&lt;/span&gt;
&lt;a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;00:41 - Mega Sena da Virada&lt;/li&gt;
&lt;li&gt;02:33 - Concorrência&lt;/li&gt;
&lt;li&gt;04:11 - Intro a Heroku&lt;/li&gt;
&lt;li&gt;04:46 - Deploy antigo&lt;/li&gt;
&lt;li&gt;06:24 - Iniciando Tutorial Heroku&lt;/li&gt;
&lt;li&gt;06:49 - Setup: Git é Básico!&lt;/li&gt;
&lt;li&gt;07:22 - Heroku Login&lt;/li&gt;
&lt;li&gt;08:18 - Primeiro Deploy&lt;/li&gt;
&lt;li&gt;10:03 - Buildpack? Dockerfile?&lt;/li&gt;
&lt;li&gt;10:35 - Entendendo Dynos e Containers&lt;/li&gt;
&lt;li&gt;11:30 - Escalando Dynos&lt;/li&gt;
&lt;li&gt;13:16 - Entendendo Load Balancer&lt;/li&gt;
&lt;li&gt;16:41 - Vendo logs dos Dynos&lt;/li&gt;
&lt;li&gt;17:22 - Entendendo Procfiles&lt;/li&gt;
&lt;li&gt;18:58 - Mais sobre escalar dynos&lt;/li&gt;
&lt;li&gt;20:21 - Entendendo Gerenciamento de Dependências&lt;/li&gt;
&lt;li&gt;24:37 - Adicionando nova dependência&lt;/li&gt;
&lt;li&gt;26:40 - Adicionando Addons&lt;/li&gt;
&lt;li&gt;28:16 - Conectando num shell remoto&lt;/li&gt;
&lt;li&gt;29:41 - Configurando com variáveis de ambiente&lt;/li&gt;
&lt;li&gt;33:39 - Adicionando Banco de Dados&lt;/li&gt;
&lt;li&gt;37:13 - Database Migrations&lt;/li&gt;
&lt;li&gt;37:47 - Conectando no Banco de Dados&lt;/li&gt;
&lt;li&gt;39:22 - Releases e Rollback&lt;/li&gt;
&lt;li&gt;40:34 - Conclusão&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Mega da Virada: Loterias Caixa colocam fila em app, e site fica fora do ar (&lt;a href="https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/"target="_blank" rel="noopener"&gt;https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Getting Started on Heroku with PHP (&lt;a href="https://devcenter.heroku.com/articles/getting-started-with-php#set-up"target="_blank" rel="noopener"&gt;https://devcenter.heroku.com/articles/getting-started-with-php#set-up&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/TLRW_xTnQwY"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente vou mostrar o que é o Heroku e como é o fluxo de trabalho mínimo de um projeto web ideal. Se você já usa Heroku, aproveite pra compartilhar o video com conhecidos que ainda não usam. Se nunca viu Heroku, prepare-se pra ficar surpreso!</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>00:41 - Mega Sena da Virada</li>
<li>02:33 - Concorrência</li>
<li>04:11 - Intro a Heroku</li>
<li>04:46 - Deploy antigo</li>
<li>06:24 - Iniciando Tutorial Heroku</li>
<li>06:49 - Setup: Git é Básico!</li>
<li>07:22 - Heroku Login</li>
<li>08:18 - Primeiro Deploy</li>
<li>10:03 - Buildpack? Dockerfile?</li>
<li>10:35 - Entendendo Dynos e Containers</li>
<li>11:30 - Escalando Dynos</li>
<li>13:16 - Entendendo Load Balancer</li>
<li>16:41 - Vendo logs dos Dynos</li>
<li>17:22 - Entendendo Procfiles</li>
<li>18:58 - Mais sobre escalar dynos</li>
<li>20:21 - Entendendo Gerenciamento de Dependências</li>
<li>24:37 - Adicionando nova dependência</li>
<li>26:40 - Adicionando Addons</li>
<li>28:16 - Conectando num shell remoto</li>
<li>29:41 - Configurando com variáveis de ambiente</li>
<li>33:39 - Adicionando Banco de Dados</li>
<li>37:13 - Database Migrations</li>
<li>37:47 - Conectando no Banco de Dados</li>
<li>39:22 - Releases e Rollback</li>
<li>40:34 - Conclusão</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Mega da Virada: Loterias Caixa colocam fila em app, e site fica fora do ar (<a href="https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/"target="_blank" rel="noopener">https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/</a>)</li>
<li>Getting Started on Heroku with PHP (<a href="https://devcenter.heroku.com/articles/getting-started-with-php#set-up"target="_blank" rel="noopener">https://devcenter.heroku.com/articles/getting-started-with-php#set-up</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Bem vindos ao primeiro video de 2022. O video de hoje vai ser parte 1 de 2 videos. Eu queria falar sobre os famosos 12 fatores pra criar projetos escaláveis modernos. Pra fazer isso eu preciso que todo mundo já tenha visto pelo menos uma vez como é trabalhar com uma plataforma chamada Heroku. Se você já trabalha com Heroku, o que vou falar hoje é o arroz com feijão, mas vocês podem usar pra apresentar a plataforma pra amigos seus que ainda não conhece. Pra todo o resto, esse é o modelo de trabalho que você deveria almejar se nunca usou essa plataforma.</p>
<p>(&hellip;)</p>
<p>Antes de começar deixa eu responder algumas coisas que vi online depois dos últimos vídeos. No caso do exemplo que dei do ingresso.com eu terminei concluindo que se eles não tem controle sobre o sistema de reserva de assentos dos cinemas - que imagino que sejam aplicativos super antigos que não foram feitos pra escala de internet - então deveriam controlar o acesso a esses sistemas usando uma fila virtual. Ninguém acessa diretamente a reserva de assentos, cai primeiro numa página mais fácil de escalar de fila e fica esperando a vez.</p>
<p>Por acaso foi exatamente isso que a Caixa Econômica Federal fez pra Mega Sena da virada que ia sortear mais de 300 milhões de reais. E eles implementaram justamente uma fila virtual pra ver se aguentavam a multidão que ia tentar acessar tudo no último dia. Eu não perco meu tempo apostando em loteria porque é só garantia que vou estar dando dinheiro de graça pros outros, mas quem tentou parece que teve muita dor de cabeça mesmo tendo esse sistema de fila implementado.</p>
<p>Mas é óbvio. Só porque você implementou algum pattern ou arquitetura não existe nenhuma garantia que implementou certo ou que não exista nenhum outro problema que ficou pra trás. Eu não sei como eles fizeram, mas imagino pelo menos 2 problemas. O primeiro e mais óbvio é que o tráfego gerado por uma Mega Sena dessas deve ter sido absurdamente alto. No nível que até uma Amazon da vida sofreria um pouco. Então dou o benefício da dúvida que eles tentaram o máximo mas mesmo assim o tráfego foi ainda maior do que era imaginado.</p>
<p>O segundo problema, que acho que é o mais factível, é que o sistema foi mal feito mesmo. Não me entendam mal. Eu acho que existem sim, bons programadores, com boas capacidades técnicas e boas intenções que tentaram o máximo pra fazer isso funcionar. Porém, a grande maioria dos funcionários públicos que trabalham nos diversos departamentos e sistemas integrados provavelmente não tavam dando a mínima e foram obstáculos pros poucos que tentaram fazer as coisas direito.</p>
<p>Entenda a seguinte verdade: nenhum sistema feito pelo estado jamais vai ser tão bom quanto os melhores sistemas feitos na iniciativa privada. É uma impossibilidade. O motivo é muito simples: não tem concorrência. A mesma coisa vale pra empresas privadas que tem conluio com governos, que passam a ser basicamente empresas públicas com fachada de empresa privada. A maioria dos grandes monopólios só existem porque foram auxiliados pelos governos.</p>
<p>Sem concorrência, pra que eu preciso fazer um sistema melhor? As pessoas têm outra opção? Não, então foda-se, vai ser obrigado a usar o que tem. Mesmo se for uma merda. E eu, que trabalho aqui, vou ser mandado embora se não funcionar? Também não, então foda-se de novo. E vai ser sempre assim. Eu sinto pena dos poucos que trabalham lá que querem fazer as coisas funcionar direito, mas vão sempre esbarrar em um monte de departamento que tá pouco se fodendo e um monte de burocracia que impede melhorias. Mas é isso aí.</p>
<p>Software nunca tá “pronto”. Se o lugar tem mentalidade de &ldquo;terminar&rdquo; o software e não mexer mais, sempre vai ser uma porcaria. O Software precisa estar constantemente atualizado, constantemente consertado, constantemente expandindo e remodelado. O problema em lugares como uma Caixa é que deve ter dezenas de sistemas que tá no esquema &ldquo;tá funcionando? então não mexe!&rdquo; só que eventualmente vai precisar integrar, se comunicar. Mas não tem como, daí vai nascendo dezenas de gambiarras pra fazer as coisas funcionarem ao redor dessas ilhas radioativas. Deixe passar alguns anos assim e você tem a experiência bosta que foi a Mega da virada. É simples assim.</p>
<p>E isso me lembrou de um assunto que eu queria explicar faz algum tempo. No video sobre a história do Ruby on Rails eu mencionei rapidamente sobre Heroku e a metodologia de 12 fatores do Adam Wiggins, um dos co-fundadores do Heroku. Objetivo hoje vai ser dar uma repassada nesses conceitos pra quem nunca viu. Se você é um programador sênior certamente já sabe de tudo isso e certamente já usa no seu dia a dia. Eu não chamaria ninguém que não segue no mínimo os 12 fatores de sênior. E eu digo o mínimo porque isso é o básico do básico e na parte final do video vou até complementar mais algumas coisas que todo mundo já deveria saber.</p>
<p>Os 12 fatores em si vou falar no próximo video. Pra entender os 12 fatores você precisa saber o que é Heroku. Se nunca usou Heroku faça um favor a você mesmo e veja qualquer tutorial básico de como subir um aplicativo nele. Pra um iniciante é uma experiência que muda sua forma de pensar em deploys. Pra quem é das antigas e nunca se atualizou também vai ser um choque. Quando precisava subir um projeto web pra produção, como a gente fazia nos anos 90 e começo dos anos 2000? A gente configurava um servidor remoto Linux, num Virtual Private Server ou VPS como um Linode da vida.</p>
<p>Nesse servidor a gente configurava tudo manualmente via telnet ou SSH. Instalava um banco de dados como MySQL, um servidor web como Apache, alguma linguagem interpretada como Perl ou PHP, copiava os arquivos da aplicação, normalmente feita em Perl ou PHP, via FTP ou hoje em dia SFTP e pronto, tava tudo no ar. Essa stack inclusive tinha um nome, chamado LAMP, que é acrônimo pra Linux, Apache, MySQL e Perl ou PHP. Isso é tecnologia do fim dos anos 90. Entenda, se alguém fizer isso hoje em 2022, você tá parado no tempo mais de 20 anos. Ninguém em sã consciência atualiza código direto no servidor assim via FTP mais.</p>
<p>Eu já tinha dito no episódio do ingresso.com que o jeito errado é fazer tudo rodar numa única máquina, configurar tudo manualmente. Atualizar código direto lá é a pior forma possível de colocar uma aplicação no ar. Agora vamos ver como é o estado da arte, a melhor forma de fazer deploy tanto do ponto de vista de escalabilidade quanto de segurança. E pra isso eu vou seguir um tutorial oficial do próprio Heroku pra vocês verem como é absurdamente simples.</p>
<p>O tutorial começa com o básico. Você obviamente precisa ter uma conta criada no Heroku. Se ainda não fez isso, vai lá depois de assistir o video e cria a conta. Eles tem tutoriais pra quem usa Rails, Node, Java e muito mais, mas já que falei da stack LAMP, vamos ver como se sobe uma aplicação PHP. Vamos assumir que se você é de PHP já tem tudo instalado e obviamente usa Composer pra gerenciar suas dependências.</p>
<p>Se você for de outras linguagens como Python ou Elixir, não importa. Se nunca viu Heroku funcionando não interessa a linguagem, se atenha ao passo a passo e o raciocínio. Qualquer bom programador tem que ser capaz de no mínimo seguir o raciocínio mesmo se for em outra linguagem. Também vamos assumir que você já sabe usar minimamente Git. Se ainda não sabe, assistam meus vídeos sobre Git depois. E vocês se lembram que no video de Conhecimentos Básicos eu falo que saber Git é básico? Pois é, repito, é básico. Não tem como trabalhar em projetos modernos sem saber Git.</p>
<p>Agora, pra usar Heroku precisamos instalar a linha de comando do heroku. Praticamente tudo que vai precisar tem nessa linha de comando. Num archlinux da vida basta fazer um <code>yay -S heroku-cli</code>, num Ubuntu da vida dá pra instalar com Snap. Procure como instalar pra sua distro mas no final você deve ser capaz de abrir um terminal, digitar <code>heroku login</code> e vai abrir um navegador pra logar na conta que acabou de criar. E, claro, sempre habilite autenticação de duas etapas, é o mínimo.</p>
<p>No próximo passo ele vai mandar você fazer o clone de um projetinho de exemplo feito em PHP Symfony, que é um dos muitos frameworks web pra PHP. Symfony tem muita inspiração tanto em Rails quanto Django. Pra projetos novos recomendo usar Laravel, que tem uma comunidade mais ativa e um ecossistema que cresceu bem em torno dele. De qualquer forma, se você sabe Git, clonar um projeto é arroz com feijão. Tudo vai acontecer dentro do diretório desse projeto, então só dar <code>cd</code> pra ele.</p>
<p>Agora vamos subir essa aplicação de exemplo. Pra isso precisamos cadastrar uma nova aplicação no Heroku. Você pode subir quantas aplicações quiser na sua conta e pode começar com opções gratuitas. Depois pode transferir a propriedade das aplicações que subiu pra outra conta, como do seu cliente. Pra cadastrar é simples, do diretório do projeto, num terminal, só usar a linha de comando que instalamos e fazer <code>heroku create</code>. Se não disser que nome quer, ele vai inventar um aleatório. O nome em si não interessa tanto porque depois você vai registrar um domínio de verdade e apontar pra esse nome, o CNAME, então o usuário final mesmo nunca vai ver esse nome. Mas vai servir pra gente testar no domínio do Heroku.</p>
<p>Uma vez a aplicação registrada na sua conta, agora é só subir o código. Quando rodou o comando anterior, ele também criou um branch remoto no Git do seu projeto. Então agora é só subir o que tem na branch principal <code>main</code> que antigamente se chamava <code>master</code> pra esse remote chamado <code>heroku</code>. Relembrando, todos os remotes ficam no arquivo <code>.git/config</code>, dá um <code>cat</code> nele pra ver o conteúdo, olha o remote lá. Agora é só fazer <code>git push heroku main</code>. Olha o que vai acontecer no seu terminal.</p>
<p>Se você ainda não entendeu, o Heroku criou um repositório Git remoto associado com sua nova aplicação, esse é o remote. É como se fosse um projeto novo no GitHub que você vai dar push. Mas o Git tem uma funcionalidade que você pode configurar que ele detecte quando você faz um <code>push</code> e rodar algum script. No caso, o Heroku detectou que tá subindo código PHP e por isso vai automaticamente instalar a buildpack pra ter as ferramentas de PHP que vai precisar como o próprio interpretador PHP, o Composer, Apache, NGINX e tudo mais. O que o Heroku chama de buildpack é mais ou menos o que você chamaria de Dockerfile.</p>
<p>Aqui vale um adendo se você já usa Docker. Por que o Heroku reinventou a roda com esses buildpacks em vez de usar Dockerfiles? Na verdade é o contrário: o Heroku precede a invenção do Docker. Na realidade, muito do Docker foi inspirado no que o Heroku fez anos antes dele. O Heroku foi lançado por volta de 2008, o Docker foi lançado só em 2013. O objetivo da vida do Docker e tudo que saiu em torno de containers como Docker Compose, Docker Machine, Dokku, até Kubernetes, é conseguir imitar o que o Heroku fez antes de todo mundo.</p>
<p>Vamos considerar o que tá acontecendo nesse ponto. O comando <code>git push</code> que fizemos tá mandando o Heroku fazer o equivalente a criar uma imagem de Docker, é semelhante a um <code>docker build</code>. Nos servidores deles rodam diversos containers, que eles chamam de <code>dynos</code>. Tem de diversos tamanhos, a versão gratuita são dynos de 512 megabytes de RAM, se não me engano com uns 4 cores ou núcleos virtuais fraquinhos. Se precisar, dá pra subir pra versões de 1 giga até 14 giga de RAM se precisar muito.</p>
<p>Mas cuidado, a grande maioria das aplicações deveria conseguir rodar suficientemente bem em 512 mega de RAM, se precisa de mais que isso precisa ver se não tá com vazamento de memória, ou você programou muito porcamente e tá enchendo a memória de lixo. Trabalhar em um ambiente mais restrito do que sua máquina local é uma boa prática. Qualquer notebook hoje tem 8 giga ou mais de RAM e você muitas vezes nem percebe que sua aplicação tá usando muito mais memória do que deveria.</p>
<p>Seguindo o tutorial a próxima coisa que ele manda fazer é escalar a aplicação com o comando <code>heroku ps:scale web=1</code> que basicamente manda o Heroku subir sua aplicação num único dyno. Se mudar pra web igual a 2, ele vai subir dois dynos. O que significa isso? Vamos fazer uma conta de padeiro. Eu disse que o dyno gratuito tem uns 4 cores virtuais. Se eu configurar o apache pra pendurar um fork de processo por núcleo significa que consigo ter até 4 requisições simultâneas. Se subir 2 dynos eu posso ter até 8 requisições simultâneas. Simultâneo significa exatamente no mesmo instante. Nessa conta de padeiro estou considerando 1 requisição por processo, sem considerar opções de threads ou I/O assíncrono.</p>
<p>Digamos que a aplicação leve 100 milissegundos pra responder uma requisição. Então em 1 segundo daria pra responder até 10 requisições. Com 1 dyno então seria teoricamente possível responder até 40 requisições por segundo e com 2 dynos até 80 requisições por segundo, entenderam? Isso é teórico porque estou assumindo que cada core conseguiria responder 10 requisições todo segundo, mas depende se durante o processamento de cada requisição se não bloqueia algum recurso que outra requisição pode precisar, como banco de dados e coisas assim, por isso o tempo real pode variar bastante.</p>
<p>Containers, como Docker, como Dynos de Heroku, não são máquinas virtuais. Eu explico sobre isso no meu episódio sobre Devops. Se você não sabe a diferença recomendo que assista depois. Mas na prática só entenda que é uma forma de particionar os recursos da sua máquina e cada programa rodar isoladamente achando que está sozinho nessa máquina. Assim é possível particionar uma máquina real grandona em diversos containers menores, dividir os recursos, e te cobrar de uma forma mais fácil.</p>
<p>Mais do que isso, o Heroku já deixa muita coisa de infraestrutura preparada pra você. Por exemplo, eu expliquei que tem esse comando <code>ps:scale</code> que permite subir a imagem da sua aplicação em múltiplos dynos. Significa que você tem vários servidores web de pé ao mesmo tempo. Agora, quando um usuário digitar a URL pra sua aplicação ele não cai direto no servidor web da sua aplicação e sim num balanceador de carga, ou load balancer, proprietário do Heroku, que vai pegar as requisições e distribuir nos dynos que você tem de pé provavelmente usando uma estratégia como round robin.</p>
<p>Se você é iniciante isso pode parecer estranho. Quando sobe um processo de servidor web na sua máquina local, seja nginx, seja apache, seja um webpy de python, tomcat de java, puma de rails, vai ser um processo pendurado em uma porta. Toda vez que no seu navegador você carrega <code>localhost:3000</code> ele vai direto pra quem responde nessa porta 3000 e é isso. Mas quando você sobe vários containers, cada um com seu próprio servidor na porta 3000, cada um dos containers tem um IP interno próprio. Faz de conta, 172.16.0.10 e 172.16.0.11.</p>
<p>No seu navegador você não pode mais usar localhost porque não tem mais ninguém no ip local 127.0.0.1 na porta 3000, que seria o localhost. Quando você sobe containers de Docker localmente, ele cria uma rede virtual local. Cada container de Docker que sobe ganha um IP virtual privado e nele que o servidor web da sua aplicação vai se pendurar na porta 3000 por exemplo. Se achou confuso recomendo que estude e treine Docker na sua máquina local, em particular usando Docker Compose pra orquestar cada serviço num container separado.</p>
<p>Você pode naturalmente digitar direto o ip privado de um dos containers como 172.16.0.10:3000 e aí vai carregar sempre só desse container, mas o segundo container vai ficar lá parado sem trabalhar. Pra conseguir acessar os dois containers, em vez de acessar direto, pode subir um terceiro container, com um load balancer como o NGINX ou HAProxy ou vários outros. Nesse load balancer você configuraria uma regra dizendo, toda vez que alguém mandar uma requisição na porta 80 eu envio pra porta 3000 de um dos dois containers que tenho cadastrado.</p>
<p>Isso que se chama um proxy reverso. Digamos que o container do load balancer subiu com IP virtual 172.16.0.13. Agora você pode ir no navegador e digitar <code>172.16.0.13</code> que por default vai conectar na porta 80. O load balancer vai pegar essa requisição e mandar pra um dos dois containers web respondendo nas suas portas 3000. Pro navegador é transparente. Ele não tem idéia de quantos containers web tem por baixo, nem em que portas eles respondem de verdade, nem que IPs tem. Load balancer é tanto uma forma de distribuir requisições pra mais servidores web quanto anonimizar os IPs dos servidores para que os usuários não saibam quem são.</p>
<p>O objetivo do episódio não é ensinar redes, mas como tem muito iniciante assistindo achei melhor pelo menos dar o resumo do resumo. Pra saber mais procurem artigos sobre load balancer, em particular tentem fazer exatamente esse cenário que eu falei: subir com Docker Compose 2 ou mais containers com uma aplicação web qualquer e outro container com NGINX configurado como load balancer e veja na prática suas requisições sendo distribuídas pelos seus containers. Tem dezenas de tutoriais que você acha no Google pra fazer isso, só não ter preguiça de procurar, pra treinar qualquer um serve.</p>
<p>Falando nisso, voltando pro tutorial do Heroku, o próximo passo é ver se sua aplicação subiu direito e está respondendo como deveria. Pra isso você precisa conseguir ver os logs em tempo real. Se fosse uma aplicação local era só fazer tipo um <code>tail log/application.log</code> pra ficar monitorando o que entrar no log em tempo real. Tail é inglês pra rabo, e como o nome diz a gente fica seguindo o rabo, ou seja, o final do arquivo. Pro Heroku é parecido mas só usar a linha de comando deles fazendo <code>heroku logs --tail</code>. Lógico, o <code>--tail</code> é opcional, mas se usar vai manter o log aberto e tudo que for sendo logado vai aparecendo no seu terminal. De novo, se você tá acostumado a usar Linux isso é algo super comum.</p>
<p>Fizemos o clone da tal aplicação de exemplo mas nem vimos o que tem nele. O principal é entender que toda aplicação que sobe no Heroku precisa ter pelo menos um arquivo na raíz do projeto chamado <code>Procfile</code>. Nele definimos que no container de tipo <code>web</code> vai executar o binário executável do apache apontando pra pasta <code>web</code>. Isso é específico de cada framework. Se fosse um Rails o executável seria <code>bin/rails server</code> e assim por diante. É assim que o Heroku sabe o que é pra executar dentro do container. Se fosse um <code>Dockerfile</code> seria o equivalente ao parâmetro <code>CMD</code>.</p>
<p>Não vou mostrar isso hoje, mas além de web existem outros tipos de containers que você pode declarar como <code>queue</code> ou <code>job</code> se quiser montar imagens que sobem workers pra uma fila assíncrona, por exemplo. Eu expliquei um pouco sobre filas assíncronas no episódio do ingresso.com e no de concorrência e paralelismo. O importante é saber que Heroku não sobe só aplicações web. Além disso, o formato de arquivo <code>Procfile</code> meio que virou universal. No mundo Rails você pode usar uma ferramenta como o <code>foreman</code> que vai ler esse arquivo e localmente subir sua aplicação pra simular como rodaria no Heroku.</p>
<p>Você pode usar o foreman escrito em Ruby pra rodar localmente sua aplicação mesmo se for escrita em PHP, ou usar o node-foreman que faz a mesma coisa ou o goreman ou forego que são outros clones de foreman escritos em Go. Tanto faz no que é escrito porque só vai ler o que tem no arquivo <code>Procfile</code> e executar o que está lá. É uma boa prática testar localmente antes de subir pro Heroku ou outra plataforma que também suporte Procfiles.</p>
<p>O próximo passo do tutorial foi o que expliquei agora pouco. Como escalar sua aplicação subindo mais containers web. Só usar o comando <code>heroku ps:scale</code> e fazer <code>web=2</code> ou mais. Se quiser desligar a aplicação, tirar da web, só fazer igual a zero que vai desligar todos os containers. Você pode aumentar ou diminuir o número de containers manualmente com esse comando ou contratar um serviço de auto-escala que usa algumas estratégias pra fazer esse ajuste automaticamente dependendo da carga que sua aplicação tá recebendo.</p>
<p>Dizendo assim pode parecer que escalar sua aplicação é algo tão simples quanto rodar esse comando e subir 100 dynos de uma só vez em horário de pico. Mas isso não é uma bala de prata. Lembre-se que se subir 100 dynos vai precisar que recursos embaixo dele, como seu banco de dados, tanto consiga aguentar esse tanto de conexões simultâneas e ter CPU e RAM suficientes pra processar tanta coisa de uma só vez.  Escalabilidade nunca é automática, você precisa estar preparado pra isso. Na prática, essa funcionalidade é mais pra você economizar custos. Digamos que no máximo, os recursos que instalou suportariam 100 dynos de pé ao mesmo tempo. Mas de madrugada seu tráfego cai um monte e só 10 dynos seriam suficientes. Então você pode fazer um script que derruba 90 dynos de madrugada pra economizar custos e de manhã cedo sobe 90 dynos novos pra aguentar o tráfego do dia.</p>
<p>Agora vamos falar de dependências. Se você programa em Rails, obrigatoriamente usa a ferramenta Bundler pra gerenciar dependências. Toda nova biblioteca que precisa adicionar no seu projeto, primeiro você declara no arquivo <code>Gemfile</code> e instala a tal biblioteca com o comando <code>bundle update</code>. Você jamais vai no site da biblioteca, baixa um zip e descompacta dentro do diretório do seu projeto, isso seria uma barbárie e algo totalmente inaceitável numa sociedade civilizada moderna em 2022.</p>
<p>Se você programa em Javascript, Node.js, obviamente adiciona todas as suas dependências com o comando <code>npm install</code> e a opção <code>--save-dev</code> por exemplo. Ou se usa <code>yarn</code> usa o comando <code>yarn add</code>. Em ambos os casos isso vai atualizar o arquivo <code>package.json</code>, que todo projeto civilizado de Javascript tem obrigação de ter.</p>
<p>Se você é de Java, certamente conhece Maven e usa a ferramenta Gradle, e tudo vai estar declarado no arquivo <code>build.gradle</code> ou <code>pom.xml</code>. Se você é de Python, eu sei que é chato e ainda não é uma solução perfeita, mas deveria estar usando <code>pip</code> e todas as dependências deveriam estar no arquivo <code>requirements.txt</code>.</p>
<p>Se é de Go, só recentemente começaram a ficar mais civilizados e agora fazendo <code>go mod init</code> você cria um arquivo <code>go.mod</code> que é onde se declara dependências. Daí rodando o comando <code>go mod tidy</code> vai baixar as dependências que precisa pra compilar e executar sua aplicação. O Rust sempre veio com o utilitário Cargo e você sempre tem um arquivo <code>Cargo.toml</code> que declara os crates, que é como povo de Rust chama suas bibliotecas.</p>
<p>Entenderam? Não importa que linguagem você usa, toda linguagem civilizada tem um gerenciador de dependências padrão, sempre tem um arquivo onde se declara essas dependências, e você nunca, jamais, deve baixar bibliotecas manualmente e jogar dentro do seu repositório. Mais do que isso, todo gerenciador competente costuma ter um arquivo de tranca, de lock. Por exemplo, projetos de Javascript tem o <code>package.json</code> onde você declarou as bibliotecas e versões que queria, mas quando o <code>npm</code> ou <code>yarn</code> realmente baixam e instalam ele gera um outro arquivo chamado <code>package-lock.json</code> que você nunca deve editar manualmente, que declara exatamente quais bibliotecas e exatamente quais versões baixou.</p>
<p>Esse arquivo de lock é especialmente importante porque quando outro desenvolvedor dá pull e clona o repositório do projeto ou quando você dá <code>git push</code> pro Heroku, ele roda o <code>npm install</code> e vai baixar exatamente o que estiver nesse arquivo de lock. 1.0.0 é diferente de 1.0.2. Isso é importante pra todo mundo baixar exatamente a mesma versão de tudo. Basta uma biblioteca que era pra ser, faz de conta, versão 1.0.1 e no servidor baixar a 1.0.2 que um bug pode ser introduzido sem ninguém saber. Gerenciamento de dependências é uma ciência exata. Ela só fica caótica quando você não segue essas regras.</p>
<p>Essa longa explicação foi só pra pular pro próximo passo do tutorial onde ele rapidamente explica que nosso projeto PHP de exemplo usa um gerenciador de dependências chamado Composer. E como esperado, existe um arquivo chamado <code>composer.json</code> na raiz do projeto junto com um arquivo <code>composer.lock</code>. No caso do PHP, no arquivo <code>web/index.php</code> tem uma linha com o comando <code>require</code> carregando um <code>autoload.php</code> que é quem se responsabiliza por carregar as bibliotecas declaradas no <code>composer.json</code>.</p>
<p>Na sua máquina local, se fizer <code>compose update</code> vai baixar todas as dependências localmente e agora pode rodar o projeto na sua máquina. E quando fazemos <code>git push</code> pro Heroku os scripts no buildpack de PHP vão procurar o arquivo <code>composer.json</code> e se achar, vai rodar o <code>compose update</code> pra montar a imagem. E é por isso que você precisa gerenciar as dependências dessa forma, pra que outro desenvolvedor não tenha problemas quando baixar na máquina dele, e pra quando subir num Heroku ou máquina de produção sabemos que não vai faltar nenhuma dependência. Isso é crucial pra vivermos numa sociedade civilizada. Imagina como era na época da barbárie quando não tínhamos ferramentas como essas.</p>
<p>Vamos simular que esse projeto ainda está em desenvolvimento. Vamos adicionar uma nova funcionalidade. Pra isso começamos usando o comando <code>compose require</code> que vai declarar e puxar a biblioteca <code>cowsayphp</code> que é um programinha besta que só desenha uma vaca com caracteres ASCII. Depois disso só rodar <code>compose update</code> pra garantir que foi baixada e instalada. Se abrirmos o arquivo <code>composer.json</code> veja que ela foi declarada automaticamente pelo Composer.</p>
<p>Agora, no <code>index.php</code> podemos criar uma rota chamada <code>/cowsay</code> que vai usar essa biblioteca <code>Cowsayphp</code> e mandar ela desenhar a vaca dizendo &ldquo;Cool beans&rdquo;. Vamos só copiar e colar esse trecho do tutorial no nosso projeto, e pronto. Agora precisamos adicionar tudo isso que modificamos no repositório Git. Pra isso basta fazer <code>git add .</code>. Lembrem-se que nos vídeos de Git eu falo pra tomar cuidado pra não adicionar coisas que não precisam. Por acaso esse projeto tem um <code>.gitignore</code> e se dermos <code>git status</code> podemos ver que só vamos adicionar os arquivos do Composer e o <code>index.php</code> que modificamos. Na dúvida sempre rode <code>git status</code> antes de dar <code>git commit</code>.</p>
<p>Finalizamos com o <code>git commit -m</code> adicionando uma mensagem descritiva da modificação que fizemos e executamos um novo <code>git push heroku main</code> pra subir a modificação e gerar uma nova imagem. Olhem o Heroku recebendo, rodando <code>compose update</code> que vai baixar a biblioteca que mandamos, vai regerar a imagem, daí ele sozinho vai desligar os dynos que estavam rodando antes e subir novos dynos com a imagem nova.</p>
<p>Se usarmos o comando <code>heroku open cowsay</code> ele vai abrir o navegador pra gente, já apontando pra URL da aplicação /cowsay. Não precisa desse comando, você podia abrir o navegador e digitar a URL manualmente, mas assim é mais rápido. Demora um segundo pra abrir porque o Heroku está atualizando os dynos, mas voilá, tá funcionando. E é assim que subimos versões novas da nossa aplicação. Lembram deploy contínuo que eu falei? É assim que faz.</p>
<p>Além de toda essa infraestrutura de containers, load balancers, facilidade de subir atualizações usando Git, o Heroku tem parceria com dezenas de empresas que oferecem serviços que são úteis pras nossas aplicações. No tutorial, o próximo passo é justamente instalar uma delas, o addon pra ferramenta chamada Papertrail. Addon é sinônimo de plugin e Papertrail é um serviço que recebe os logs da nossa aplicação e oferece uma interface web que podemos monitorar e, principalmente, fazer pesquisas.</p>
<p>A maioria dos addons oferece uma versão gratuita pra gente testar e pra aplicações pequenas costuma ser suficiente também. Pra instalar basta ir no terminal e fazer <code>heroku addons:create papertrail</code>. Esses comandos têm várias opções, por exemplo pra já instalar com um plano pago mais parrudo. Sempre leia a documentação de cada addon antes de sair instalando. De qualquer forma, com o comando <code>heroku addons</code> podemos listar quais já temos instalado e veja como o Papertrail já aparece.</p>
<p>Pra abrir a interface web no seu navegador, via terminal podemos rodar <code>heroku addons:open papertrail</code> e voilá, agora temos como fazer pesquisas nos nossos logs. Isso é mais importante se considerarmos que podemos ter mais de um dyno rodando ao mesmo tempo e no Papertrail vai concentrar os logs de todos os dynos ativos. Assim podemos pesquisar os logs de todos ao mesmo tempo. Por acaso esse é um addon que eu recomendo sempre instalar em toda aplicação. Papertrail e também o Rollbar que você pode configurar pra te notificar por e-mail se alguma mensagem de erro crítico aparecer no log.</p>
<p>O próximo passo do tutorial é um pouco mais avançado e eu não recomendo que você use isso se não for um desenvolvedor mais experiente. A linha de comando do Heroku permite abrir um container novo pra onde ele vai abrir uma conexão SSH segura. Dentro dele você pode rodar o shell interativo do seu framework como o <code>php -a</code> no caso de PHP ou o <code>rails console</code> no caso de Rails ou simplesmente abrir um shell bash caso queira checar alguma coisa no nível do sistema operacional. Tudo que carrega nos containers de web também carrega nesse container de console, então pode ser bom pra debugar algum bug que não acontece na máquina de desenvolvimento mas aparece quando sobe a aplicação no Heroku. Use com cuidado, mas essa opção já salvou minha vida diversas vezes.</p>
<p>Esclarecendo, esse comando não abre SSH direto pra um dos containers web rodando. Ele abre um novo container, com a mesma imagem que sobe nos containers web, só isso. A vantagem é que de lá você tem os mesmos acessos ao banco de dados se precisar muito rodar alguma query de emergência ou algo assim. E justamente por isso eu falo pra tomar muito cuidado, porque o que você rodar no banco vai ser permanente. Quando desconecta do shell interativo, esse container é destruído, por isso não crie ou baixe arquivos pra lá porque esse container vai sumir tão logo você se desconecte dele. É especificamente pra tarefas administrativas especiais.</p>
<p>Outro conceito que pra iniciantes pode não ser muito óbvio são variáveis de ambiente. Por exemplo, imagino que a maioria que usa Linux no mínimo já lidou com variáveis como <code>PATH</code> num arquivo local como <code>.profile</code> ou <code>.bashrc</code> da vida. Você faz alguma coisa como <code>export ABC=blabla</code>. Isso declara uma variável global na sua sessão e você pode ver o conteúdo da variável fazendo <code>echo $ABC</code> e vai imprimir o <code>blabla</code> no seu terminal.</p>
<p>Pois bem, é considerado uma boa prática declarar configurações assim como variáveis de ambiente, particularmente em containers. Se você já viu um arquivo <code>.env</code> na raíz do seu projeto, ele serve pra declarar e simular variáveis de ambiente do projeto. De novo, a gente inventou isso no Rails e todo framework web meio que adotou a mesma funcionalidade. Fazemos a configuração num arquivo pra cada desenvolvedor não precisar manualmente ficar escrevendo um monte de <code>exports</code> no seu profile local de Bash. Além disso é boa prática ter um arquivo como <code>.env.example</code> que quando clonamos o projeto fazemos uma cópia dele pra <code>.env</code>, assim não precisamos adivinhar quais variáveis existem pra usar.</p>
<p>E mais importante, é boa prática colocar <code>.env</code> no arquivo <code>.gitignore</code> pra nunca entrar no repositório Git. Isso porque os frameworks que suportam essa convenção, se existir o arquivo <code>.env</code> ele tem prioridade sobre as variáveis de ambiente de verdade configuradas no seu sistema operacional. Mas quando subimos pra produção não queremos que a aplicação carregue desse arquivo e sim das variáveis de verdade no sistema operacional. Então sempre se lembre dessa regra: <code>.env</code> sempre declarado no <code>.gitignore</code>.</p>
<p>Entendido isso, localmente na sua máquina de desenvolvimento você edita o que precisa no arquivo <code>.env</code> que só existe na sua máquina, mas em produção configura variáveis de verdade. No Heroku fazemos isso com o comando <code>heroku config:set</code>. Vamos fazer um exemplo pra ilustrar isso. Naquele arquivo <code>index.php</code> vamos adicionar mais uma rota, no caso substituir a rota raiz do site. Vamos repetir a palavra &ldquo;Hello&rdquo; X vezes, e essas X vezes vai estar declarado na variável de ambiente chamada <code>TIMES</code>. Pra ler essa variável, em PHP, se usa a função <code>getenv</code>.</p>
<p>Agora, não temos como editar um <code>export</code> no arquivo de profile de um dyno, pra isso usamos a linha de comando <code>heroku config:set TIMES=20</code>. Pra listar todas as variáveis que existem só usar o comando <code>heroku config</code>. Olhem como tem uma variável do Papertrail que foi criado pela linha de comando que usamos pra adicionar o addon, esse é o token secreto de acesso. Não tem problema ter coisas como tokens e senhas em variáveis de ambiente, porque elas só existem dentro do container. Se alguém conseguiu ver esse token é porque ganhou acesso ao seu container de produção e seu problema é bem maior do que só ter o token exposto, você está com tudo exposto. Ninguém jamais deve ter acesso direto aos containers.</p>
<p>E pra reforçar, é exatamente por isso que nunca se deve adicionar arquivos como <code>.env</code> no repositório Git, porque normalmente guardamos coisas como senhas e tokens em variáveis de ambiente, e esse tipo de informação, jamais, sob nenhuma hipótese, pode aparecer dentro de um repositório Git. Nenhuma credencial ou segredo jamais deve estar no código fonte do projeto. Tem que ser um tosco de proporções bíblicas pra pensar em colocar segredos num versionador de código que todo mundo tem acesso.</p>
<p>E pronto. Agora podemos fazer <code>git add .</code>, <code>git commit -m</code> e uma mensagem e finalmente <code>git push heroku main</code> de novo. Ele vai criar uma nova imagem com as modificações que acabamos de fazer e se abrirmos o navegador com <code>heroku open</code>, esperamos um segundo pro Heroku derrubar os dynos e subir com a imagem nova e, voilá, veja &ldquo;Hello&rdquo; repetido 20 vezes.</p>
<p>E como mencionamos “banco de dados” algumas vezes, o último passo do tutorial é justamente adicionar Postgres na nossa aplicação. Assim como o Papertrail, Postgres é um addon que tem diversos tamanhos e preços e você deve checar as opções antes de adicionar. Isso porque se pegar um muito pequeno e precisar dar upgrade, o processo não é simples e muito menos trivial. Leia a documentação do Heroku sobre isso. Uma das melhores coisas do Heroku é justamente o serviço de banco de dados, que é um dos melhores que existem, mas você precisa ter um mínimo de noção do que tá fazendo. Eles não fazem mágica nem são à prova de idiotas.</p>
<p>Avisos dados, pra adicionar a versão gratuita menor, que se chama <code>hobby-dev</code>, basta digitar no terminal <code>heroku addons:create heroku-postgresql:hobby-dev</code>. Agora vamos adicionar uma funcionalidade no nosso projeto PHP pra conectar no banco e listar o conteúdo de uma tabela em HTML, o arroz com feijão só. Pra isso começamos usando o Composer pra adicionar a biblioteca de PDO que é PHP Data Objects. Se você é de Java ou .NET da vida, é a mesma coisa que um DAO da vida.</p>
<p>Fazemos <code>compose require csanquer/pdo-service-provider=~1.1dev</code> que vai baixar uma versão compatível mas não necessariamente exata com 1.1dev dessa biblioteca, lembram? Por isso precisamos ter um arquivo de lock que vai registrar a versão exata que ele achou e baixou. Rodamos um <code>compose update</code> pra garantir que baixou tudo e agora podemos alterar o arquivo <code>index.php</code> de novo pra configurar o acesso ao banco de dados. Vamos copiar e colocar do tutorial.</p>
<p>O importante é saber que é uma convenção do Heroku ter a URL de acesso ao banco declarado numa variável de ambiente chamada <code>DATABASE_URL</code>. Podemos ver ela usando o comando <code>heroku config</code> e olha só. E esse trecho de código que copiamos e colamos é pra pegar o conteúdo dessa variável com a função <code>getenv</code> e parsear os diversos componentes dessa string e passar pra biblioteca de PDO configurar coisas como username, password, host, porta e path pra conseguir conectar no novo banco de dados Postgres. E simples assim, subimos um banco de dados seguro e funcional na nossa infraestrutura.</p>
<p>Agora vamos criar uma nova rota lá embaixo chamada &lsquo;/db&rsquo;. Ele vai usar esse PDO pra mandar uma query pro banco e fazer um loop com <code>while</code> pra ir montando um array com todos os nomes que voltaram da query. E pra montar o HTML, o framework Symfony oferece um sistema de template chamado Twig. Passamos o array de nomes pro template chamado de <code>database.twig</code>.</p>
<p>Então precisamos criar um novo arquivo <code>views/database.twig</code> e vamos copiar e colar o template do tutorial nele. Ele vai pegar cada nome que veio no array que passamos pra ele e montar uma lista em HTML, nada de mais, arroz com feijão. Isso tudo feito, vamos adicionar as modificações no Git com <code>git add .</code>. Com <code>git status</code> podemos ver que modificamos os arquivos do Composer, o <code>index.php</code> e criamos um arquivo novo <code>database.twig</code>. Está correto então podemos fazer <code>git commit -m</code> com uma mensagem descritiva e finalmente <code>git push heroku main</code> pra mandar as modificações pro Heroku.</p>
<p>Espero que a essa altura você já esteja acostumado com o processo de criar uma nova funcionalidade, adicionar no Git, mandar pro Heroku e conseguir testar de verdade. O Heroku recebe a modificação, monta uma nova imagem, derruba os dynos que estavam rodando e sobe de novo com a nova imagem. Só tem um probleminha.</p>
<p>Note que criamos um novo banco de dados, mas ele está vazio. Em qualquer framework web moderno existe um recurso chamado Migrations, mais uma coisa que nasceu no Ruby on Rails e todos os outros frameworks imitaram, onde criamos scripts que vão criar as tabelas e índices que precisamos, caso já não existam, e também já pré-cadastram coisas como conta de administrador, se precisar. Isso tudo fica em scripts que declaramos pro Heroku executar na próxima vez que subir dynos novos. Procure a documentação do seu framework e aprenda sobre Migrations porque é super importante.</p>
<p>Como isso é só um tutorial simples, tentando ser didático pra ensinar sobre o Heroku e não sobre seu framework, ele pula qualquer coisa sobre Migrations e manda você abrir um novo container de shell, lembra? Que nem abrimos o bash remoto? Só que no caso agora é abrindo o console do Postgresql que é o <code>psql</code>, então vamos fazer isso usando o comando <code>heroku pg:psql</code>.</p>
<p>Agora abriu o console remoto lá no servidor do Heroku já conectado no nosso novo banco de dados. Só precisamos fazer isso uma vez, mas vamos copiar do tutorial o comando pra criar a tabela chamada test_table e em seguida vamos dar insert em alguns valores aleatórios como esse &lsquo;hello database&rsquo; e também um &lsquo;hello world&rsquo;, por que não? Pronto, pra sair do console do Postgres é só digitar <code>\q</code>.</p>
<p>Podemos abrir o navegador de novo usando o comando <code>heroku open db</code> que é a nova rota que criamos e voilá, veja que conseguiu executar a query no banco, trazer as duas linhas que acabamos de inserir, e montar o HTML com elas. E pronto, nesse estágio fomos capazes de criar uma nova aplicação no Heroku, ir subindo novas funcionalidades à medida que fomos codificando, configurando o ambiente e cadastrando novos addons como o Papertrail e Postgres. Esse é o básico do básico sobre Heroku que todo mundo deveria saber. Mas o Heroku faz bem mais que isso. No fim do tutorial, no último passo, ele dá links pra outros artigos como o &ldquo;How Heroku Works&rdquo; que vai começar a explicar muito mais detalhes sobre o ciclo de vida de uma aplicação em dynos.</p>
<p>Por exemplo, lembram que durante o tutorial repetimos <code>git push heroku</code> várias vezes? Todas as vezes ele cria uma nova imagem com as últimas modificações, derruba os dynos rodando a versão antiga e sobe tudo de novo com a imagem nova. O que eu não expliquei é que as imagens antigas continuam disponíveis se precisar. O Heroku chama isso de releases ou lançamentos. Se rodar o comando <code>heroku releases</code> podemos ver tudo que subimos desde a primeira vez. Ele vai numerando cada release e acho que você pode cadastrar tags nelas também pra facilitar achar depois.</p>
<p>A vantagem mais imediata é: digamos que você subiu uma nova versão que não testou muito bem e, surpresa, começou a dar pau em produção. Em vez de ficar tentando consertar na tentativa e erro durante o sufoco, podemos rapidamente dar rollback pra última release que sabemos que tava funcionando, por exemplo <code>heroku releases:rollback v10</code>. A versão 11 que acabamos de subir dá pau, mas a versão 10 funcionava, então voltamos pra ela. É o tipo de coisa que você nunca ia conseguir fazer rápido e organizado assim se estivesse atualizando código fonte manualmente usando FTP. E é por isso que você paga mais por isso também.</p>
<p>Eu normalmente não gosto de fazer videos de tutoriais de ferramentas específicas porque elas envelhecem muito rápido. Mas assim como no caso de Git, o Heroku existe faz mais de dez anos e continua funcionando basicamente do mesmo jeito. Esse mesmo tutorial que funcionaria em 2010 continua funcionando em 2022 e provavelmente vai continuar funcionando por muitos mais anos do mesmo jeito e toda nova solução de devops que aparece é pra tentar se aproximar desse ideal de fluxo de trabalho. Por isso, mesmo que você não use no seu trabalho atual, acho muito importante que se familiarize com esse jeito de trabalhar.</p>
<p>No próximo episódio vamos pegar o que aprendemos hoje pra discutir um pouco mais sobre arquitetura e gerenciamento de projetos ágeis de verdade. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>devops</category><category>cloud</category><category>desenvolvimento-web</category><category>akitando</category></item><item><title>[Akitando] #49 - Devo usar NOSQL? | "ENDGAME" para Iniciantes em Programação | Série "Começando aos 40"</title><link>https://akitaonrails.github.io/2019/04/24/akitando-49-devo-usar-nosql-endgame-para-iniciantes-em-programacao-serie-comecando-aos-40/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/04/24/akitando-49-devo-usar-nosql-endgame-para-iniciantes-em-programacao-serie-comecando-aos-40/</guid><pubDate>Wed, 24 Apr 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/EdOkYEE1J_Y"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente chegamos ao décimo-quarto e último episódio da série &amp;ldquo;Começando aos 40&amp;rdquo;. E eu quis coincidir o término da série com o dia da pré-estreia de Avengers: Endgame, porque eu sou geek assim mesmo :-)&lt;/p&gt;
&lt;p&gt;No episódio de hoje vou dar um pequeno overview sobre o que eu considero como alguns dos principais aspectos de bancos de dados relacionais, NoSQL, e as melhores escolhas pra sua aplicação.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/EdOkYEE1J_Y"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente chegamos ao décimo-quarto e último episódio da série &ldquo;Começando aos 40&rdquo;. E eu quis coincidir o término da série com o dia da pré-estreia de Avengers: Endgame, porque eu sou geek assim mesmo :-)</p>
<p>No episódio de hoje vou dar um pequeno overview sobre o que eu considero como alguns dos principais aspectos de bancos de dados relacionais, NoSQL, e as melhores escolhas pra sua aplicação.</p>
<p>Vou aproveitar pra complementar a discussão de máquinas virtuais, containers, IaaS, PaaS adicionando minhas opiniões sobre FaaS ou Function as a Service.</p>
<p>E no final quero deixar um pensamento sobre esse mundo cloud e o impacto quanto ao mundo de código livre pra todos pensarem.</p>
<p>Se você ainda não assistiu todos os episódios, não esqueçam que tem a playlist com todos os episódios pra maratonar: <a href="https://www.youtube.com/watch?v=O76ZfAIEukE&amp;list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki"target="_blank" rel="noopener">https://www.youtube.com/watch?v=O76ZfAIEukE&list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki</a></p>
<p>Desta vez quero tirar uma folga de uma ou duas semanas antes de retornar então é o melhor momento pra ver todos os vídeos do canal que ainda não assistiu.</p>
<p>Ajudem o canal compartilhando pra todo mundo!</p>
<p>Pular direto pra FaaS: 20:56</p>
<p>Pular direto pra Conclusão: 26:46</p>
<p>Links:</p>
<ul>
<li>
<p>Was MongoDB Ever the Right Choice? (<a href="https://www.simplethread.com/was-mongodb-ever-the-right-choice/"target="_blank" rel="noopener">https://www.simplethread.com/was-mongodb-ever-the-right-choice/</a>)</p>
</li>
<li>
<p>Redis Labs changes its open-source license - again (<a href="https://techcrunch.com/2019/02/21/redis-labs-changes-its-open-source-license-again/"target="_blank" rel="noopener">https://techcrunch.com/2019/02/21/redis-labs-changes-its-open-source-license-again/</a>)</p>
</li>
<li>
<p>NoSQL Databases: An Overview (<a href="https://www.thoughtworks.com/insights/blog/nosql-databases-overview"target="_blank" rel="noopener">https://www.thoughtworks.com/insights/blog/nosql-databases-overview</a>)</p>
</li>
<li>
<p>A SHORT HISTORY OF DATABASES: FROM RDBMS TO NOSQL &amp; BEYOND (<a href="https://www.3pillarglobal.com/insights/short-history-databases-rdbms-nosql-beyond"target="_blank" rel="noopener">https://www.3pillarglobal.com/insights/short-history-databases-rdbms-nosql-beyond</a>)</p>
</li>
<li>
<p>Redis has a license to kill: Open-source database maker takes some code proprietary (<a href="https://www.theregister.co.uk/2018/08/23/redis_database_license_change/"target="_blank" rel="noopener">https://www.theregister.co.uk/2018/08/23/redis_database_license_change/</a>)</p>
</li>
<li>
<p>MySQL Performance: MyISAM vs InnoDB (<a href="https://www.liquidweb.com/kb/mysql-performance-myisam-vs-innodb/"target="_blank" rel="noopener">https://www.liquidweb.com/kb/mysql-performance-myisam-vs-innodb/</a>)</p>
</li>
<li>
<p>Why Amazon DynamoDB isn’t for everyone (<a href="https://read.acloud.guru/why-amazon-dynamodb-isnt-for-everyone-and-how-to-decide-when-it-s-for-you-aefc52ea9476"target="_blank" rel="noopener">https://read.acloud.guru/why-amazon-dynamodb-isnt-for-everyone-and-how-to-decide-when-it-s-for-you-aefc52ea9476</a>)</p>
</li>
<li>
<p>SQLite vs MySQL vs PostgreSQL: A Comparison Of Relational Database Management Systems (<a href="https://www.digitalocean.com/community/tutorials/sqlite-vs-mysql-vs-postgresql-a-comparison-of-relational-database-management-systems"target="_blank" rel="noopener">https://www.digitalocean.com/community/tutorials/sqlite-vs-mysql-vs-postgresql-a-comparison-of-relational-database-management-systems</a>)</p>
</li>
<li>
<p>When Should I Use Amazon Aurora and When Should I use RDS MySQL? (<a href="https://www.percona.com/blog/2018/07/17/when-should-i-use-amazon-aurora-and-when-should-i-use-rds-mysql/"target="_blank" rel="noopener">https://www.percona.com/blog/2018/07/17/when-should-i-use-amazon-aurora-and-when-should-i-use-rds-mysql/</a>)</p>
</li>
<li>
<p>Why Uber Engineering Switched from Postgres to MySQL (<a href="https://eng.uber.com/mysql-migration/"target="_blank" rel="noopener">https://eng.uber.com/mysql-migration/</a>)</p>
</li>
<li>
<p>MySQL Might Be Right for Uber, but Not for You (<a href="https://dzone.com/articles/on-ubers-choice-of-databases"target="_blank" rel="noopener">https://dzone.com/articles/on-ubers-choice-of-databases</a>)</p>
</li>
<li>
<p>Showdown: MySQL 8 vs PostgreSQL 10 (<a href="http://blog.dumper.io/showdown-mysql-8-vs-postgresql-10/"target="_blank" rel="noopener">http://blog.dumper.io/showdown-mysql-8-vs-postgresql-10/</a>)</p>
</li>
<li>
<p>Apache Cassandra Architecture Fundamentals (<a href="https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/"target="_blank" rel="noopener">https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/</a>)</p>
</li>
<li>
<p>Highly available Redis Architecture (<a href="https://medium.com/kokster/highly-available-redis-architecture-613c89f887b4"target="_blank" rel="noopener">https://medium.com/kokster/highly-available-redis-architecture-613c89f887b4</a>)</p>
</li>
<li>
<p>Paper Review: Dynamo: Amazon’s Highly Available Key-value Store (<a href="https://sookocheff.com/post/databases/dynamo/"target="_blank" rel="noopener">https://sookocheff.com/post/databases/dynamo/</a>)</p>
</li>
<li>
<p>Using the HTC G1, 10 years later: 2008&rsquo;s smartphone is effectively a dumbphone in 2018 (<a href="https://www.androidpolice.com/2018/12/24/using-htc-g1-10-years-later-2008s-smartphone-effectively-dumbphone-2018/"target="_blank" rel="noopener">https://www.androidpolice.com/2018/12/24/using-htc-g1-10-years-later-2008s-smartphone-effectively-dumbphone-2018/</a>)</p>
</li>
</ul>
<p>=== Script</p>
<p>Olá, Fabio Akita</p>
<p>Este é o terceiro episódio do tema de devops pra encerrarmos o assunto e ao mesmo tempo vamos finalmente encerrar a série &ldquo;Começando aos 40&rdquo; neste décimo quarto episódio.</p>
<p>No episódio da semana passada eu disse que iria explicar sobre bancos de dados. Eu tentei escrever esse script diversas vezes de diversas formas nos últimos dias, mas é um assunto muito mais complexo do que caberia num único episódio. Eu não vou entrar em detalhes de como eles são implementados, os algoritmos e arquitetura. Eu queria falar sobre clustered indexes, bloom filters, write ahead logs, mvcc, consistent hashing e muito mais. Em vez disso quero só compartilhar algumas opiniões que talvez nem todos concordem, mas que deve ser o mais útil pra maioria das pessoas.</p>
<p>Então quero aproveitar pra falar muito rapidamente de uma coisa que não mencionei no episódio anterior quando falei sobre máquinas virtuais e containers, o tal de Function as a Service ou FaaS que muitos ficam hypando hoje em dia. Finalmente vou fechar com um pensamento de como tudo isso que eu expliquei nos últimos episódios afeta o mundo de código livre. Hoje é dia do Endgame, fiquem comigo!</p>
<p>(&hellip;)</p>
<p>Como primeiro assunto, vamos falar rapidamente sobre bancos de dados e arquitetura. Se você estiver mais interessado em ir direto pra parte de Function as a Service vá pra esse tempo indicado aqui embaixo. E se quiser ir direto pra conclusão, vá pra este outro tempo aqui embaixo.</p>
<p>Se você é iniciante eu não recomendo tentar instalar e configurar manualmente nenhum banco de dados sozinho. Nem os bancos relacionais como MySQL ou Postgres e nem os NoSQL como Redis, MongoDB ou Cassandra. Não adianta só seguir um tutorial e fazer rodar. Garantir que você não vai perder dados importantes é muito mais do que meramente fazer um servidor rodar. Seguir um tutorial é absolutamente trivial.</p>
<p>Todos os bancos tem dezenas de configurações diferentes que devem ser planejadas exclusivamente pro uso da aplicação. Não existe uma única configuração que serve universalmente pra tudo. E não, NoSQLs não são mais fáceis de configurar ou dar manutenção do que os bancos relacionais. Se você tem essa noção de algum lugar, tire isso da sua cabeça. Eu diria que uma infraestrutura NoSQL de verdade dá tanto trabalho ou mais do que bancos relacionais. Especialmente se realmente formos eliminar pontos únicos de falha e tivermos requerimento de estratégias de recuperação de desastres com zero downtime, ou seja, o banco sempre estar disponível mesmo que um dos servidores do cluster sofra algum problema.</p>
<p>Se você for programador aprenda SQL e o modelo relacional corretamente. Você precisa saber como fazer queries decentes. A pior parte de uma aplicação é quando o desenvolvedor acredita que a biblioteca de mapeamento objeto-relacional que ele está usando, seja Hibernate no Java, SQLAlchemy no Python, ActiveRecord no Ruby, Eloquent ORM no PHP, nenhum deles vai fazer queries eficientes pra você. E queries ineficientes é um dos principais ofensores em desperdício de memória e performance. Não adianta trocar de banco se seu código é uma porcaria, nenhum banco vai fazer mágica.</p>
<p>Muita gente acha que usar um MongoDB é bala de prata pra combinar facilidade de uso e performance, especialmente pra iniciantes. Se você não assistiu meu episódio sobre o livro The Mythical Man Month, aqui vai um resumo: não existe bala de prata!</p>
<p>Pra começar, se você subir uma instância de MongoDB e deixar ele sozinho, você já começou errado. MongoDB não tem garantias de 100% de durabilidade. O melhor caso de uso do Mongo é que o dataset caiba inteiro em RAM. Todas as operações são feitas em RAM, é o que garante sua performance. Mas isso significa que se a máquina der pane antes da RAM ter chance de dar flush pro disco, você vai perder dados. Você pode configurar pra que ele garanta mais durabilidade. Por exemplo, hoje quando você manda gravar alguma coisa no Mongo ele devolve ok antes de ter realmente gravado. O ok do Mongo é mais tipo &ldquo;bele, recebi a ordem pra gravar, quando der eu gravo&rdquo;. Diferente de um banco relacional que só dá ok se garantidamente o dado foi gravado. Você pode configurar o Mongo pra fazer o mesmo, devolver ok só se garantidamente uma outra instância de replica master recebeu o dado. Mas aí você perde a performance que era justamente o que te fez escolher Mongo em primeiro lugar.</p>
<p>Aliás, uma instalação mínima de Mongo precisa de pelo menos duas instâncias em replica set. Eu disse que o sweet spot é o dataset caber em RAM, mas você não tem RAM infinita, se você pretende armazenar ordens de grandeza mais dados do que cabe em RAM, talvez precise fazer shards. Mas configuração de shard precisa ser planejada do começo. Mudar de uma configuração sem shard pra ter shard não é só ligar ou desligar uma chave, você vai ter que refazer toda a infraestrutura do zero e mover todos os dados de lugar se escolher errado. Some replicação e sharding pra configurar e você criou um emaranhado de gato pra resolver. Portanto não, um MongoDB não é mais simples do que qualquer outro banco se você realmente vai usar da forma certa. Instalar na sua máquina só pra brincar, qualquer banco é simples, não só o Mongo.</p>
<p>Um NoSQL wide column store como Cassandra, apesar de ter similaridades com bancos relacionais só na superfície, especialmente nos schemas e no CQL que é similar a SQL, não tem absolutamente nada a ver. Eles são como key-value stores multi dimensionais com column families e foram feitos pra serem bancos altamente distribuídos, com múltiplos nós num cluster, de preferência em múltiplas regiões diferentes. Cassandra não foi feito pra usar numa única instância, como banco de um blog. O sweet spot dele é funcionar em cluster, com múltiplos nós, e com muita escrita.</p>
<p>Aliás, a maioria dos NoSQL é alguma derivação de uma estrutura de chave e valor, os key-value stores, como um Hash ou um Dicionário que qualquer linguagem de programação tem. O próprio Mongo pra mim é um key-value onde o value é um documento JSON, por isso chamamos de Document store. O sweet spot deles é localizar um valor tendo a chave em mãos. Diferente de um banco relacional, a maioria não é nem um banco transacional e nem um banco analítico, ou seja, que você pode sair fazendo queries como em SQL com qualquer critério e a qualquer momento. Pesquisas em bancos NoSQL precisam ser planejadas, indexadas com antecedência. NoSQLs tendem a ser bancos que você quer que funcionem em cluster, com shards, ou seja, nenhum servidor tem todos os dados que você precisa ao mesmo tempo, e se sua pesquisa for mal planejada, ela vai ter que ir vasculhando em todos os nós da rede, e isso custa processamento e custa tempo, muito tempo.</p>
<p>E um aviso muito importante que eu já dei em outros episódios: você não é o Facebook, nem o Google, nem o Netflix, nem o Uber. As experiências de engenharia dentro do Uber só tem valor pra outro produto com o mesmo nível de escala do Uber. Aceite a seguinte realidade: os 99,999% de nós jamais vai chegar a uma fração da escala do Uber. Portanto as decisões mais críticas de infraestrutura que eles relatam e que dão excelentes blogs posts e palestras são quase como ficção científica pra gente: é legal de ver, mas não nos impacta de nenhum jeito.</p>
<p>Um exemplo disso foi um blog post incendiário de um engenheiro do Uber de 2016 intitulado: &ldquo;Por que a engenharia do Uber migrou de Postgres pra MySQL&rdquo; acusando o Posgres de ter uma &ldquo;arquitetura ineficiente pra escritas&rdquo;. Se você ler só isso vai sair que nem um idiota dizendo &ldquo;puuts, temos que migrar nossos Postgres pra MySQL porque ele tem defeitos de escrita&rdquo;. Tem idéia de qual imbecil isso soa? Pra começo de conversa, o teor do artigo se refere a limitações do Postgres em casos extremos de grande volume de updates ou atualizações. E novamente, na escala do Uber. Tanto MySQL quanto Postgres são excelentes escolhas pra todos os casos que você precisa de um banco relacional, e que por acaso é a grande maioria dos casos. Programador é muito crédulo, fica acreditando em conto do vigário toda hora, é impressionante. Parem com isso!</p>
<p>Na prática a maioria de nós vai sempre acabar usando uma combinação de um banco relacional com um cache, usando algo como um Redis. Nessa configuração o Redis pode até ser um pouco mais relaxado porque o certo é fazer com que os dados sejam consultados no Redis e o que não estiver lá você carrega do banco relacional e grava no Redis à medida que for precisando. Portanto o Redis pode ser reconstruído do zero mesmo se der pane e precisar derrubar tudo. E você usa o Redis pra manter coisas pesadas de calcular e gravar num banco relacional como agregações, coisas com contadores, médias e outras métricas que agregam múltiplas linhas do banco.</p>
<p>Num banco relacional, o que mais custa performance é escrever, mais do que ler. A escrita precisa ter o tal ACID garantido: atomicidade, consistência, isolamento e durabilidade. Dados escritos precisam garantidamente estar no disco de tal forma que uma pane inesperada não faça você perder dados. Múltiplas conexões escrevendo ao mesmo tempo não podem um pisar no pé do outro, você precisa de um esquema de concorrência como multiversion concurrency control ou MVCC que a maioria dos bancos usa hoje em dia. E você precisa de transaction logs pra conseguir dar rollback ou voltar ao estado original dos dados se uma transação de múltiplas operações dá pau na metade.</p>
<p>Numa aplicação com grande pressão de escrita, digamos um ecommerce em dia de Black Friday, fechando várias compras por segundo. Você deve programar sua aplicação pra usar filas. Toda nova compra entra na fila e vai sendo escrito no banco à medida que der. Você nunca pode só aumentar a quantidade de servidores de aplicação web e deixar conectar todo mundo no mesmo banco ao mesmo tempo, isso só vai aumentar a contenção e no final ninguém consegue escreve. Então é melhor todo mundo entrar numa fila e só a fila gerenciar o banco. As leituras usam caches de Redis e isso tira a carga de pesquisa no banco. O grande gargalo numa aplicação web é o banco de dados, porque mesmo com replicação e sharding, você sempre vai ter contenção por causa das garantias ACID.</p>
<p>Além disso, num banco relacional você aprende na faculdade a idéia de normalização, onde separa os fatos em tabelas e faz referências entre as tabelas, de forma a diminuir ao máximo a redundância dos dados entre tabelas. Do ponto de vista de modelagem, assim é mais bonito. Mas uma verdade é que do ponto de vista de performance, quanto mais você normalizar, pior pode ser a performance. Tanto bancos relacionais quanto NoSQL privilegiam tabelas denormalizadas. Você faz trade-off de redundância e mais uso de disco pra ter mais performance. Uma coisa cara no processamento de pesquisas é justamente fazer joins entre tabelas, por isso bancos como Postgres oferecem recursos como views materializadas. Views é como se fosse uma tabela lógica, que não existe, porque é o resultado dinâmico de uma query, mas uma view materializada é como se fosse uma tabela denormalizada de verdade.</p>
<p>Ou seja, numa aplicação de verdade, você talvez se veja obrigado a denormalizar algumas tabelas. Um jeito errado de usar um MongoDB ou Cassandra por exemplo, é granular as coisas demais e tentar fazer o equivalente a joins. NoSQL não foi feito pra ter joins e relacionamentos. E você vai descobrir que muitos problemas de performance que você tem com bancos relacionais é normalização demais. Pense em sharding e se você deixar uma tabela num servidor, outra tabela com dependências em outro servidor, como você vai fazer joins rápida entre tabelas em servidores diferentes? Como tudo em computação, não existe uma forma de quanto é normalizar demais. É uma arte, muita tentativa e erro em diferentes casos de uso.</p>
<p>Um exemplo bem idiota, a famosa tabela de estados. A grande maioria dos programadores faz uma tabela separada chamada estados com uma coluna de código pra colocar por exemplo SP e outra coluna de nome pra colocar São Paulo. Daí numa outra tabela de cadastro você tem um campo estado_id que é uma referência pra linha da tabela de estados. Agora toda vez que você puxar o cadastro precisa fazer um join com a tabela de estados. Eu acho isso desnecessário. Eu colocaria SP e São Paulo direto na linha da tabela de cadastros e um select simples, sem join, vai devolver tudo que eu preciso. Mas aí toda linha vai duplicar São Paulo toda vez. Foda-se. A probabilidade do estado de São Paulo mudar de nome é praticamente zero. Se um dia mudar sim, vai custar um pouco caro dar update em todas as linhas de todas as tabelas que ficou com isso duplicado, mas isso é uma possibilidade remota. Pra que eu vou normalizar dados que nunca mudam?</p>
<p>Mas isso significa que não pode haver o caso de alguém mandar gravar São Paulo com til e de outro lugar alguém mandar gravar um Sao Paulo sem acento. A letra C de consistência do ACID vem migrando do banco pra aplicação faz algum tempo e se você começar a denormalizar, vai ter que garantir essa parte da consistência na aplicação. Vamos assumir que seu código não é porco, que você colocou enums no seu código e validações antes de mandar gravar no banco. Lógico, novamente é um trade-off, vou trocar performance por mais trabalho. Como eu disse, computação é uma arte, não existe almoço de graça, se pra você performance é mais importante, vai ter mais trabalho. Se pra você conveniência é mais importante, você paga tendo menos performance. Aliás, pra ter mais segurança, mais disponibilidade, mais durabilidade, mais integridade, tudo isso você paga tendo menos performance. Se quiser aumentar a performance, alguns desses fatores devem cair, por isso fica esperto!</p>
<p>E no final o que você vai ter com mais frequência é uma solução híbrida. Independente da sua linguagem favorita ou framework web, o mais comum é você usar um banco relacional. Eu pessoalmente gosto de Postgres mas não há nenhum motivo pra não usar MySQL com InnoDb ou mesmo seus forks como as versões da Percona ou o MariaDB. Você vai acabar movendo a consistência pra aplicação, com boas validações e bons testes no seu código. Também vai acabar denormalizando algumas tabelas pra evitar joins desnecessários, sempre criar índices corretamente, mas também evitar criar índices demais porque quanto mais índices pior a performance de escrita no banco.</p>
<p>Você também quer evitar gargalos de leitura e gargalos de escrita. Pra otimizar a leitura, você quer colocar um cache na frente do banco relacional. Você vai programar pra primeiro procurar o que quer num Redis. Se não achar lá, ou seja, tiver um cache miss, daí vai no banco relacional puxar os dados e gravar no Redis pra estar lá da próxima vez; com algum período de tempo pra expirar esse dado. E opcionalmente, se não quer o risco de parar o banco caso venham escritas demais, você vai programar sua aplicação pra colocar o pedido de escrita numa fila, seja no próprio sistema de publish/subscribe do Redis ou numa fila de verdade como RabbitMQ. Significa que o tempo geral de escrita deve ser mais lento do que mandar direto pro banco, mas se por acaso vier um pico inesperado que o banco não está preparado pra receber, em vez dele dar timeout e perder as operações, é sempre melhor ficar mais lento mas que garantidamente uma hora vai executar.</p>
<p>E não, programar com um framework assíncrono, seja Node.js ou qualquer outro que hoje em dia suporte async/await também não é uma bala de prata. Ajuda um pouco, mas não resolve o problema de picos e gargalo de concorrência no banco. No dia da Black Friday o que resolve o problema são filas, estruturas que você consegue controlar. async/await são boas pra ajudar, mas são só mais uma ferramenta e nunca a única solução que vai resolver tudo.</p>
<p>E se você precisar fazer uma coisa chamada Full text Search, que é pesquisas como você faz numa Amazon, onde você encontra produtos similares e tem resultados por relevância, você tem suporte a isso tanto no MySQL quanto no Postgres, mas na idéia de tirar o máximo de processamento do banco, o certo é manter um outro serviço só pra isso, no caso um Elasticsearch que é um dos melhores pra full text search. Toda vez que você escrever no banco relacional, também manda escrever no Elastic. Novamente vamos gastar mais recursos pra ganhar performance dos dois lados. Trade-off.</p>
<p>Se você usar Heroku, significa sua aplicação web rodar em um ou mais dynos web, e você ter um ou mais workers assíncronos que rodam em dynos de workers. Daí um Heroku Postgres, configurado com um follower pra onde você vai redirecionar as queries mais pesadas. Adicione também o Heroku Redis ou outro SaaS como Redis Cloud pra cache. Eu adicionaria um Cloudinary se sua aplicação precisar armazenar e processar uploads, um SendGrid se precisar mandar e-mails, um Rollbar pra guardar logs de erro, um New Relic RPM pra monitorar a performance da aplicação e um Bonsai Elasticsearch pra funcionalidades de pesquisa avançada. Espere gastar algo em torno de 200 a 300 dólares ou uns 1000 reais por mês. Parece muito? Não é, porque se você pretende instalar e configurar e manter tudo sozinho, você precisa de um sysadmin que vai te custar 5 a 10 vezes isso por mês. E isso só compensa se você for manter pelo menos 10 aplicações iguais a essa.</p>
<p>Se você realmente for manter parte disso sozinho, você que é sysadmin vai acabar usando um IaaS como a AWS ou Google Cloud. Nesse caso vai configurar as coisas com Kubernetes, Helm, Terraform e mesmo assim o ideal vai ser manter o banco de dados gerenciado em produtos como o AWS RDS ou AWS Aurora ou Google Cloud SQL. O banco de dados é a pior parte pra se manter, acredite. O trabalho simplesmente não compensa a menos que você seja muito grande. Não estou nem dizendo tamanho do Uber, mas você precisa estar lidando com terabytes diários de dados críticos pra compensar uma solução sob medida.</p>
<p>É tudo uma questão de gerenciamento de risco, um trade-off entre pagar mais pra ter mais segurança. Quanto custa pro seu negócio ficar fora do ar por 2 dias numa Black Friday? Quanto custa pro seu negócio perder os dados financeiros de meio dia de negociações? Na verdade, quanto custa perder 1 dia dos seus dados? Se você disser que não tem nenhum problema, eu questionaria a importância da existência do seu produto.</p>
<p>Uma última dica de infraestrutura: independente se você colocar sua aplicação numa máquina virtual de um VPS ou no AWS EC2 ou em containers via Kubernetes, ou mesmo numa plataforma como serviço como o Heroku ou Openshift, em qualquer dos casos contrate a Cloudflare. Ele é um serviço que vai substituir seu DNS, toda vez que alguém digitar seu domínio num navegador, o tráfego vai todo pra Cloudflare primeiro, e ele vai tanto servir como CDN que é um cache pra coisas como as imagens e outros assets do seu site como vai servir de detecção e prevenção de ataques. Se algum hacker ou algum moleque com um script tentar invadir seu site, o Cloudflare vai ser sua primeira linha de defesa e por experiência vou dizer que eles são muito bons. Altamente recomendado. Custa caro, mas de novo, quanto custa pra você se alguém entrar no seu site e roubar os dados dos seus clientes? Ou causar um Denial of Service no seu ecommerce no pico da Black Friday?</p>
<p>Como segunda seção deste vídeo, deixa eu explicar Function as a Service ou FaaS, que são produtos recentes como o AWS Lambda ou Microsoft Azure Functions ou Google Cloud Functions. Eles são basicamente containers. Se você já usou coisas containers de Docker ou dynos de workers do Heroku, a idéia é similar. A implementação varia entre os diferentes fornecedores então cuidado porque não é um padrão! Você configura um evento ou um trigger, que pode ser várias coisas, uma chamada HTTP, um pub/sub de algum serviço de fila como o AWS SQS, ou o Firebase no caso do Google Cloud. Quando esse evento vier, ele vai subir um container pequeno que vai executar essa sua tal lambda ou função que está englobada num frameworkzinho específico pra plataforma. Dizer lambda ou functions é mais marketing do que qualquer outra coisa, se você ainda não tinha notado, pra aproveitar esse hype em cima de linguagens funcionais, mas não tem nenhuma relação.</p>
<p>Os containers já vem pré-configurados com imagens pra diversos frameworks como Node.js, ou Ruby, ou Go, ou Python, ou Java, ou C#. A idéia não é rodar um framework web monolítico como Ruby on Rails, ou ASP.NET MVC, ou Play Framework. Repetindo: sim, como é um container comum, que roda uma linguagem conhecida, que inclusive permite você instalar dependências externas, então tecnicamente você pode rodar o quiser nesses containers. Mas só porque você pode, não quer dizer que você deve. Sempre existe a ferramenta certa pro problema certo e na minha opinião FaaS não foram feitos pra hospedar sites grandes inteiros.</p>
<p>Máquinas virtuais de um IaaS como AWS EC2 ou mesmo num VPS tendem a ter um SLA alto. Eles não garantem que a máquina vai ficar de pé pra sempre, mas a intenção é ficar de pé por um longo período de tempo. Semanas ou meses. Num PaaS como Heroku, os containers que ele chama de dynos também tendem a ficar de pé por longos períodos, dias ou semanas. Mas não é uma garantia, por isso ele força você a não depender de gravar coisas no disco local, porque os dynos são containers voláteis. Você deve programar de tal forma que se um dyno restartar, a aplicação reinicie sem problemas e tudo que você precisa está em banco de dados ou caches externos.</p>
<p>Um container de função tende a ser menor e a ficar de pé por períodos ainda menores, horas, no máximo dias. A intenção é que você não se preocupe de manualmente ter que subir esses containers. Eles vão sendo iniciados a partir desses triggers ou eventos. A função deveria receber os dados do evento, processar o que precisar o mais rápido possível, e assumir que ele pode desligar depois disso. Na prática o AWS ou Google vai manter o container de pé por mais algum tempo caso outros eventos estejam esperando na fila. Mas se ficar sem fazer nada por muito tempo ele deveria desligar pra não desperdiçar recursos.</p>
<p>Como é possível receber um trigger de tipo HTTP ou seja, você poderia apontar seu navegador pra uma URL que seja gatilho pra ativar uma função, e de dentro da função você poderia conectar num banco de dados e devolver um HTML, você pode ficar tentado a colocar um site, ou pior, uma aplicação web inteira. E vai funcionar. Existem alguns frameworks web mais enxutos feitos pra tentar tirar vantagem disso.</p>
<p>Se você não tem problema em ter alta latência e tempo de resposta demorado de tempos em tempos, pode funcionar sim. Mas você precisa garantir que sua aplicação seja pequena. Como os containers de funções podem desligar a qualquer momento, quanto maior for sua aplicação mais vai demorar pra ele levantar um novo container no próximo evento. Eu pessoalmente acho que usar AWS Lambda e outros FaaS pra servir aplicações web é o uso errado da tecnologia.</p>
<p>Lembram como eu falei agora pouco que uma das formas de evitar gargalos no banco de dados é jogar o pedido de escrita numa fila e depois um worker que ouve essa fila ir escrevendo no banco? Esse seria um jeito. Você enfileira os pedidos no AWS SQS que é uma fila, conecta ele como event source no AWS Lambda, faz uma função que conecta no AWS Aurora e aí grava no banco. Ou outras coisas pesadas pra sua aplicação como processar imagens ou vídeos. Você manda uploads da sua aplicação pro AWS S3, daí manda enfileirar a ordem no AWS SQS de novo, que vira event source pra um Lambda que vai puxar o arquivo do S3, processar e jogar de volta no S3 ou manda arquivar no AWS Redshift.</p>
<p>Eu diria, de maneira simplória, que o AWS Lambda está pra AWS como um dyno de worker está pro Heroku. Ele não é nenhuma grande revolução como muito palestrante ou blog post faz parecer. Ele é mais um sub-produto do uso de containers pra tentar desperdiçar menos recursos. Em vez de você subir um EC2 inteiro e deixar ele esperando sem fazer nada até vir alguma coisa pra rodar, melhor criar um container que vai ficar simplesmente desligado se não tiver nenhuma atividade. Ele é melhor pra aplicações que não tem tanta atividade assim o tempo todo, por exemplo, quando de noite ou de fins de semana não vem nenhum novo evento ou vem muito pouco.</p>
<p>E nos casos onde sua aplicação tem picos difíceis de prever, com esse sistema ele pode subir múltiplos containers se a fila estiver com muita pressão. Então Function as a Service é só isso: um container com um código simples dentro de uma função, preferencialmente, que vai responder a eventos pré-configurados, seja de uma fila, seja um gatilho via HTTP, com o mínimo possível de configuração.</p>
<p>Como terceiro e último tema de hoje, quero compartilhar um pensamento com vocês. Eu não sou um idealista, eu não sou um ativista: eu gosto de pensar que sou mais uma testemunha da história. Se possível eu prefiro tentar explicar as coisas da forma como eu vejo, mas não julgo ninguém por estar de um lado ou de outro porque só a história vai realmente dizer quem estava certo. Eu prefiro estar atento e me mantendo anti-frágil às mudanças. Quem leu Nassim Taleb, sabe o que isso significa.</p>
<p>Faz uns 30 anos que estamos vivendo num mundo de tecnologia dividido entre o mundo de código livre e o de código proprietário. Todos sabem que até o fim dos anos 90 o mundo de código proprietário sempre foi dominante. Mas principalmente a partir do novo século que o mundo de código livre evoluiu muito rápido. Hoje em dia todo desenvolvedor que se preza usa ferramentas derivadas do mundo de código aberto. Tudo que eu vim falando nessa série toda, incluindo distribuições Linux, Docker, Kubernetes, MySQL, Redis, tudo isso é mundo de código livre.</p>
<p>Em 1995 Bill Gates soltou o famoso memorando interno na Microsoft explicando a ameaça do navegador à hegemonia do sistema operacional Windows e outros produtos como o Office. Em breve o navegador substituiria o sistema operacional. E ele estava certo.</p>
<p>Considere o seguinte: antigamente precisaríamos comprar um servidor de e-mails como um Microsoft Exchange. Finalmente surgiram opções de código aberto como Zimbra, Open Xchange e vários outros. A mesma coisa foi acontecendo com todo software proprietário. E por um tempo pareceu que o modelo estava funcionando e o mundo open source foi ganhando força até que as versões abertas passaram a superar as fechadas em muitos lugares, em particular em tecnologias de web, redes, servidores e infraestrutura.</p>
<p>Então surgiu o Gmail em 2006 e na sequência a Amazon e seu AWS, que inaugurou o conceito de empacotar as coisas como serviço, e expor esses serviços como APIs, em uma escala que não se imaginava. E muitas empresas seguiram o mesmo modelo, oferecendo software como serviço. Antes você pagava licenças caras pra comprar ou assinar pelo uso de software proprietário. O que mudou agora é que com os canais de distribuição da internet podemos fazer a mesma coisa que antes, que era comprar software encaixotado de prateleira ou via consultoria ou vendedores. Então o que realmente mudou foi o preço ser muito menor porque o software consegue chegar em mais pessoas e empresas.</p>
<p>Mas note: voltamos ao mesmo modelo dos anos 90. Você paga uma assinatura mas não é proprietário do software. Pior: você não tem acesso ao código do software, porque ele funciona fechado atrás do que chamamos de um walled garden ou jardim emparedado. É um jardim bonito, mas lembre-se que ele não é aberto. Apesar da Amazon, Google, Facebook, Netflix, Uber e todos os outros de fato colaborarem ainda com software livre porque seria burrice não fazer isso, já que todos usam Linux e outras ferramentas.</p>
<p>Mas o software que torna a Amazon a Amazon ou o Google o Google jamais vai ser aberto. O AWS é o Windows da Amazon. O Google G Suite é o Office do Google. Custa muito caro criar uma versão aberta que seja igualmente competitiva, especialmente porque estamos falando de larga escala. Quanto mais eles crescerem, mais difícil vai ser pra qualquer outra empresa conseguir competir com eles. Então é um mercado com uma barreira de entrada enorme. E você basicamente trocou o Microsoft Office proprietário pelo Google Docs que também é proprietário.</p>
<p>Na prática, a maior parte do software que realmente dependemos no nosso dia a dia é proprietário, mas achamos que está tudo bem porque roda num navegador e não no Windows, mas tudo que tem acesso livre na verdade é pago com os dados da sua privacidade - e eu nem sou necessariamente contra, mas se você de alguma forma achava que é idealista de software livre usando tecnologias do Google ou Facebook, hoje já está num mundo fechado na sua maior parte, e você é equivalente a ser evangelista da Microsoft nos anos 90. De novo, nenhum problema nisso, só esclarecendo que não é o que você achava.</p>
<p>Considere que muito do que hoje é mantido como software livre não são nada mais do que bibliotecas que consomem APIs. Eles sozinhos não servem pra nada, são apenas cascas em cima de clientes de HTTP. Toda biblioteca da Amazon, do Google, do Facebook conectam com os serviços deles e só servem pra isso. Muitos desenvolvedores de software livre como o Salvatore que criou o Redis tem reclamado disso faz meses. Eles chegaram a mudar a licença pra uma mais controversa que impede que empresas como Amazon ou Google possam usar produtos como o RedisSearch, RedisGraph, RedisJSON e outros produtos.</p>
<p>Aos poucos você vê uma Amazon agindo como a Microsoft dos anos 90, adotando software de fora, e criando modificações que atraiam as pessoas pra sua versão proprietária e depois fechar as pessoas dentro de seu walled garden. Veja o AWS Aurora que é um MySQL modificado e cuja modificação não é open source. Daí eles lançam um AWS Lambda e uma gama de outros produtos que basicamente vão prender você dentro da AWS. Uma vez que você adotar esses produtos e seu negócio realmente der certo, você não vai mais sair de lá. Lembra o que os desenvolvedores reclamavam de Windows e Office? A AWS é o novo Windows e Office caso ainda não tenham percebido.</p>
<p>Eu comecei a pensar em outra coisa por causa de retrogaming e meu hobby gostando de tecnologias antigas. Se eu pegar um computador dos anos 80, um Commodore 64, eu consigo rodar todos os softwares daquela época. Se eu pegar um Gameboy, ainda consigo rodar todos os cartuchos. E como foram feitos dumps dos cartuchos, CDs e DVDs, então todos os jogos foram preservados em algum site. Essa parte do passado está preservada pra sempre.</p>
<p>Mas se eu pegar um celular Android de primeira geração, o HTC G1, muitos dos serviços nele não funcionam. Porque eles são só cascas pra software as a service que já não existe mais. Vários hardwares de 10 anos atrás vão começar a falhar sem serviços pra conectar. Consoles de videogame é outro exemplo. Se a Playstation Network decidir que nenhum PS3 pode mais logar na rede deles, você basicamente perdeu o uso desse hardware. Todo jogo online daqui a 20 anos, sem os servidores proprietários, não vão servir pra mais nada. Não há como preservar mais esse software. Daqui 40 anos uma geração não vai ter como saber o que se usava nos anos 2010 a não ser por fotos de tela. Eu posso rodar o Office de 1995 hoje se quiser, mas não poderemos mais rodar o Google Docs de 2019 daqui a alguns anos.</p>
<p>E a direção dos software as a service indicam uma mudança de paradigma. Um mundo ideal de um Uber é onde a maioria das pessoas não precisasse mais comprar carros. Uber é carro como serviço. Pra um AirBnb o mundo ideal é onde a maioria das pessoas não precisasse mais comprar casas. AirBnb é casa como serviço. iFood é comida como serviço. Netflix é entretenimento como serviço. Spotify é música como serviço, você não tem mais propriedade sobre sua mídia de música ou filmes. E assim por diante.</p>
<p>Como eu disse antes, em tecnologia tudo é um trade-off. Estamos trocando propriedade por conveniência. Eu não digo que isso é bom ou ruim, o veredito ainda não foi definido. Mas eu achei interessante colocar como pensamento final desta série. O mundo da tecnologia desta década é diferente de 10 anos atrás, que foi diferente de 20 anos atrás, então não espere que daqui a 10 anos vai ser igual agora, você pode esperar uma mudança geral de ecossistema e tecnologias mais ou menos a cada 10 anos.</p>
<p>E com esse pensamento, eu tenho o prazer de finalmente encerrar esta série! Eu realmente não pensei muito quando comecei. Imaginava que fosse encerrar em 3 ou 4 episódios e acabou se tornando uma mini-série de 14 episódios! Eu publiquei o primeiro episódio em 22 de Janeiro e não pulei nenhuma semana. E eu quis encerrar exatamente hoje porque coincidiu de ser o dia do Endgame, claro!</p>
<p>Depois desse projeto eu quero tirar uma ou duas semanas de folga do canal e ver o que quero gravar a seguir. Não tenho dúvidas que só toquei na ponta do iceberg ainda, mas espero que tenha atiçado a curiosidade de todo mundo que queira se tornar um desenvolvedor. Mandem dúvidas nos comentários abaixo e sugestões de novos assuntos. Se curtiram o vídeo mandem um joinha, e por favor compartilhem o video pra ajudar o canal. Não esqueçam de assinar abaixo e clicar no sininho pra saberem quando eu voltar. A gente se vê na próxima, até mais!!</p>
]]></content:encoded><category>bancos-de-dados</category><category>cloud</category><category>arquitetura-de-software</category><category>akitando</category></item><item><title>[Akitando] #48 - Entendendo "Devops" para Iniciantes em Programação (Parte 2) | Série "Começando aos 40"</title><link>https://akitaonrails.github.io/2019/04/17/akitando-48-entendendo-devops-para-iniciantes-em-programacao-parte-2-serie-comecando-aos-40/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/04/17/akitando-48-entendendo-devops-para-iniciantes-em-programacao-parte-2-serie-comecando-aos-40/</guid><pubDate>Wed, 17 Apr 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/mcwnQVAn0pw"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Hoje finalmente vamos usar tudo que aprendemos até agora pra explicar as diferenças entre hypervisors e containers, e agora sim, falar um pouco mais de Docker e Kubernetes.&lt;/p&gt;
&lt;p&gt;Precisamos explicar o que foi essa mudança no mundo de sysadmins de ter que lidar com hardware pra um mundo onde hardware essencialmente se tornou software pra muitos casos, especialmente em Web e como empresas como a Amazon AWS e Heroku ajudaram a mudar inclusive a forma como programamos.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/mcwnQVAn0pw"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Hoje finalmente vamos usar tudo que aprendemos até agora pra explicar as diferenças entre hypervisors e containers, e agora sim, falar um pouco mais de Docker e Kubernetes.</p>
<p>Precisamos explicar o que foi essa mudança no mundo de sysadmins de ter que lidar com hardware pra um mundo onde hardware essencialmente se tornou software pra muitos casos, especialmente em Web e como empresas como a Amazon AWS e Heroku ajudaram a mudar inclusive a forma como programamos.</p>
<p>Links:</p>
<ul>
<li>Goodbye Docker and Thanks for all the Fish (<a href="https://technodrone.blogspot.com/2019/02/goodbye-docker-and-thanks-for-all-fish.html"target="_blank" rel="noopener">https://technodrone.blogspot.com/2019/02/goodbye-docker-and-thanks-for-all-fish.html</a>)</li>
<li>The Twelve-Factor App (<a href="https://12factor.net"target="_blank" rel="noopener">https://12factor.net</a>)</li>
</ul>
<p>=== Script</p>
<p>Olá pessoal, Fabio Akita</p>
<p>Este é o décimo terceiro episódio da série Começando aos 40, e parte 2 do tema de devops. Recapitulando, semana passada eu expliquei como saímos das hospedagens compartilhadas e colocations dos anos 90 para as VPSs e opções baseadas em tecnologias de virtualização. Entendemos as diferenças de full virtualization, paravirtualização e um pouco de containers e hoje vamos entrar na melhor década do século XXI até agora pra gente. Pra muita gente a revolução na área veio com Docker e pra outras pessoas esse período pode estar chegando ao seu fim. Vamos ver como!</p>
<p>(&hellip;)</p>
<p>Como eu disse no episódio anterior, o mundo de infraestrutura não estava preparado para o boom que começou a acelerar principalmente a partir de 2005. Unicórnios estavam se proliferando, crescendo em demanda muito rápido e nenhum data center estava preparado para eles. Aliás, no episódio anterior eu mencionei VPS mas não expliquei direito o que são. Virtual Private Servers, basicamente são serviços onde você escolhe um tipo de máquina virtual, quanto de CPU, quanto de memória, de disco e que sistema operacional e paga uma mensalidade por máquina. Na época eu gostava de usar os da Linode mas tinha vários outros. Era como alugar uma máquina num data center, só que virtual, então mais barato e mais fácil.</p>
<p>A própria Amazon, no começo dos anos 2000 estava passando por problemas de escala. Cada novo projeto levava 3 meses só pra configurar a infraestrutura, as máquinas, os bancos de dados, a rede. E fazendo tudo do zero toda vez. Sendo um ecommerce estamos falando de um negócio de margem baixa, por isso cada centavo conta. E nesse caso foi natural juntar anos de experiência criando essa infraestrutura do zero pra criar um serviço pra automatizar e gerenciar todos esses recursos, mitigando essa necessidade de fazer tudo do zero o tempo todo e foi assim que em 2006 a Amazon lança os produtos EC2 pra máquinas virtuais, o S3 pra blob storage e o SQS que é o Simple Queue pra filas - um componente importante pra escalabilidade como já expliquei nos episódios sobre concorrência. Logo depois a AWS lança o EBS que é o Elastic Block Storage que são volumes montáveis nas máquinas EC2 pra ser a segunda opção de armazenamento.</p>
<p>A grande vantagem de máquinas EC2 é você poder criar, ou provisionar uma máquina, criar um volume EBS, anexar esse volume à máquina EC2, e de dentro dela montar o volume no sistema operacional, formatar e usar como se fosse um disco normal. Dessa forma tudo que você instalar ou configurar ficaria persistido nesse volume. Se amanhã você quisesse criar uma segunda máquina bastava criar um snapshot da máquina e carregar num segundo EC2 ou se quisesse aumentar a capacidade computacional da máquina, podia literalmente apagar a máquina, criar outra maior e anexar o mesmo volume e tudo funcionaria praticamente plug and play.  Descrevendo desse jeito se parece com qualquer VPS, então o que foi tão diferente assim?</p>
<p>A AWS fez duas coisas muito diferentes dos VPS: cobrança e controle. Primeiro foi o modelo de bilhetagem onde você pagaria por hora de uso dos recursos, ou por megabytes usados no caso do S3. Em vez de pagar uma mensalidade pela máquina você pagaria pelo uso real. Esse tipo de granularidade, mais próxima da minutagem de celulares nunca tinha sido tentada em hospedagens. Ou seja, se você decidisse que só esta semana precisa de uma máquina maior, podia fazer a mudança, depois voltar pro tipo de máquina menor, com facilidade e pagando só pelas horas usadas.</p>
<p>Mais importante: você podia controlar tudo via APIs, sem precisar entrar num dashboard. De repente você queria criar e apagar máquinas com mais frequência, pra tirar vantagem desse modelo. Uma vantagem de ser a Amazon é que eles começaram rapidamente a replicar o modelo em múltiplas regiões geográficas diferentes, permitindo que grandes empresas pudessem escalar rapidamente e com redundância entre diferentes data centers, com a facilidade de enviar um comando numa API. Esse tipo de granularidade de cobrança e de controle simplesmente não existia.</p>
<p>Acho que aqui podemos dividir a história da infra de web em 2. O antes e depois da AWS, com o advento de lidar comercialmente com máquinas da mesma forma como lidamos com software. Deixa eu explicar. Desde sempre na história da informática tínhamos uma polarização: desenvolvedores de software gostam e precisam mexer no software o tempo todo, pra corrigir bugs, pra adicionar novas funcionalidades que vão atrair mais pessoas. Já um sysadmin, ou dba ou administrador de redes, depois que terminam de configurar tudo, eles querem mexer na infraestrutura o menos possível, de preferência nunca mais. Software vs Hardware, soft vs hard.</p>
<p>Isso porque infraestrutura como vocês puderam ver até agora sempre foi um House of Cards … não, não esse … esse outro, isso, literalmente. Racks, roteadores, cabeamento, HDs, metal e parafusos. Um monte de peças de lego montados um em cima do outro. Assopre muito perto e a casa despenca. Basta uma linha de configuração errada e você comprometeu a segurança e integridade de tudo. Basta um comando de formatar o drive errado e você perdeu todos os dados.</p>
<p>Isso me lembrou de um caso que eu vi acontecer anos atrás. Um operador de data center fez um script às pressas pra resolver um problema. No Bash você tem várias variáveis globais que começa com cifrão, não lembro o que ele queria fazer mas no final ele digitou uma letra errado. Esse script foi enviado via SSH pra dezenas de servidores reais. Talvez tenha sido o $0 que pega o último comando rodado. De repente dezenas de máquinas ficaram sem o Bash! Logar remoto via SSH não funcionava porque não tinha mais o Bash pra abrir um novo terminal. A única forma de voltar a ter acesso nas máquinas foi ir até o data center e várias pessoas com pen drives conectarem fisicamente, espetar os pen drives, e copiar o binário do bash de volta. Foi meio tenso.</p>
<p>Por isso um bom sysadmin precisa ser uma pessoa paranóica, que checa e recheca o que está fazendo, que vai fazer o possível pra ter redundância de tudo, que vai fazer backup do backup, que vai checar que o backup de fato funciona pra restaurar, que vai monitorar cada minuto de cada servidor pra ver se alguma coisa está acontecendo. E mesmo assim, num momento de emergência, depois de varar 2 noites, uma letra errada num script pode gerar uma catástrofe.</p>
<p>Enfim, dependendo do seu projeto, podia levar dias ou até meses pra coordenar a configuração de uma nova infraestrutura pra um projeto. Mexer nesse house of cards … depois de tudo pronto era proibido, você tinha que tratar essas coisas como se fosse material nuclear: só pessoal autorizado podia criar um novo índice numa tabela num banco de dados ou instalar qualquer pacote novo. Rodar scripts direto no servidor, nem pensar. No fim dos anos 90 era caos, mas à medida que entramos nos anos 2000 e o mundo enterprise começou a adotar tecnologias de web, a cultura corporativa foi sendo adotada primeiro.</p>
<p>Foi só depois da recuperação da crise da internet de 2001, depois que os unicórnios que falei antes começaram a aparecer, que essa cultura entrou em conflito com as necessidades de escalar mais rápido. A cultura de infraestrutura dessa época, regada a coisas como ITIL era insuficiente. Não é a mesma coisa mas ITIL era como se fosse o Waterfall no mundo de software ou os atuais Pseudo Agile pra gestão de projetos, um conjunto de processos e rituais burocráticos e corporativistas.</p>
<p>Dentro de um lugar como Twitter ou Amazon você teria guerra civil entre desenvolvedores e sysadmins se as coisas continuassem do mesmo jeito. Em projetos pequenos, os VPS ajudaram porque era fácil dar máquinas virtuais pra desenvolvedores, mas eles ainda não tinham tudo que se precisava pra escalar. As ferramentas de gestão de configuração como Chef ou Puppet também ajudaram os sysadmins porque agora o que levava dias pra configurar levaria horas ou minutos.</p>
<p>Mas a divisão entre os dois lados continuaria, um desenvolvedor de software simplesmente não tem conhecimento ou experiência suficiente pra ganhar acesso de entrar numa máquina de produção. Ninguém quer correr o risco de alguém não autorizado entrar e deixar uma brecha de segurança pra trás. E da forma como se fazia software até esse ponto, você fazia deployment da aplicação no servidor e alguém precisava entrar na máquina via um terminal pra acertar arquivos de configuração, acertar tabelas e índices no banco, rodar alguma rotina extra pra instalar dependências que faltaram e coisas assim.</p>
<p>Mas quando a AWS liberou máquinas virtuais como um serviço tivemos uma quebra de paradigma. A somatória das tecnologias que falei até agora facilitaram uma coisa que antes era muito difícil: destruir e recriar máquinas em poucos segundos. Pra começar, a AWS era muito maior que qualquer VPS. Só isso já eliminou a necessidade de precisar ter máquinas físicas pra maioria das médias e até grandes empresas que não tinham políticas antiquadas e aceitavam mais riscos.</p>
<p>Agora os sysadmins ganharam a capacidade de criar imagens e oferecer essas imagens aos desenvolvedores rapidamente. Era muito fácil criar ambientes de teste separados do ambiente de produção mas ambos configurados exatamente iguais, de forma que o que funcionava em ambiente de teste garantidamente funcionaria nas máquinas de produção. E o melhor, a custos muito menores e com menos necessidade de planejar com tanta antecedência porque não havia coisas como mensalidades que são compromissos longos.</p>
<p>Entenda uma mudança de papéis: antes os sysdadmins lidavam diretamente com bare metal. Instalavam os servidores reais em racks físicos e ligavam cabos de redes e configuravam direto na máquina com teclado e monitor conectado no hardware. Com as VPS e depois a AWS, nasceu uma segunda categoria de sysadmins que não precisavam mais ter contato com equipamento físico, a menos que trabalhassem em data centers. As empresas não precisavam mais se preocupar em comprar hardware e sim em contratar serviços.</p>
<p>Com as tecnologias de virtualização e containerização que falei no episódio anterior, nós essencialmente empacotamos uma máquina virtual como um software. E foi criado um novo modelo de negócios em cima dessa nova categoria de software. Por agora vamos só entender que no momento em que o provisionamento de máquinas virtuais se tornou programático, via APIs, passamos a tratar a infraestrutura como software. Assim nasce a Infraestrutura como Serviço ou IaaS.</p>
<p>Surgiram esforços open source pra clonar os conceitos proprietários da AWS como o Eucalyptos que a Canonical adotou mas hoje já meio que morreu, e o OpenStack que foi adotado por centenas de empresas e veio evoluindo bastante nos últimos anos e é composto por dezenas de componentes como o Nova que orquestra hypervisors como o Hyper-V ou vSphere, o Neutron que cuida de tudo relacionado com rede incluindo detecção de invasão, enfim, dezenas de componentes.</p>
<p>E o OpenStack só iniciou mesmo a partir de 2010, ou seja 4 anos depois da AWS, só pra vocês entenderem que o que a AWS inventou em 2006 era difícil de replicar. Levou anos pra aparecer concorrentes de verdade. Foi só depois de 2010 que ganhamos boas opções comerciais como Microsoft Azure e Google Cloud. Eles estão competindo bem e as opções são equiparáveis na maioria dos produtos. Como a gama de opções de cada uma é muito grande então diferentes pessoas vão ter críticas de coisas diferentes em diferentes cenários. Não tem um vencedor hegemônico.</p>
<p>Até aqui eu dei uma rápida passada pelos conceitos de paravirtualização e virtualização acelerada por hardware como as instruções VT-X além das ferramentas de gestão de configuração como Chef e finalmente a primeira geração de orquestração de infraestrutura como OpenStack que ajuda a levar um ambiente como a AWS pra dentro de grandes empresas que precisem de garantias totais de segurança como agências governamentais ou empresas que lidam com dados de alta periculosidade ou privacidade como dados médicos ou financeiros sensíveis. Ou sei lá, os projetos de uma bomba nuclear. Você certamente não deveria colocar essas coisas no Dropbox.</p>
<p>Essa vertente de IaaS privado é mais conhecido como Cloud Privado e existem várias empresas que oferecem soluções como o OpenShift da RedHat, o CloudFoundry da Pivotal. Assim você consegue ter a flexibilidade de infraestrutura virtualizada que pode ser rapidamente utilizada pelos projetos internos sem precisar da antiga burocracia de infraestrutura e se mantendo num perímetro controlado fora da internet pública.</p>
<p>Até aqui vimos até onde as tecnologias de virtualização chegaram no mundo comercial. Mas containers ainda não entraram na história. Vamos voltar um pouco pra 2006, que foi quando o Google desenvolveu outro projeto, o Process Container que foi uma evolução em cima dos Jails de BSD ou Zonas de Solaris ou mesmo os OpenVZ e Vserver de Linux. Repetindo: a ideia não é virtualização como Vmware ou Parallels ou Virtualbox, e sim isolamento dos processos e dos recursos da máquina.</p>
<p>Esse projeto foi renomeado depois pra Control groups ou cgroups. A ideia ainda é fazer a kernel mentir pros processos. Desse jeito você pode ter dois processos rodando na mesma máquina, no mesmo sistema operacional, mas um sem saber da existência do outro, quase como se estivessem em máquinas completamente diferentes. Se o objetivo é só isolar um processo, todas as opções de jail que culminaram no cgroups são ordens de grandeza mais leves do que virtualização, porque você ainda está rodando somente uma kernel em vez de múltiplas kernels virtualizadas.</p>
<p>Vamos rever conceitos: um sistema operacional é um conjunto de binários. A primeira coisa depois do boot loader é a kernel, é o primeiro grande binário que carrega com os privilégios máximos do Ring-0 e é responsável por carregar todo o resto do sistema, que por sua vez são conjuntos de programas como os drivers, supervisores como systemd, daemons e assim por diante. Em distribuições Linux, mesmo rodando praticamente o mesmo binário de kernel, o que diferencia um RedHat de um Ubuntu ou Arch é esse conjunto de binários que vai empacotado na distribuição e como eles são configurados, incluindo preferências em estrutura de diretórios ligeiramente diferentes, sistema de pacotes como yum ou apt ou pacman. Mas tudo começa com o kernel e os drivers, daí ele monta o volume de disco como o “barra” que é o root e de lá vai carregando todo o resto no userland ou Ring-3.</p>
<p>O tal cgroups consegue isolar os recursos da máquina e fazer os processos em userland enxergar só o que ele mandar enxergar. Ele vai dizer quanto de CPU, de memória o processo vai enxergar mas em particular, assim como o conceito do chroot que falei antes, ele pode mascarar o volume de disco e carregar um root diferente do sistema hospedeiro. Se você carregar um outro root com outro conjunto de programas userland, na prática você pode ter um hospedeiro que é um RedHat e dentro do jail do cgroups carregar os binários que formam um Ubuntu ou qualquer outra distro.</p>
<p>Voltando ao cgroups do Google, em 2008 tivemos o lançamento do LXC ou Linux Containers, que é um conjunto que inclui coisas como daemons e ferramentas de linha de comando que engloba não só o cgroups que passou a vir no kernel do Linux como namespaces que permite etiquetar processos. O cgroups consegue criar os limites pra grupos inteiros de processos. Quando você cria um namespace de processos, seu processo vira o PID, ou process ID, número 1 e os filhos desse processo vão seguindo nessa sequência. Diferentes namespaces vão ter diferentes processos PID 1. Você tem namespaces de rede, daí seu processo pode se ligar a uma porta virtual, assim vários processos podem responder numa porta 80 virtual, na mesma máquina, e uma bridge ou ponte virtual de rede vai cuidar pra que isso seja possível, fazendo tipo um NAT entre os containers e a rede externa. E tem namespaces de mounts, onde você pode montar e desmontar volumes dentro do container, sem afetar os mounts do sistema hospedeiro.</p>
<p>Lembram como eu expliquei que o sistema operacional mascara os endereços reais de memória pra uma memória virtual e o endereço virtual 0 de memória que um processo vê não é o endereço 0 real da memória? Ou como um chroot mascara o filesystem e cada processo enxerga roots no disco em lugares diferentes? Assim dois processos nunca vão pisar um em cima do outro? Pense em cgroups e namespaces como o equivalente pra todos os outros recursos da máquina, incluindo recursos de rede e I/O. Eu sei que estou sendo repetitivo mas é importante deixar claro: os containers de LXC parecem muito com um sistema operacional, mas eles compartilham a kernel do mesmo sistema, muito diferente de hypervisors que gerenciam máquinas virtuais inteiras, cada um com seu kernel. Um jail que restringe processos e compartilha o mesmo kernel entre vários jails é o que chamamos hoje de um container.</p>
<p>Esses anos entre 2006 e 2008 pra mim foram muito interessantes. Não quero entrar em muitos detalhes aqui mas eu acho que hoje em dia não preciso mais explicar o que é o GitHub ou GitLab, porque qualquer desenvolvedor que se preza já sabe usar Git. Se você ainda tinha alguma dúvida, Git é obrigatório, tem que saber! Mas antes de 2006 isso não era verdade, muitos usavam Subversion ou outras alternativas como BitKeeper, as porcarias de SourceSafe ou Clearcase, ou mesmo CVS. Mas a partir de 2007 o Git, que é uma ferramenta para gerenciar repositórios de código fonte versionados e distribuídos começou a ganhar terreno rápido, e com o início da popularização do Github em 2008 na comunidade Ruby on Rails, ela criou as sementes não só pra versionar código de aplicações mas também pra servir como mecanismo para automatizar muita coisa.</p>
<p>Também em 2008 outra empresa surgiu na comunidade Ruby on Rails pra resolver o problema que eu vim discutindo até agora de instalar, configurar e manter infraestruturas em máquinas virtuais. Pense somar um provedor de VPS com as capacidades de Linux containers via os cgroups e integrando com um processo de deployment usando repositórios Git. Esse é o Heroku. Se você nunca usou, deveria. É a recomendação pra todo mundo que não tem um sysadmin trabalhando tempo integral ou pra desenvolvedores que não tem experiência com infraestrutura.</p>
<p>Como já disse antes, o AWS EC2, principalmente em 2008, ainda não é muito diferente de um VPS do ponto de vista de um desenvolvedor. São máquinas virtuais. Você programa em PHP, ou Python ou Ruby. Mas pra subir sua aplicação precisa saber instalar e configurar banco de dados, servidor web, rede e tudo mais. Ou seja, ou vai entrar manualmente via SSH e configurar tudo na mão, ou vai usar um Capistrano ou Puppet pra fazer isso. E se você é iniciante isso não é trivial.</p>
<p>Mas e se existisse um serviço por cima desse AWS onde você pudesse simplesmente criar uma conta, cadastrar uma nova aplicação, daí rodar um “git push heroku” que é o comando de git pra subir seu código num repositório Git, e de repente toda sua aplicação é instalada, configurada e está pronta pra rodar, inclusive com banco de dados e outros serviços? Isso é o Heroku. Eles conseguiram tirar vantagem de cgroups e criar seu próprio padrão de containers e montar um modelo de negócios, 5 anos antes de um Docker aparecer. O que você chama de Dockerfile hoje, mais ou menos é o que o Heroku chamou de buildpacks. Todo mundo tentou fazer algo parecido mas levou anos pra chegar perto. Como o Heroku nunca abriu seu padrão de containers pro mundo open source, precisou o Docker aparecer anos depois pra finalmente termos um concorrente.</p>
<p>O que o Heroku conseguiu fazer foi tornar a infraestrutura trivial pra desenvolvedores. Lembram como eu passei o episódio inteiro da semana passada falando como era complicado montar uma infra com confiabilidade e segurança? É o melhor meio do caminho entre uma VPS ou AWS puro e um Kubernetes porque eles acertaram a usabilidade e foram inteligentes no modelo de negócios.</p>
<p>A partir de 2006 o mundo de tech startups explodiu, inspirada por todos os novos unicórnios crescendo como se não houvesse amanhã. Todo mundo queria conseguir subir suas coisas nas novidades da AWS mas ninguém tinha conhecimento suficiente de infraestrutura pra ficar lidando com CF Engine ou Puppet. Pior ainda, um desenvolvedor não sabe como de fato configurar um banco de dados. Não basta só instalar porque a configuração padrão costuma ser ruim, então você precisa saber tunar esse banco pra caber no seu servidor corretamente. Ninguém sabe configurar replicação master slave. E por aí vai.</p>
<p>Por causa disso se abriu um nicho pra tech startups que não existia até então. Se você quer enviar e-mails, jamais tente montar seu próprio servidor de e-mail como a gente fazia no começo dos anos 2000. Em vez disso contrate um serviço como Sendgrid ou Mailgun. Em vez de montar seu próprio servidor de Redis, com redundância, backup e tudo, contrate uma RedisLabs. Em vez de montar seu próprio servidor de cache com Memcached, contrate a Memcachier. Em vez de ter que lidar com repositório pra upload de imagens ou outros arquivos e ter que programar coisas como processamento de imagens, contrate a Cloudinary. E por aí vai.</p>
<p>Se a AWS inaugurou a categoria de IaaS ou infraestrutura como serviço, o Heroku inaugurou a categoria de PaaS ou plataforma como serviço e finalmente, empresas como New Relic, Papertrail, CloudAMQP, Sendgrid, Cloudinary, e centenas de outros inauguraram a terceira categoria: o famoso SaaS ou Software as a Service.</p>
<p>E o que o Heroku fez de inteligente foi desde o começo integrar esses diversos SaaS em sua plataforma de tal forma que fosse simples tanto via o dashboard web quanto via linha de comando adicionar esses serviços aos nossos projetos, além de integrar a cobrança de tudo numa fatura só. Mas eles mantiveram pra eles alguns dos serviços mais críticos como o banco de dados Postgresql e depois o Redis. E por isso era tão difícil tentar implementar um clone de Heroku você mesmo. A parte de containers, buildpacks e deployment usando um comando como git push não era tão difícil subir algo parecido num VPS como Linode ou DigitalOcean. Mas não adiantava nada fazer deployment da sua aplicação e não ter um banco de dados confiável. Daí você caiu no mesmo problema de antes de ter que configurar o banco de dados e, principalmente, manter ele rodando.</p>
<p>Eu diria o seguinte, se você é desenvolvedor de software, não tem experiência real como sysadmin, e precisa subir sua aplicação web mas não sabe nem como começar a configurar um Kubernetes, vá pro Heroku. É muito menos dor de cabeça. E não faça a conta de centavos por hora vezes horas por mês e comparar com o preço do Heroku e dos outros SaaS. Considere quanto custa um sysadmin pra configurar e dar manutenção na infra toda por toda a vida da sua aplicação. Não tente fazer infra sozinho se nunca fez isso antes, é sempre um desastre. E se sua aplicação web não monetiza o suficiente pra pagar a conta, você está fazendo alguma coisa muito errada.</p>
<p>Mas, além de ficar aqui fazendo propaganda de graça pro Heroku eu queria falar de outra coisa importante. O Heroku foi inteligente também educando os desenvolvedores. Nós desenvolvedores que crescemos no fim dos anos 90, com coisas como Java ou mesmo PHP, em ambiente corporativo, estávamos mais preocupados em codar de qualquer jeito dentro do prazo e não se preocupar tanto com a qualidade ou peso da aplicação. A métrica era praticamente linhas de código por hora. Se a aplicação ficasse pesada era só culpar a infra e mandar instalar numa máquina maior e foda-se.</p>
<p>Em 2008, no Heroku os containers ou dynos como eles chamavam vinham em vários tamanhos, contanto que esse tamanho fosse 512Mb de RAM e acho que só depois colocaram 1GB de RAM e anos depois opções maiores. Nós rapidamente começamos a fazer aplicações web mais e mais complexas e de repente não cabia em 512Mb!! Código mal feito roda na máquina do desenvolvedor, mas quebra em produção, em particular em recursos mais restritos como nos dynos do Heroku. Aliás, dynos é como o Heroku chama seus containers.</p>
<p>Eu sempre achei isso sensacional, porque é muito fácil fazer um código porco. E é sempre bom não contar com a muleta de só aumentar a máquina e mostrar imediatamente que a aplicação que tá mal feita mesmo. Quando você não tem outra opção, vai precisar programar direito. Então o Heroku ajudava a separar programador bom de programador ruim. Inclusive o co-fundador do Heroku, Adam Wiggins publicou um guia chamado the twelve factors ou os 12 fatores, que vou deixar nos links na descrição abaixo, que descreve como uma aplicação web deveria ser programada e organizada pra ser escalável. O framework Ruby on Rails desde sempre seguia esses 12 fatores e com a popularização do Heroku todo framework web que se preza passou a seguir, independente de rodar no Heroku ou não porque esses 12 fatores são universais pra apps minimamente bem feitas. E não era tão simples, um Wordpress da vida dava trabalho pra se adequar em todos os fatores na época.</p>
<p>Graças a isso uma nova geração de técnicas, otimizações, procedimentos e convenções começaram a mudar a forma como a gente programava. Em vez de pensar em rodar numa única máquina grande, tínhamos que parar pra pensar em rodar em múltiplos containers menores. Entendemos que a forma de escalar massivamente não é verticalmente aumentando a máquina, mas sim horizontalmente com mais containers. E na intenção de quebrar aplicações grandes pra escalar que nasceu também a idéia de micro-serviços. Eu sou contra quebrar sua aplicação em dezenas de micro-serviços sem motivos. Mas um bom motivo é quando uma aplicação grande não cabe num container, então fica óbvio que precisa ou cortar fora a gordura que não precisa, ou particionar em mais de uma aplicação em containers diferentes.</p>
<p>Pulando a história pra 2013 finalmente surge o tal Docker que inicialmente usou o Linux Containers ou LXC e adicionou novas ferramentas. Em particular, ele se aproveita do fato de você poder montar um root separado do sistema hospedeiro no container e passou a usar o Union File System. Pense um filesystem que consegue montar uma imagem que é somente de leitura como o novo root desse container, e toda vez que você tenta escrever ele cria uma segunda camada por “cima” vamos dizer assim.</p>
<p>Lembram quando expliquei sobre o conceito de Copy on Write pra memória no caso de processos que fazem fork no Linux? É a mesma coisa só que pra disco. Por isso as imagens de Docker são incrementais. Se você usa Git, pense se você pudesse montar um repositório de Git como o root do seu container e toda vez que você modifica ou escreve alguma coisa é como se fizesse um novo commit. Assim você consegue inclusive voltar um desses commits pra versão anterior. Mais importante: você consegue empacotar esse filesystem inteiro num arquivo, que são as imagens que seu cliente de docker baixa.</p>
<p>O Docker depois trocou o LXC, que se usava via a biblioteca libvirt, por uma que eles mesmos fizeram, a libcontainer. Mas acho que o mais importante do Docker foi padronizar a receita pra descrever as imagens no que hoje conhecemos como Dockerfiles. Esses Dockerfiles são parecidos com as promessas de um CF Engine ou as receitas de um cookbook de Chef. É um arquivo onde você declara como essa máquina vai ser configurada, que pacotes vão ser instalados, você pode inclusive declarar que comandos rodar da primeira vez que a imagem é criada ou toda vez que ela é inicializada. E também padronizar a interface de uso de linha de comando que hoje até o Kubernetes meio que copia.</p>
<p>Tem gente que ainda confunde Docker com alguma coisa parecida com um Virtualbox, mas espero que com o que expliquei até agora tenha ficado mais claro. Docker não é full virtualization, não é paravirtualization e não é um hypervisor. Virtualização é literalmente falsificar o hardware físico. Nesse hardware virtual você pode rodar múltiplos sistemas operacionais diferentes, ou seja, múltiplas kernels diferentes, incluindo kernels que não são do Linux como Windows ou BSD ou Mac e inclusive de processadores diferentes de Intel ou AMD como os fazem os emuladores de videogame.</p>
<p>Docker é um conjunto de ferramentas pra organizar e facilitar o uso de de cgroups, namespaces e union filesystem, tudo que você podia usar sozinho, mas a casca do Docker por cima obviamente torna tudo ordens de grandeza mais simples. Portanto você deve ter entendido que a idéia toda é rodar os programas de todo mundo no mesmo sistema operacional, na mesma máquina, sem rodar outro sistema operacional por cima. Na prática é uma máquina só, um sistema só, rodando os programas lado a lado mas com a kernel mentindo pra todos eles acharem que não tem ninguém mais.</p>
<p>Mas se você já usou Docker já deve ter instalado em cima de um RedHat ou Ubuntu e dentro dos containers ter conseguido instalar e rodar outra distro Linux, totalmente diferente, como Arch ou Alpine e inclusive rodar um pacman e instalar pacotes, ou seja, fica parecendo que é um VMware ou outro hypervisor onde você instala outro sistema operacional. Como isso é possível se eu acabei de dizer que não é virtualização e estamos compartilhando o mesmo sistema operacional?</p>
<p>Falando de forma bem simplificada, uma distro Linux é o conjunto do binário da kernel do Linux e de programas como o gerenciador de pacotes, o binário do Bash e outros utilitários e serviços. Tirando a kernel que roda no Ring-0, o tal kernel space, todo o resto roda em userland. Agora pense uma distro que eu removo o kernel, só sobra os programas em userland. E eu falei que num container nós compartilhamos a mesma kernel. Ou seja, se o container montar um diretório pra onde estão os programas de userland que definem um Ubuntu, então dentro desse container, pra todos os efeitos e propósitos, é um Ubuntu, mesmo que do lado de fora estamos rodando o Docker num Fedora. O Ubuntu dentro do container vai usar a kernel compartilhada do Fedora. Essa é a mágica dos containers.</p>
<p>Obviamente rodar todos os programas userland de um Ubuntu inteiro dentro de um container é pesado e um desperdício de recursos, especialmente se eu for inventar de querer rodar 10 containers, todos com os mesmos userland de Ubuntu repetidos. Por isso temos outras distros mais adequadas pra rodar num container como o CoreOS ou Alpine, que são userlands ordens de grandeza menores e só com os binários essenciais pra rodar a maioria dos programas que precisamos. A idéia do Docker é que o seu programa dentro do container não dependa de nada do sistema operacional hospedeiro e rode suas próprias dependências. A idéia de um container não é rodar dezenas de programas dentro dele, mas sim somente em um. Um container só pro Postgres, outro container só pro Redis, outro container só pro NGINX.</p>
<p>Por isso mesmo que não existe Docker de Windows ou de Mac. A kernel do Windows não tem cgroups e namespace nem union file system, nem o Mac e acho que nenhum UNIX BSD também. Mas então como eu consigo rodar Docker no Windows ou no Mac? Na realidade você primeiro tem que iniciar um hypervisor, seja tipo 1 como o Hyper-V ou o novo Hypervisor Framework no MacOS a partir da versão 10.10 Yosemite, ou mesmo um hypervisor tipo 2 como Virtualbox ou Vmware workstation.</p>
<p>Daí você cria uma máquina virtual no hypervisor e daí instala uma distro Linux com Docker. Agora, de dentro dessa máquina virtual você pode criar containers e compartilhar a kernel desse Linux. Daí o cliente de Docker rodando no Windows se comunica com o Docker rodando dentro da máquina virtual, por exemplo, via a rede interna TCP. Esse é o truque todo! Por isso, em hardwares físicos equivalentes, o Docker vai ser mais performático se o sistema operacional hospedeiro for também um Linux. Se for um Windows ou Mac você vai perder performance porque vai precisar também rodar um Linux num hypervisor antes.</p>
<p>A idéia de um container não é ser um sistema operacional independente. Como já disse o correto é rodar somente um único processo por container. Por isso você tem hoje soluções como o Docker Compose que permite que você declare num arquivo texto pra subir vários containers, cada qual com seu Dockerfile e dizer que volumes eles vão montar pra compartilhar arquivos, ou que portas eles vão expor que o outro container vai conseguir enxergar.</p>
<p>Em paralelo a isso, digamos que você montou seu Dockerfile e seu container funciona bonitinho na sua máquina de desenvolvimento, mas agora eu quero subir essa mesma imagem na minha conta na AWS EC2 ou DigitalOcean ou outro provedor de máquinas virtuais. Pra isso existe o Docker Machine, que vai facilitar o processo de subir ou fazer deployment dos seus containers num servidor na internet. Nesse caso o provedor precisa fornecer o suporte a Docker em suas máquinas virtuais ou você mesmo instalar o serviço de Docker na máquina virtual que provisionar.</p>
<p>Só fazer alguns containers não é suficiente. Você quer ter controle maior pra subir uma infraestrutura inteira com dezenas de máquinas virtuais e containers, além de configuração de rede, políticas de segurança e outras coisas. Organizar todos esses recursos virtuais e não virtuais de uma forma coordenada requer mais que só um vmware ou docker, por isso existem as soluções que chamamos de orquestradores, como o Docker Swarm, ou o Mesos, ou Marathon ou Nomad. Mas em 2019 quem realmente se tornou o padrão de fato é o Kubernetes do Google, que está disponível em todo provedor de infraestrutura como serviço, desde o Google Cloud, Microsoft Azure ou AWS.</p>
<p>Essas soluções se confundem com o próprio Docker já que num Kubernetes você instala imagens compatíveis com as de Docker e o runtime de containers por um tempo foi o próprio Docker. Na prática imagine que você está no Google Cloud, que é concorrente da Amazon AWS. Você tem máquinas virtuais rodando em cima de alguma coisa como o Citrix XenServer, que do ponto de vista de um servidor, é como se fosse seu Virtualbox ou Vmware que você roda no seu desktop ou notebook. O Kubernetes enxerga isso como Nodes ou nós. E dentro desses nodes você pode rodar múltiplos containers Docker que o Kubernetes enxerga como containers, agrupados dentro de Pods. Mais do que isso você tem um registro central de toda a infraestrutura de tal forma que se um container tiver problemas e cair ou node cair, o sistema central sabe como reconstruir.</p>
<p>Se você está iniciando, parece algo que um Docker compose e Docker machine resolveriam, mas o Kubernetes, ou seus concorrentes como o Swarm ou Mesos, são mais complexos que isso. Além de containers, temos que lidar com storage, com endereços de IP externos e internos, regras de firewall, clusters. Ele está preparado pra cenários com múltiplas zonas; por exemplo distribuir parte dos seus containers na zona Estados Unidos Central e outros containers na zona Asia sul caso você tenha público tanto na América quanto na Ásia e quer oferecer a melhor performance pros dois públicos. E existe o suporte a cluster de federações, dessa forma você pode manter parte da sua infraestrutura no Kubernetes do Google Cloud que é GKE e outra parte no Kubernetes da AWS que é o EKS. Ou seja, em provedores diferentes de cloud.</p>
<p>Em resumo o Docker se tornou uma organização, um ecossistema, e um conjunto de ferramentas, mas por baixo dos panos os componentes que realmente importam e possibilitam isso vem na kernel do Linux, que é o cgroups e o namespace. Todo o resto é só uma convenção de uso e um padrão. Inclusive surgiu uma iniciativa do próprio Docker pra padronizar o runtime de containers e o formato das imagens, que é o Open Container Initiative ou OCI. Diversos projetos concorrentes ao Docker surgiram por conta do runtime-spec e image-spec como gvisor, clearcontainers ou katacontainers. Eu mencionei que o Kubernetes usa o runtime de containers do próprio Docker mas pouco a pouco já se vê instalações de Kubernetes que não usam mais o Docker.</p>
<p>O Heroku teve um papel importante em educar e demonstrar como montar uma plataforma como serviços viável. E o Docker teve um papel importante em consolidar o uso de containers Linux e padronizar seu manuseio. Mas com a abrupta popularização do Kubernetes, se não tomar cuidado, o futuro do próprio Docker é incerto já que o Kubernetes está comendo a fatia de mercado de orquestração. De qualquer maneira, se você quer ser um bom sysadmin nos tempos modernos, no mínimo precisa saber como funciona Docker e seus outros produtos e Kubernetes. E não basta treinar só na sua máquina com minikube, você precisa entrar num Google Cloud e usar o kubernetes lá. Isso é o mínimo hoje, além do que eu já venho falando desde o começo desta série: estude e aprenda Linux de verdade!</p>
<p>E por hoje vamos parar por aqui, eu falei rapidamente de como saltamos do mundo primitivo de hospedagens compartilhadas e deployment de sites via FTP pras evolução das tecnologias de virtualização e containers, até chegar no Docker e Kubernetes. Mas isso ainda é só o pico do iceberg. Eu acho que pra fechar o tema de devops, semana que vem vou tentar falar especificamente sobre banco de dados e outro termos hipster, os famigerados NoSQL e talvez uma pincelada na arquitetura moderna de Web e o comportamento das diferentes linguagens de programação nessa arquitetura de cloud.</p>
<p>Mesmo estando num mundo de Kubernetes, as opções todas que eu falei desde o começo como hospedagens compartilhadas, servidores físicos em colocation, VPS, todas ainda existem e muita gente ainda programa do jeito antigo e tem muita dificuldade de entender porque as coisas são diferentes hoje. Por isso eu precisava que vocês entendessem onde esse monte de nomes como cgroups, hypervisors se encaixa no contexto geral. Se ficaram com dúvidas - e eu imagino que ficaram - mandem nos comentários abaixo pra galera ajudar, se curtiram mandem um joinha, continuem ajudando o canal compartilhando com seus amigos, não deixem de assinar o canal e clicar no sininho pra não perder a continuação dessa história! A gente se vê semana que vem, até mais!</p>
]]></content:encoded><category>devops</category><category>containers</category><category>cloud</category><category>akitando</category></item><item><title>[Akitando] #47 - Entendendo "Devops" para Iniciantes em Programação (Parte 1) | Série "Começando aos 40"</title><link>https://akitaonrails.github.io/2019/04/10/akitando-47-entendendo-devops-para-iniciantes-em-programacao-parte-1-serie-comecando-aos-40/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/04/10/akitando-47-entendendo-devops-para-iniciantes-em-programacao-parte-1-serie-comecando-aos-40/</guid><pubDate>Wed, 10 Apr 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/bwO8EZf0gLI"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente chegamos no tema final da série! Vamos falar um pouco sobre o tal do &amp;ldquo;devops&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Esta parte meio que depende dos conceitos que vimos nos últimos episódios então se você é iniciante, e ainda não assistiu os anteriores, recomendo que faça isso.&lt;/p&gt;
&lt;p&gt;No episódio de hoje quero dar um pouco do contexto histórico, dos anos 90 até os anos 2000, indo de configuração manual de máquinas, hospedagens compartilhadas, virtualização até os VPS. Vamos entender como uma coisa foi levando pra próxima.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/bwO8EZf0gLI"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente chegamos no tema final da série! Vamos falar um pouco sobre o tal do &ldquo;devops&rdquo;.</p>
<p>Esta parte meio que depende dos conceitos que vimos nos últimos episódios então se você é iniciante, e ainda não assistiu os anteriores, recomendo que faça isso.</p>
<p>No episódio de hoje quero dar um pouco do contexto histórico, dos anos 90 até os anos 2000, indo de configuração manual de máquinas, hospedagens compartilhadas, virtualização até os VPS. Vamos entender como uma coisa foi levando pra próxima.</p>
<p>Muita gente confunde muitos termos, virtualização, paravirtualização, jails, containers, hypervisors e não sabe o que é o que, então eu vou distinguir tudo pra vocês finalmente entenderem.</p>
<p>=== Script</p>
<p>Olá pessoal, Fabio Akita</p>
<p>Depois de muitas semanas, finalmente terminamos o tema de Back-end semana passada. Mas continuando na série Começando aos 40, décimo-segundo episódio já, precisamos entrar no tema de infraestrutura e devops. É bom eu já deixar claro desde já que minha formação e experiência principal é desenvolvimento de software, mas ao longo dos anos eu tive que lidar com infraestrutura várias vezes então digamos que eu tenho muitas opiniões a respeito. Mas é bom lembrar que eu nunca atuei como administrador de sistemas em tempo integral.</p>
<p>Aliás, durante esses próximos episódios você vão ver que vou ficar o tempo todo complementando “como eu já expliquei antes” e eu já expliquei mesmo, nos episódios anteriores. Então eu realmente recomendo que você tenha assistido todos os últimos 11 vídeos antes de começar esse se você ainda for iniciante. Se já é programador, vamos lá, e talvez você veja que mesmo trabalhando na área ainda tem coisas que você não sabe e, nesse caso, recomendo que tente ver os vídeos anteriores também, eu sempre escolho falar coisas que eu vejo que a maioria das pessoas não conhece, independente quantos anos já trabalha.</p>
<p>Devops é um daqueles termos chatos de definir e que você provavelmente está usando errado. É a mesma coisa com Agile. Agile não é só ter sprints ou backlogs ou rituais. Devops também não é um título, não são ferramentas, não é uma metodologia, não é uma cultura. É tudo isso e nada disso. Pois é, é uma merda mesmo esse termo, eu detesto coisas difíceis de definir. Pra entender melhor devops é mais fácil começar delineando os problemas e no final talvez chegar na conclusão.</p>
<p>(&hellip;)</p>
<p>Eu diria que se eu fosse detalhar tudo que eu gostaria sobre o tal devops, eu ficaria até o fim do semestre. Em vez disso, assim como fiz com front-end, quero dar mais um apanhado geral da linha do tempo e alguns conceitos e ferramentas. Vocês tem que entender que a área é gigantesca, você pode passar anos lidando com infraestrutura e nunca ver tudo. Por exemplo, eu nunca operei diretamente num data center, eu conheço vários dos conceitos mas o hardware moderno de data centers é uma coisa que eu só conheço na teoria, portanto eu não vou entrar muito nesse assunto. A área específica de redes é outro mundo a parte. Uma coisa é rede caseira ou de pequenas empresas, outra coisa completamente diferente é lidar com redes físicas em data centers, lidar com fibra ótica e roteadores nível “industrial” que você vai encontrar em provedores. Também existem os data centers privados em grandes corporações que usam muitas das mesmas coisas.</p>
<p>Na prática, metade do mundo de infraestrutura você nunca vai ter acesso, pelo simples fato que os hardwares e componentes nível data center você nunca vai chegar a colocar as mãos. Os tipos de componentes que temos no mundo de varejo consumidor, os PCzinhos xing lings que montamos ou mesmo os notebooks, usam componentes muito mais baratos e menos confiáveis do que você usa num data center. Apesar dos conceitos serem similares, os componentes que você tem acesso não se comparam com os de um data center. Então não é o seu CPU Core i7 ou i9 só que centenas deles num servidor. Nossos componentes de varejo desperdiçam muito espaço físico e são muito pouco confiáveis pra ficar ligados 24 por 7. Servidores sérios usam processadores com dezenas de cores que conseguem ficar ligados meses a fio sem desligar. Inclusive muitos desses componentes podem ter até menos performance se comparado com similares mais baratos que você compra no mercadolivre, mas é o trade-off de sempre: trocamos um pouco de performance por confiabilidade. Aliás, a coisa mais importante no mundo de infraestrutura é confiabilidade e segurança.</p>
<p>Nos anos 90, quando os data centers estavam começando a nascer, não era incomum muitas empresas que queriam ter presença na internet comprar seus próprios servidores ou até mesmo PCs caseiros, contratar a tal internet dedicada que era super caro porque estamos falando de uma época que internet era um troço opcional, a maioria das pessoas sequer usava ainda, e quem usava discava por modem de madrugada pra pagar menos pulsos. Felizmente a Lei de Moore estava no seu período mais acelerado e o início da Bolha da Internet do meio pro fim dos anos 90 acelerou o desenvolvimento da infraestrutura geral. Quando eu estava na faculdade acho que o Brasil tinha tipo uns 3 backbones que ligavam o país com o resto do mundo. Não era incomum ter problema num backbone tipo o da Fapesp e metade do Brasil ficava ilhado do resto do mundo. Era assim rudimentar no começo dos anos 90.</p>
<p>Quando estávamos fazendo os primeiros sites dinâmicos, com ASP ou PHP ou Perl, eu expliquei que o mais popular era rodar algo como Apache com coisas como mod_php ou Windows NT 4 com a primeira versão do IIS pra rodar ASP. Os bancos de dados não eram grande coisa e o mundo enterprise só rodava coisas como Oracle ou IBM DB2. O mundo open source tinha acabado de começar e um MySQL nem podia ser considerado um banco de dados de verdade ainda. O SQL Server estava iniciando depois que a Microsoft adquiriu a Sybase. Então as opções não eram muitas.</p>
<p>Um Apache funcionava inicialmente fazendo fork de si mesmo pra cada nova requisição. As requisições HTTP caíam em algum virtual host. Um virtual host é uma configuração do servidor Web indicando quem vai responder a requisição. Podia ser mapeando a URL pra uma pasta no disco que tem HTMLs estáticos ou podia ser mapeando a URL pra ativar algum programa CGI ou um modulo como mod_perl ou mod_php. Seja no Apache antigo ou num NGINX moderno, você sempre vai estar configurando virtual hosts.</p>
<p>Os virtual hosts ficavam num arquivo de configuração do Apache e toda vez que você mexia nesse arquivo precisava restartar o servidor pra carregar essas configurações e habilitar novos sites. Assim um único servidor Apache podia servir múltiplos sites de uma só vez. Nas faculdades era comum você ter servidores Unix como HP-UX ou Solaris com Apache e cada usuário do UNIX tinha uma pasta onde ele podia jogar arquivos que automaticamente ficavam disponíveis no servidor da faculdade. Com o tempo alguns alunos podiam ter contas nesses servidores e também compartilhar coisas no mesmo servidor. Foi assim que eu comecei a experimentar com web e aprender Unix em 1996.</p>
<p>Entendam que até o fim os anos 80 não era comum uma pessoa ter um computador inteiro só pra ela. Especialmente computadores potentes já que mesmos os caseiros dos anos 80 eram super limitados e fracos. A maioria não era muito mais potente que um nintendinho 8-bits. Por isso os grandes mainframes usavam sistemas operacionais que permitiam o tal time-sharing que já expliquei. E a geração UNIX evoluiu sendo um sistema multi-usuário. Quando nos anos 80 e 90 as comunidades de desenvolvimento quiseram tentar trazer a experiência do UNIX pra plataforma baixa, culminando no Linux, também ganhamos essas características de multi-usuários e multi-tarefa. Ou seja, no mesmo sistema operacional era possível ter múltiplos usuários guardando seus dados e documentos digitais na sua conta e seus programas não afetam o de outros usuários. Já sabemos como o sistema operacional isola os processos de afetar uns aos outros, mas também tínhamos esse isolamento no nível do usuário.</p>
<p>Somado ao fato que servidores sempre foram coisas caras, que o sistema operacional tinha a habilidade de ter múltiplos usuários, e que a maioria dos programas não roda consumindo 100% dos recursos o tempo todo, permitindo multi-tarefa preemptiva, foi natural começarmos a ter PCs montados com Linux, Apache e MySQL preparados pra rodar múltiplos sites de múltiplos usuários tudo na mesma máquina. Quando PHP começou a se popularizar, começaram a surgir vários painéis de gerenciamento pra facilitar cadastrar e gerenciar novos usuários e novos sites, produtos como o famoso cPanel criado em 1996.</p>
<p>Assim começa a era dos servidores corporativos web que sempre foram relegados a um recurso secundário frente aos gigantes ERPs como os da Oracle ou SAP. Pouca gente sabia mexer em Unix ou Linux, então painéis como cPanel tiveram um papel muito importante. Mas algumas pessoas foram um passo além: criaram um modelo de negócios onde era possível qualquer pessoa pagar uma mensalidade e ganhar um usuário numa máquina dessas e uma conta num derivado de um cPanel. Assim eles podiam criar um banco de dados restrito dentro de um MySQL, podiam configurar virtual hosts pros seus sites e podiam hospedar aplicações escritas em CGI com Perl ou PHP que era a opção mais performática. E assim se popularizou a primeira grande stack de tecnologias da web: o LAMP ou Linux, Apache, MySQL e PHP. E com uma stack aceita pela maioria, empresas puderam ser fundadas pra oferecer serviços, e assim começam a nascer as famigeradas hospedagens compartilhadas como as que você vê até hoje em empresas como Locaweb ou Godaddy.</p>
<p>O conceito é simples: dividir os recursos da máquina pra dezenas ou centenas de sites pequenos. Cada site pequeno tem pouco tráfego e só durante algumas horas do dia, então era possível apertar vários deles na mesma máquina. Até hoje temos centenas de milhares de sites funcionando assim. E com isso criou-se um ecossistema de pequenas agências e freelancers que podiam desenvolver esses sites pequenos, e foi assim que a primeira geração de sites começou a expandir rápido.</p>
<p>Por causa disso também, essa primeira geração de programadores se preocupou muito pouco com infraestrutura. Os únicos lugares onde precisava dos tais webmasters ou administradores de sistemas ou o que eu gosto de chamar: sysadmins, eram nas grandes empresas que mantinham seus próprios pequenos servidores, seja localmente no prédio da empresa ou em regime de co-location que era o aluguel de espaço e energia dentro de um data center onde você levava seu PC e instalava na internet dedicada deles, ou mesmo dentro dos provedores que forneciam o serviço de hospedagem compartilhada.</p>
<p>Nessa primeira geração, a segurança ainda era muito ruim. Na verdade, como tudo nasceu dentro de ambientes governamentais ou acadêmicos décadas atrás, você meio que podia contar com uma etiqueta e acordo de cavalheiros. Mas quando esses sistemas passaram a ser expostos na internet, isso abriu a caixa de pandora pra hackers do mundo inteiro começar a brincar. E naquela época era muito fácil hackear os servidores, já que os sysadmins da época sabiam no máximo seguir o procedimento do manual ou dos cursos pra instalar os Linux e cPanel e colocar na internet. E acredite, naquela época ou hoje, só fazer um curso não é 10% do que você realmente vai precisar no mundo real.</p>
<p>Não era difícil achar servidores pelo mundo com porta de Telnet aberta, com usuários sem senha que você podia entrar e vasculhar a máquina e era possível até ver arquivos como /etc/passwd que tinha senhas abertas usando criptografia fraca. Em poucas horas ou dias você podia invadir um servidor. A gente baixava arquivos de senhas de servidores, usava contas em outros servidores pra deixar um programinha de brute force rodando, com rainbow tables que são basicamente tabelas de palavras pré-criptografadas que podíamos só comparar com os valores criptografados nos arquivos de senha, o que é muito mais rápido do que tentar descriptografar. Em outro episódio explico mais sobre isso.</p>
<p>E no lado do servidor web tínhamos um problema. Já sabemos que um Apache funciona fazendo forks de si mesmo ou usando threads. Também sabemos que pra um Apache rodar PHP bastava rodar como CGI ou instalar coisas como um mod_php. Já sabemos como CGI puro é extremamente lento, onde cada requisição precisava subir o binário do PHP, executar o script e terminar. Então a opção do mod_php sempre foi a preferida no começo.</p>
<p>Quando viesse uma requisição, a execução desse script se dava dentro do espaço de memória do processo do Apache. Pelos virtual hosts o processo Apache sabe como responder a requisições de diferentes sites. Embora os usuários no Linux sejam isolados, o processo do Apache é compartilhado, ou seja, o Apache roda com um usuário e grupo, por exemplo chamado “apache”. Daí a pasta na home do usuário precisava dar permissão pra esse grupo e era assim que o Apache acessava os arquivos dos usuários pra servir nos virtual hosts. Entenda que o servidor web é um processo único que ouve na mesma porta 80 pra todo site que ele serve, e coisas como o nome do domínio ou sub-domínio ou sub-pasta é que diferencia de que usuário buscar os html ou php.</p>
<p>E nesse começo de web onde tanto Apache, quanto PHP quando o mod_php eram bem mal feitos, ou melhor dizendo, ingenuamente mal feitos, era muito comum descobrir um bug no num desses componentes que acabava permitindo executar coisas ou acessar dados da memória do processo que o script não deveria. E fazendo isso podíamos de um usuário chegar nos dados de outro usuário usando o Apache como ponte. Hospedagens compartilhadas passaram anos sofrendo desse problema.</p>
<p>Uma forma de limitar esse problema era não executar o php via mod_php e dentro do espaço de memória do Apache inteiro. Foram construídas outras versões do executável do php como o suphp que era servido pelo mod_suphp do Apache, que executava o script como o usuário e não como o apache. Havia a opção de chroot que mudava a raíz do root ou raíz de diretório, de tal forma que o script não conseguisse acessar o root de verdade do sistema e acabar acessando arquivos que não deveria. Depois de várias tentativas ao longo dos anos, no final a opção menos pior era rodar os scripts como FastCGI com a vantagem que diferente de CGI ele mantém o executável rodando, evitando ter que reiniciar toda hora, e ele podia rodar sob a permissão do usuário em vez da permissão mais global do Apache.</p>
<p>Eu expliquei tudo isso pra explicar que nesse período tínhamos um dilema pra resolver: o hardware e internet eram muito caros. Então você queria usar o máximo possível dos poucos recursos disponíveis. Rodar a opção mais performática como mod_php causava problemas de segurança. Mas a opção menos pior que é FastCGI usava muito mais memória porque agora você precisava ter no mínimo uma instância do PHP por usuário com sites. E em ambientes de hospedagem compartilhada você gostaria de minimizar o uso de memória pra enfiar o máximo possível de usuários por servidor. É uma grande dilema e ficamos presos nisso por muitos anos, do fim dos anos 90 e a década dos anos 2000 inteiro.</p>
<p>E isso foi um problema porque pra muita gente, essa foi a escola mais acessível pra aprender sobre web. Você pagava, sei lá, 10 reais, e tinha um espaçozinho pra jogar um site no ar. E você tinha basicamente coisas como ASP clássico e PHP, configurados em algo como FastCGI que significava que seu site era rápido mais ou menos e de vez em quando o servidor precisava matar seu processo FastCGI se tivesse pouco uso, pra conservar memória e deixar outros usuários rodarem, ou seja, de vez em quando sua requisição ficava lenta esperando um novo processo carregar.</p>
<p>Nesse caso você gostaria de fazer apps leves, com pouco código, porque quanto mais código mais demorado ia ficar. E fora isso o único jeito que se tinha na época pra subir suas apps era conectar via FTP e ficar jogando seus scripts php e imagens em alguma pasta do seu usuário onde o virtual host do apache estava configurado pra ler. Além disso você sempre estava limitado à versão do php e bibliotecas do sistema operacional que o provedor de hospedagem compartilhada oferecia. Se precisasse de alguma biblioteca binária compartilhada do sistema, teria que pedir pro suporte instalar pra você, talvez.</p>
<p>Então até a virada do século você tinha a opção barata e ultra limitada de hospedagens compartilhadas ou precisava se virar com seu próprio servidor, seja numa empresa com infraestrutura própria, o que era raro, ou colocando seu servidor em co-locations em data centers. Nesses casos precisava você mesmo configurar tudo manualmente do zero e largar uma porta de telnet, ftp ou ssh abertos pra conseguir conectar de fora via internet, numa época em que internet 24 por 7 ainda não era uma realidade pra grande maioria das pessoas. Nessa época também era muito comum você ter os papéis dos administradores de banco de dados e administradores de rede.</p>
<p>A partir do ano 2000 algumas linhas de pesquisa e experimentação começaram a dar frutos. Eu falei rapidamente do chroot que era uma forma de limitar até onde no filesystem seus processos poderiam acessar. Um conceito maior se chama “Jail” literalmente prisão e foi no FreeBSD em 2000 que começou a ser possível particionar os recursos do mesmo sistema operacional nessas jails, de forma a isolar os processos, fazendo eles pensarem que estavam sozinhos na máquina, sem ter acesso a outras jails. E ele implementou isso de tal forma que jails podiam ter seu próprio endereço IP separado.</p>
<p>Em 2001 tivemos o Linux VServer, que eram patches na kernel do Linux que permitiam algo parecido com os Jails de FreeBSD pra particionar o sistema. Esse sistema durou até 2006 mais ou menos. Novamente o objetivo era limitar o que um processo pode fazer, que recursos e quais recursos pode acessar, de tal forma que um processo mal intencionado ou bugado não conseguisse derrubar o sistema operacional inteiro.</p>
<p>Quase no fim da sua vida, em 2004 a Sun lançou os containers de Solaris. Em conceitos é também parecido com os Jails de FreeBSD ou o VServer de Linux, criando o que ele chama de zonas, que são restrições ao que se podia fazer no sistema no nível do sistema operacional. Solaris naquela época ainda era uma referência tendo recursos como zonas somado ao lendário filesystem ZFS. E no mundo Linux surgiu algo similar chamado Open VZ, o famoso Virtuozzo.</p>
<p>Jails, Containers, Zonas, nenhum deles é o que a maioria das pessoas pensa quando falamos em virtualização. Tudo isso que eu vou chamar só de containers são basicamente patches na kernel do sistema operacional para possibilitar restringir os recursos do sistema aos processos. Na verdade a gente faz a kernel literalmente mentir pros processos. Se a máquina real tem 10 cores, a kernel mente pro processo e diz que só tem 1 pra ele. Se a máquina tem 64GB de RAM, a kernel mente de novo e diz que só tem 4 GB. Se tem outros 50 processos rodando na máquina, quando o processo pedir a lista, a kernel mente e esconde todos os outros processos. Então o processo é basicamente enganado e a essa mentira da kernel damos o nome de container.</p>
<p>Mas se falarmos de virtualização de verdade, a história vai bem mais longe, pra década de 60 e os mainframes. Como já expliquei, nessa época as máquinas eram proibitivamente caras e inacessíveis e a única forma de múltiplas pessoas conseguirem usar a mesma máquina era aproveitar as diversas pausas que o sistema operacional precisa fazer como esperar um arquivo gravar ou dados serem transferidos na rede e aproveitar pra computar outra coisa de outro usuário, o conceito de time sharing. Mas os pesquisadores de mainframe foram um passo além. Quando as máquinas começaram a ficar mais poderosas, eles começaram a particionar a máquina pra rodar mais de uma kernel de cada vez, essencialmente tendo múltiplos sistemas operacionais rodando em paralelo.</p>
<p>No caso de mainframes, cada máquina virtual tinha acesso a recursos reais próprios como canais de I/O, dessa forma eles são extremamente eficientes. Nos anos 80 era praticamente impossível falar de virtualização nos computadores caseiros de 8 ou 16 bits. Simplesmente não havia nem poder computacional e nem recursos suficiente pra se pensar em rodar múltiplos sistemas operacionais na mesma máquina. Não tinha recurso suficiente male male pra rodar 2 programas de uma só vez.</p>
<p>Nos anos 90 começamos a brincar mais com esse conceito à medida que as máquinas foram ficando mais poderosas e mais acessíveis. Pense que no fim dos anos 80 as máquinas mais poderosas do mundo doméstico tinham 33Mhz e no fim dos anos 90 estávamos pra quebrar a barreira do 1Ghz. Ou seja, em uma década o poder computacional teórico ficou 30 vezes maior a um preço similar. Além disso nos anos 90 tivemos o nascimento do Linux o que abriu as portas pra desenvolvedores poderem experimentar os conceitos que já existiam no mundo dos mainframes na plataforma baixa.</p>
<p>O conceito é razoavelmente simples. Todo processo faz as tais syscalls como já expliquei. Além disso processos normais rodam num ambiente mais restrito, o tal Ring-3 que também já expliquei antes. Ou seja, um processo no Ring-3 não tem permissão pra chamar funções da CPU que só a kernel no Ring-0 tem acesso. Porém, e se fizéssemos um programa especial que por sua vez carrega seus programas? E se esse programa especial respondesse a syscalls restritas mentindo as respostas? O conceito todo de virtualizar é responder uma mentira. Por exemplo, se você tentar carregar uma kernel no Ring-3 ela não vai funcionar porque vai querer fazer chamadas de Ring-0 pra acessar o hardware real, que não existem na Ring-3.</p>
<p>Mas se esse programa especial responder a essas chamadas e mentir, a tal kernel no Ring-3 ou user land vai “acreditar” que está com acesso à máquina real. Fazendo uma tangente na filosofia isso me lembra o demônio malicioso de Descartes ou a Alegoria das Cavernas de Platão ou simplesmente o experimento do cérebro no jarro. Nosso cérebro é como se fosse um computador, e seu I/O ou input e output são nossos 5 sentidos, o tato, audição, visão, paladar e olfato. Nosso corpo humano funciona como os teclado e mouse desse computador. E se fosse possível tirar o cérebro do nosso crânio e colocar num jarro. E agora fosse possível ligar o I/O do cérebro ao de um computador, mandando sinais elétricos criados por esse computador.</p>
<p>Para o cérebro, se os sinais fizerem sentido, ele não tem como saber que não está num corpo humano de verdade, ele “acredita” que tem um corpo, só que virtual. Nós poderíamos criar mentiras no computador e enviar esses sinais direto pro cérebro. Poderíamos inventar que estamos no Havaí e mandar as imagens do cenário de praia, o cheiro da água do mar, a brisa batendo na pele, a barulho das águas batendo nas pedras e pra todos os efeitos o cérebro vai acreditar que está no Havaí. E é exatamente isso que fazemos com o tal programa especial, ele vai mentir pro processo que é o cérebro e o processo vai acreditar que está onde esse programa especial disser que ele está.</p>
<p>O sistema operacional, pra todos os efeitos e propósitos, é um supervisor de processos. Ele é responsável por gerenciar os recursos da máquina e tornar esses recursos disponíveis pros processos da forma mais eficiente possível. Se o sistema operacional é um supervisor, o que seria um supervisor de um supervisor? Que tal um hyper visor? E é isso que o tal programa especial que eu falei é: um hypervisor.</p>
<p>Temos 2 grandes tipos de hypervisors, o tipo 1 que seriam instalados no bare metal, ou seja, na máquina real de verdade, antes do sistema operacional. Exemplos disso são o VMware vSphere, Citrix XenServer, ou Microsoft Hyper-V. Os hypervisors tipo 2 são os que rodam por cima do sistema operacional, como um programa normal, por exemplo os Virtualbox, VMware Workstation, Parallels Desktop, QEMU.</p>
<p>No lado do mainframe o tipo 1 já era comum nos anos 90, mas nos computadores domésticos começamos a explorar o tipo 2 primeiro já que era o mais simples de fazer. Basicamente simular uma CPU em software, um programa especial que responde a syscalls da Ring-0, mentindo pros outros programas. Se você prestou atenção nos episódios anteriores, sabe que fazer syscalls da Ring-3 pro Ring-0, passando de userland pra kernel-space toda hora é uma coisa custosa. E como você está simulando os recursos da máquina, vai precisar toda hora se comunicar com o sistema operacional pra efetuar a operação de verdade. Portanto é impossível simular um Pentium de 100Mhz em cima de um Pentium de 100Mhz. Talvez você consiga simular um Pentium de 10Mhz em cima de um Pentium de 100Mhz. Quando você virtualiza a CPU inteira e roda outro sistema operacional inteiro por cima, você ganha o peso de dois sistemas operacionais inteiros: o Guest ou convidado que é a kernel enganada, e o Host ou hospedeiro que é o sistema de verdade por baixo e entre eles você tem o programa especial, o Hypervisor Tipo-2. É bastante peso.</p>
<p>Em 1999 tivemos dois grandes lançamentos que eu acho interessantes: estávamos vendo o nascimento da VMWare trazendo produtos pra virtualizar um Linux em cima do Windows ou vice versa, virtualizando o sistema operacional: um hypervisor tipo 2. E em paralelo víamos o lançamento de um dos filmes mais influentes daquela geração que foi Matrix. Basicamente a Matrix virtualizou nosso mundo, eu poderia dizer que a Matrix era um hypervisor tipo 1, que podia rodar um sistema operacional chamado “mundo real”. E do jeito como eu gosto de pensar, também rodava Zion. Só que havia backdoors entre os dois mundos, então era um hypervisor com bugs propositais e os processos podiam atravessar entre os dois sistemas virtualizados.</p>
<p>Deixando a nerdice de lado um pouco, a diferença entre os containers como os Jails de BSD ou OpenVZ é que você rodava os processos de forma restrita e isolada mas todos no mesmo sistema operacional. A vantagem da virtualização com hypervisors era recriar uma máquina virtual literalmente, onde um novo sistema operacional podia ser instalado e os programas rodam como se estivessem numa máquina de verdade. Obviamente a primeira solução é muito mais rápida, mas se você quisesse rodar um programa de Windows em cima de um Linux, isso só seria possível simulando o hardware, mas ao mesmo tempo seria extremamente lento.</p>
<p>Simular uma máquina inteira do zero é o que chamamos de Full Virtualization, ou seja, do ponto de vista do sistema operacional ele está na Matrix, ele não tem como saber que a máquina por baixo não é de verdade já que o hypervisor está mentindo nas syscalls pra ele. Só que a vida de um mentiroso é muito difícil, como já diria o senso comum. Você não devia mentir sozinho, em vez disso, podemos fazer um conluio, uma conspiração mesmo. No fim basta o processo rodando acreditar que está numa máquina de verdade. Então poderíamos modificar o sistema operacional pra só ele saber que está numa máquina virtual, criando um acordo entre ele e o hypervisor. Então nas syscalls mais difíceis de mentir, o sistema operacional pede ajuda direto pro hypervisor e assim as coisas ficam mais rápidas. No caso criou-se um padrão de interfaces do hypervisor que o sistema operacional tinha conhecimento. Versões de Windows e Linux foram modificadas diretamente pra acessar essas APIs e a isso demos o nome de Paravirtualização.</p>
<p>Mas o problema é conluios dependem das duas partes fazerem sua parte na mentira, e nesse caso você não podia instalar qualquer sistema operacional de qualquer versão nesses hypervisors de paravirtualização como o Xen. O sistema operacional precisava saber mentir e ter patches pra participar do conluio. A VMware tinha o padrão Virtual Machine Interface ou VMI que por um tempo passou a vir na kernel do Linux. Mas ela foi removida por conta do próximo grande acontecimento a partir de 2005 e 2006.</p>
<p>Ao mesmo tempo quase, de forma independente, a Intel e a AMD resolveram estender o conluio pra dentro do hardware da CPU. No final, é muito lento deixar o hypervisor mentir sozinho, mas se a CPU participar da mentira, ela se torna quase tão rápido quanto a verdade. A AMD lançou extensões às instruções binárias da CPU que chamou de AMD-V e a Intel lançou a mesma coisa mas chamando de VT-X, parece mentira mas são apenas 10 novas instruções que fazem o sistema operacional virtualizado achar que está na Ring-0, que é todo o objetivo dos esforços de virtualização. Essas 10 instruções aqui embaixo
(VMPTRLD, VMPTRST, VMCLEAR, VMREAD, VMWRITE, VMCALL, VMLAUNCH, VMRESUME, VMXOFF, and VMXON)</p>
<p>Essa evolução dos processadores funcionou tão bem que a performance de muitas aplicações chegou a aproximar a performance nativa sem virtualização em alguns casos. Foi em 2005 que as plataformas baixas finalmente chegaram mais próximos dos mainframes dos anos 70 que rodavam sistemas como o System/370.</p>
<p>Vamos recapitular, no meio da primeira década dos anos 2000 já começamos a ter hypervisors tipo 1 que podia rodar múltiplos sistemas operacionais em paralelo no bare metal. E também tínhamos a opção de hypervisors tipo 2 pra rodar um sistema hospedeiro leve e por cima dele rodar múltiplos sistemas operacionais também. Isso começou a se tornar extremamente importante pra data centers onde os servidores sempre foram difíceis de dar manutenção e dividir os recursos entre diferentes aplicações, de diferentes clientes, rodando na mesma máquina, com segurança e performance. Com as novas instruções VT-X e AMD-v dando performance suficiente, começou de fato o mercado de virtualização da infraestrutura.</p>
<p>De qualquer forma, voltando aos servidores dava trabalho montar um servidor novo. Houve uma época que se eu precisasse de um servidor, eu tinha que abrir uma ordem de serviço no data center da empresa ou do provedor. Eles iam levar 1 ou 2 dias, pra reservar um hardware, instalar e configurar um sistema operacional, ligar monitoramento, conectar na rede e deixar disponível pra mim. E essa máquina ia ficar a maior parte do tempo sem fazer nada, e mesmo quando estivesse fazendo nunca ia usar todos os recursos ao máximo. Por isso era tão caro: pra provisionar era caro, e a máquina era um grande desperdício na maioria do tempo.</p>
<p>Os VPS ajudaram a aliviar esses dois problemas. Como os hypervisors podem ser automatizados, agora bastava instalar o sistema operacional uma vez e fazer um snapshot, ou seja copiar os dados que compõe a instalação num arquivão, e a isso chamamos de Imagens. Como as máquinas são virtuais e são sempre as mesmas, as imagens são sempre compatíveis sem problemas de coisas como drivers. Quem tinha que se preocupar com drivers e hardware que podia mudar (tipo quando trocávamos uma placa de rede com defeito) era o sistema hospedeiro ou o hypervisor tipo 1, ou seja, só quem roda em bare metal. Portanto, via uma interface web simples e passando um número de cartão de crédito, em poucos minutos eu podia ter uma máquina virtualizada ligada na rede pronta pra usar. Foi uma evolução impressionante no começo dos anos 2000.</p>
<p>Tudo isso ajudava uma pessoa a criar uma máquina, mas ainda não ajudava tanto as grandes empresas que precisavam controlar coisas realmente complexas e distribuídas com múltiplos servidores. Quando era só uma máquina, alguém da empresa entrava nela de vez em quando pra ficar dando manutenção, instalando coisa nova e rodando atualização. Mas se você tinha 5, 10 ou 100 máquinas, precisava de uma equipe inteira pra ficar manualmente atualizando o sistema ou consertando problemas. Era tosco lidar com equipes de TI fazendo tudo na mão nessa época. A Microsoft tinha serviços no Windows Server pra tentar automatizar as coisas, mas vou te dizer que ter que lidar com Active Directory sempre foi um pé no saco e é até hoje uma das tecnologias que eu mais detestei ter que lidar, era tipo uma extensão do Registry pra servidor, ambas que só dão dores de cabeça. Eu entendo os motivos e a utilidade, mas continua sendo uma das soluções com a pior usabilidade desde a época dos mainframes.</p>
<p>Uma outra categoria de software começou a surgir, principalmente a partir de 1993 quando foi lançado pela primeira vez, ou a partir de 1998 quando saiu a versão 3 do software conhecido como CF Engine. Qualquer administrador de sistemas Linux trabalhando com vários servidores, por exemplo nos provedores de hospedagens compartilhadas como eu expliquei, usaram CF Engine 3 em algum momento. Claro, a maioria não sabia o que era isso porque as pessoas têm a tendência de não estudar ou querer fazer coisas complicadas repetidas vezes pra mostrar manja. Mas os realmente bons são preguiçosos: nós não gostamos de fazer a mesma coisa repetidas vezes e preferimos fazer um software pra fazer as coisas chatas por nós. CF Engine é isso: um estagiário de administrador de sistemas que tem as receitas pra instalar a mesma máquina do mesmo jeito todas as vezes, num único comando.</p>
<p>O CF Engine funciona com uma linguagem declarativa que descreve uma promessa. Na prática é a descrição do que o servidor deveria ter instalado (os diversos pacotes e binários) e como esses componentes deveriam ser configurados (ordens pra editar os arquivos de configuração como os que ficam no famoso diretório /etc) e quais serviços deveriam ser inicializados pelo systemd logo que a máquina inicia. O CF é um automatizador, em vez de você ter que entrar máquina a máquina e ficar digitando dezenas de comandos pra fazer tudo isso, e ficar esperando comando a comando executar; você instala no máximo um sistema operacional zerado, instala o agente do CF e faz ele conectar no servidor de CF. De lá o servidor por enviar essas promessas pra nova máquina e fazer tudo que precisa pra que fique do jeito que você precisa.</p>
<p>Avançando rapidamente na história, na sequência tivemos o Puppet lançado em 2005 e escrito em Ruby, o que possibilitou usar as capacidades de criar sub-linguagens, que chamamos de DSLs ou Domain Specific Languages pra criar o equivalente das promessas de CF. Em 2009 também em Ruby tivemos o lançamento do Chef que é o motor de produtos como o AWS Opscode por exemplo.</p>
<p>A grande vantagem de sistemas como CF Engine, Chef ou Puppet é descrever infraestrutura como código. Dessa forma, se os pré-requisitos fossem os mesmos, você podia não só mandar rodar a mesma receita várias vezes e garantidamente ter a mesma máquina no final como podia compartilhar essas receitas da mesma forma como compartilhamos código open source e a comunidade podia ajustar essas receitas, fazendo ficar melhor do ponto de vista de minimizar as coisas que eram instaladas ou como melhorar as configurações pra ficar mais seguras. Em vez de todo administrador de sistemas ter que reinventar a roda do zero, podia já partir de uma base comum.</p>
<p>Sistemas como o CF, Puppet ou Chef seguem uma arquitetura de cliente-servidor onde as novas máquinas ganham agentes que são processos que ficam rodando e escutando ordens vindas de um servidor. Assim era mais simples provisionar novas máquinas, bastava que no processo a imagem do sistema operacional viesse com o agente e quando ela plugasse na rede podia ser vista pelo servidor e iniciar o processo de enviar as receitas pra instalar o que fosse necessário. Uma coisa que muito iniciante reclamava e ainda reclama é que esses sistemas tinham pelo menos 2 componentes: os agentes e o servidor, então tinha bastante coisa pra configurar antes de ter a primeira máquina automaticamente configurada. Se seu trabalho era configurar dezenas de máquinas, tudo bem, mas se você só queria configurar uma única máquina, era mais trabalho do que você gostaria de ter.</p>
<p>Antes desses sistemas ficarem mais populares o mais comum era algum estagiário manualmente instalar o sistema operacional, configurar a rede e plugar a máquina rede, deixando pelo menos o Telnet ou mais recentemente o servidor de SSH configurado. Pra quem não sabe, a porta 23 sempre foi do Telnet que é uma forma de acessar o terminal do servidor remotamente. Telnet é o teletype network e na prática você podia se logar remotamente da sua máquina num servidor Unix ou Linux.</p>
<p>Esse protocolo é antigo, do fim dos anos 60 então quando a internet começou a se popularizar ficou bem claro que era muito inseguro. Se você não sabe disso é muito simples fazer uma coisa chamada sniffing que é ouvir os pacotes TCP trafegando na rede e ver o que tem dentro deles, se o dado estiver aberto você pode facilmente interceptar e ler esses pacotes. Protocolos de TCP como Telnet, FTP, SMTP, HTTP todos são muito fáceis de sniffar e por isso viemos gradativamente substituindo o uso deles por outros mais seguros como o SSH, SFTP, SMTP com TLS, e HTTPS. SSH que todo mundo conhece hoje em dia e é exposto na porta 22.</p>
<p>Assim como Telnet, você pode abrir um terminal remoto interativo via SSH e ficar digitando comandos manualmente. Ou você pode automatizar o SSH e usar ele como um túnel. Ou seja, você pode criar um programa que envia comandos ao cliente de SSH sem que você precise abrir o terminal interativo; como um programa de linha de comando. Em cima desse conceito, podíamos criar arquivos de texto com vários comandos que queremos rodar no servidor. O SSH funciona também pra transferir arquivos de forma encriptada como um FTP melhorado. Então podemos mandar um script de comandos pro servidor e depois mandar executar esse script no servidor.</p>
<p>Se você pensou 2 segundos, isso possibilita outra coisa: automatizar a instalação e configuração de novos servidores. Todo novo sistema operacional de servidor que se preza já deixa pré-instalado um servidor como o OpenSSH. Com CF, Chef ou Puppet precisamos deixar também instalados os seus programas agentes. Mas só com SSH já dá pra fazer muita coisa. E assim em 2006 surge outra ferramenta do mundo Ruby, o Capistrano que antes se chamava SwitchTower. É um sistema de deployment sem agentes ou agentless. O agente no caso seria o próprio servidor de SSH. Com comandos simples você pode instalar sua aplicação em múltiplos servidores, configurados todos iguais porque você estava automatizando os mesmos comandos pra rodar em todos eles, e de quebra ele podia também configurar seu servidor de banco de dados, seu balanceador de carga e tudo mais, com a vantagem de ser mais simples que CF ou Puppet.</p>
<p>Se você que começou nos anos recentes pensa que o Ansible que foi lançado em 2015 foi uma grande novidade, na verdade ele foi só uma evolução do Fabric de Python que saiu antes dele e que por sua vez foi um clone de Capistrano. Em linhas gerais temos os gerenciadores de configuração de infraestrutura, mais complicados, que são cliente-servidor como CF, Puppet e Chef e os que são mais simples e agentless como Capistrano e Ansible. Eu diria que ambos servem propósitos diferentes pra públicos diferentes. Se você é um desenvolvedor de aplicações web que precisa subir uma aplicação simples numa máquina virtual num VPS e depois conseguir fazer deployments automatizados rápidos das atualizações e correções, você deveria usar Ansible.</p>
<p>Por outro lado, se você precisava manter 10 máquinas web, 3 máquinas de bancos de dados, 1 servidor de cache e coisas assim, era melhor organizar sua infraestrutura em conjuntos de receitas usar coisas como Chef pra criar e atualizar essas máquinas. As pessoas pensam que infraestrutura é só instalar a máquina, colocar na rede e acabou. Longe disso, você precisa atualizar o sistema operacional com correções de segurança, você precisa subir atualização das aplicações que rodam nelas. Mais importante: se uma máquina é invadida ou mesmo dá algum problema inexplicável, não vale a pena tentar consertar, você precisa preparar a arquitetura pra que seja possível destruir a máquina por completo e recriar uma nova pra colocar no lugar. Manualmente é um trabalho de corno e você vai errar muitas vezes esquecendo de instalar ou configurar alguma coisa, mas automatizado você sempre garante que a máquina vai ser montada da forma certa.</p>
<p>Essas ferramentas de automatização não só precisavam instalar máquinas mas também precisavam garantir que as premissas de segurança estão sendo cumpridas. Que as políticas certas estão aplicadas, que as permissões certas de usuários foram feitas, que as máquinas estão nas vlans corretas e que as regras de firewall também estão corretas. Esse conhecimento veio sendo automatizado também.</p>
<p>Agora pense assim, o começo do século XXI foi extremamente caótico com a bolha da internet que explodiu em 2001. Passamos uns 2 ou 3 anos de depressão, sem nada muito interessante realmente acontecendo. Mas então a controvérsia do Napster vs a indústria da música, o lançamento do iPod e iTunes, começou a trazer o entretenimento de massa pra internet, bandas independentes começaram a se lançar via internet e a hegemonia da MTV como influenciador começou a desaparecer quando as redes sociais de primeira geração, como MySpace, começaram a tomar a internet de assalto. Se você não tem noção desse efeito pense que em 2006 MySpace foi mais visitado do que o próprio Google, consegue imaginar isso?</p>
<p>Já em 2008 o Facebook destronou o MySpace, então imaginem dois titãs em disputa. 2008 que eu já expliquei que foi a época da maior crise econômica dos tempos modernos mas no lado da internet as coisas estavam esquentando absurdamente, principalmente em Silicon Valley. Foi em 2005 que o Netflix pivotou pra oferecer vídeo streaming em vez de entregar DVD, foi em 2006 que o Google comprou o YouTube, em 2007 que nasceu Twitter, em 2008 que Facebook despontou como a liderança das redes sociais, também em 2008 que a Apple inaugurou o iPhone App Store, em 2009 que surgiu o Uber, em 2010 que surgiu o monstro Groupon, que já praticamente morreu. Ou seja do meio pro final dos anos 2000 tivemos uma aceleração abrupta que não se via desde o fim dos anos 90. E é por isso que esse foi o período que mais tivemos evolução nas tecnologias.</p>
<p>Diferente dos anos 90, agora estávamos num ponto onde as pessoas comuns já tinham acesso à internet em casa e partir dos anos 2010 começamos a colocar a internet no bolso das pessoas, acesso de qualquer lugar, a qualquer momento. E com o advento dos smartphones e das redes sociais criamos uma massa gigantesca de pessoas que querem ficar online o tempo todo, e mesmo tendo coisas como ecommerce antes, a escala que os unicórnios atingiram exigiam muito. Ecommerces tem horário e dias pra picos. Redes sociais tem picos o tempo todo. Instalar máquinas na mão dentro de uma Netflix seria completamente inviável. Por isso muitas das novas tecnologias vieram exatamente desses unicórnios. Pra piorar a partir de 2011 ainda começamos a adotar mais e mais tecnologias que ajudavam a manter as pessoas online mais tempo, como WebRTC pra comunicações e WebSockets.</p>
<p>Até então a internet era feita pra ser desconectada. Você podia carregar um site com seus conteúdos, desconectar da internet e continuar lendo. Não era prático, mas o protocolo HTTP foi feito pra não ter estado. Você baixa, renderiza no navegador e acabou. Mas com WebRTC e WebSockets você passou a gerenciar estados e precisa estar online pra tudo funcionar, coisas como Messenger, Slack, Whatsapp, só fazem sentido num ambiente onde as pessoas estão online ao mesmo tempo na maior parte do tempo.</p>
<p>Entenda uma coisa importante: a grande maioria das boas práticas e recomendações que você vê na internet hoje vinda de engenheiros de um Netflix, de um Twitter, de um Facebook, ou de um Google, só fazem sentido pra eles na maior parte do tempo. Ouça isso com muita atenção: você não é o Netflix, tá muito longe. O Netflix levou anos pra ser o Netflix. Os unicórnios são chamados de unicórnios porque são quase impossíveis se você tentar replicar e precisam de vários fatores incontroláveis incluindo estar no lugar certo na hora certa, pra acontecer. Portanto não é porque você usa uma tecnologia do Google que vai te levar um pouco mais próximo de ser um mini Google. Não existe correlação, esqueça essas fantasias.</p>
<p>Portanto não, você não deveria usar um Apache Mesos, tecnologia criada pelo Twitter, na sua infraestrutura de 3 máquinas na sua micro-empresa que você gosta de chamar de tech startup. Toda vez que você ouvir o argumento “devíamos usar isso porque o Twitter usa” ignore completamente essa pessoa, ela não tem idéia do que está falando e é uma pessoa perigosa.</p>
<p>Isso tudo dito o fato é que de repente começaram a surgir empresas que estavam escalando absurdamente rápido num ambiente onde a maioria dos data centers disponíveis não sabiam como atender essa demanda. Era muito demorado manualmente instalar e configurar máquinas. Mesmo com ferramentas como CF, Chef eles estavam atingindo limites. Pense assim: num ambiente cliente e servidor, onde de repente você tem mil máquinas batendo num servidor de configurações como o CF e você vai engargalar a rede se precisar baixar um binário grande pra mil máquinas na rede interna, vai começar a engargalar o servidor de configurações. Novas arquiteturas precisaram ser criadas.</p>
<p>Estamos chegando na era mais acelerada do mundo de infraestrutura web que eu argumentaria que foi a partir de 2006 até pelo menos 2015. Se você acompanhou os últimos episódios sabe o quanto de coisas saiu nessa década e se não assistiu agora é a hora de ver porque chegamos ao final deste episódio. No próximo episódio quero contar como as tecnologias que mencionei hoje vão culminar em coisas como Docker ou Kubernetes que você tanto ouve falar em tudo que é blog ou evento de tecnologia.</p>
<p>Eu espero que vocês tenham ganhado um contexto melhor quanto ouvirem sobre essas tecnologias. Eu falei de muitos conceitos então é natural que vocês tenham dúvidas, não deixem de mandar nos comentários pra galera poder ajudar também. Se curtiram o vídeo mandem um joinha, compartilhem com seus amigos, assinem o canal e não esqueçam de clicar no sininho pra não perder os próximos episódios. A gente se vê semana que vem, até mais!</p>
]]></content:encoded><category>devops</category><category>automacao</category><category>cloud</category><category>akitando</category></item><item><title>Minhas Notas sobre Deploy Produção do Ruby on Rails no Google Cloud Kubernetes Engine</title><link>https://akitaonrails.github.io/2018/01/09/minhas-notas-deploy-producao-ruby-on-rails-google-cloud-kubernetes/</link><guid isPermaLink="true">https://akitaonrails.github.io/2018/01/09/minhas-notas-deploy-producao-ruby-on-rails-google-cloud-kubernetes/</guid><pubDate>Tue, 09 Jan 2018 20:13:00 -0200</pubDate><description>&lt;p&gt;Estou usando o Google Cloud com Kubernetes Engine há dois meses e pouco, do zero à produção. Na prática, não levei um mês para montar tudo, mas levei mais um mês para descobrir algumas arestas bem chatas.&lt;/p&gt;
&lt;p&gt;TL;DR: O Google está fazendo um trabalho razoavelmente bom como contrapeso para a AWS não se acomodar. Se você já sabe tudo sobre AWS, eu te encorajo a testar o Google Cloud. Provavelmente por memória muscular, eu ainda me sentisse mais confortável com a AWS, mas agora que me forcei a passar pelo processo de aprendizado, estou bastante confiante com Google Cloud e Kubernetes para a maioria dos meus cenários.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Estou usando o Google Cloud com Kubernetes Engine há dois meses e pouco, do zero à produção. Na prática, não levei um mês para montar tudo, mas levei mais um mês para descobrir algumas arestas bem chatas.</p>
<p>TL;DR: O Google está fazendo um trabalho razoavelmente bom como contrapeso para a AWS não se acomodar. Se você já sabe tudo sobre AWS, eu te encorajo a testar o Google Cloud. Provavelmente por memória muscular, eu ainda me sentisse mais confortável com a AWS, mas agora que me forcei a passar pelo processo de aprendizado, estou bastante confiante com Google Cloud e Kubernetes para a maioria dos meus cenários.</p>
<p>Aviso completo: não sou especialista, então leve o que digo com um grão de sal. É um daqueles assuntos que tenho muita vontade de falar, mas também sou muito cuidadoso com a escolha das palavras para você não ter uma ideia errada sobre as soluções propostas.</p>
<p>O objetivo desse exercício é principalmente guardar alguns trechos e pensamentos para consulta futura. Então tenha em mente que isso também não é um tutorial passo a passo. Minha primeira intenção foi ir por esse caminho, mas aí percebi que seria quase como escrever um livro inteiro, então não dessa vez.</p>
<p>Para ter sucesso com algo como Google Cloud e Kubernetes, você <strong>precisa</strong> ter experiência em infraestrutura. Se você nunca instalou boxes Linux de nível servidor do zero, se nunca fez otimizações de servidor, se não está confortável com componentes bare-metal do lado do servidor, não tente um deploy real em produção. Sua aposta mais segura ainda é algo como o Heroku.</p>
<p>Você tem que ser o tipo de pessoa que gosta de fuçar nas coisas (como provavelmente você já me leu fazendo em <a href="http://www.akitaonrails.com/linux"target="_blank" rel="noopener">posts anteriores</a> do blog).</p>
<p>Não sei tudo, mas sei o suficiente. Então só precisei descobrir quais peças se encaixariam melhor nas minhas necessidades. Você <strong>precisa</strong> definir suas necessidades antes de tentar escrever seu primeiro arquivo YAML. Planejamento é crucial.</p>
<p>Antes de tudo, isso é o que eu queria/precisava:</p>
<ul>
<li>Camada de aplicação web escalável, onde eu pudesse fazer tanto rolling updates (para atualizações com <strong>zero downtime</strong>) quanto escalamento horizontal automático e manual dos servidores.</li>
<li>Storage persistente montável <strong>com</strong> snapshots/backups automáticos.</li>
<li>Banco de dados robusto gerenciado (Postgresql) com backups automáticos e <strong>replicação fácil</strong> para instâncias somente leitura.</li>
<li>Solução gerenciada para armazenar secrets (como o suporte a ENV do Heroku). Nunca armazene configuração de produção no código-fonte.</li>
<li>Suporte a imagens Docker sem precisar construir infraestrutura customizada para deploy.</li>
<li>Endereços IP externos estáticos para integrações que exigiam IP fixo.</li>
<li>Terminação SSL para poder conectar ao CloudFlare (CDN é obrigatório, mas não suficiente — em 2018 precisamos de algum nível de proteção contra DDoS).</li>
<li>Segurança suficiente por padrão, para que tudo fique — em teoria — bloqueado a menos que eu decida abrir.</li>
<li>Alta disponibilidade em diferentes regiões e zonas de data center.</li>
</ul>
<p>É fácil fazer deploy de uma aplicação web demo simples. Mas eu não queria uma demo, queria uma solução de nível produção para o longo prazo. Melhorias na minha implementação são muito bem-vindas, então fique à vontade para comentar abaixo.</p>
<p>Alguns dos problemas para quem está começando:</p>
<ul>
<li>A documentação é <em>muito</em> extensa, e você vai encontrar <em>quase</em> tudo — se souber o que está procurando. Também tenha em mente que Azure e AWS também implementam Kubernetes com algumas diferenças, então parte da documentação não se aplica ao Google Cloud e vice-versa.</li>
<li>Existem muitos recursos em estágios alpha, beta e stable. A documentação acompanha bem, mas a maioria dos tutoriais com alguns meses pode não funcionar mais como esperado (esse aqui incluído — estou assumindo Kubernetes 1.8.4-gke).</li>
<li>Existe um conjunto de palavras que se aplicam a conceitos que você já conhece mas têm nomes diferentes. Se acostumar com o vocabulário pode atrapalhar no começo.</li>
<li>Parece que você está brincando com Lego. Muitas peças que você pode misturar e combinar. É fácil de bagunçar. Isso significa que você pode construir uma configuração adaptada às suas necessidades. Mas se você só copiar e colar de tutoriais, você <strong>vai</strong> ficar preso.</li>
<li>Você pode fazer <em>quase</em> tudo através de arquivos YAML e linha de comando, mas não é trivial reutilizar a configuração (para ambientes de produção e staging, por exemplo). Existem ferramentas de terceiros que lidam com bits YAML parametrizáveis e reutilizáveis, mas eu faria tudo à mão primeiro. Nunca, jamais, tente templates automatizados em infraestrutura sem saber exatamente o que eles fazem.</li>
<li>Você tem 2 ferramentas de linha de comando <em>pesadas</em>: <code>gcloud</code> e <code>kubectl</code>, e a parte confusa é que elas nomeiam algumas coisas de forma diferente mesmo sendo as mesmas &ldquo;coisas&rdquo;. Pelo menos, <code>kubectl</code> é próximo do <code>docker</code>, se você estiver familiarizado com ele.</li>
</ul>
<p>Mais uma vez, isso <strong>NÃO</strong> é um tutorial passo a passo. Vou anotar alguns passos mas não tudo.</p>
<h3>Camada Web Escalável (a própria aplicação web)<span class="hx:absolute hx:-mt-20" id="camada-web-escalável-a-própria-aplicação-web"></span>
    <a href="#camada-web-escal%c3%a1vel-a-pr%c3%b3pria-aplica%c3%a7%c3%a3o-web" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A primeira coisa que você precisa ter é uma aplicação web totalmente compatível com os 12 fatores.</p>
<p>Seja Ruby on Rails, Django, Laravel, Node.js ou o que for. Deve ser uma aplicação totalmente shared-nothing, que não depende de escrever nada no sistema de arquivos local. Uma que você possa facilmente desligar e iniciar instâncias independentemente. Sem sessão no estilo antigo em memória local ou em arquivos locais (prefiro evitar session affinity). Sem uploads para o sistema de arquivos local (se for necessário, você terá que montar um storage persistente externo) — sempre prefira enviar streams binários para serviços de storage gerenciados.</p>
<p>Você precisa ter um <a href="https://tomanistor.com/blog/cache-bust-that-asset/"target="_blank" rel="noopener">pipeline adequado que produza cache-busting através de fingerprinting de assets</a> (e gostando ou não, o Rails ainda tem a melhor solução out-of-the-box no seu Asset Pipeline). Você não quer se preocupar com invalidar caches em CDNs manualmente.</p>
<p>Instrumente sua aplicação, adicione o <a href="https://rpm.newrelic.com"target="_blank" rel="noopener">New Relic RPM</a>, adicione o <a href="https://rollbar.com"target="_blank" rel="noopener">Rollbar</a>.</p>
<p>De novo, isso é 2018 — você não quer fazer deploy de código ingênuo com injeção de SQL (ou qualquer outro tipo de input), sem <code>eval</code> sem verificação no seu código, sem brecha para CSRF ou XSS, etc. Vá em frente, compre a licença do <a href="https://brakemanpro.com/"target="_blank" rel="noopener">Brakeman Pro</a> e adicione ao seu pipeline de CI. Posso esperar&hellip;</p>
<p>Como isso não é um tutorial, vou assumir que você é mais do que capaz de se cadastrar no Google Cloud e encontrar seu caminho para configurar um projeto, configurar sua região e zona.</p>
<p>Demorei um pouco para entender a estrutura inicial no Google Cloud:</p>
<ul>
<li>Você começa com um <a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects"target="_blank" rel="noopener">Projeto</a>, que é o guarda-chuva para tudo que sua aplicação precisa.</li>
<li>Em seguida você cria <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-architecture"target="_blank" rel="noopener">&ldquo;clusters&rdquo;</a>. Você pode ter um cluster de produção ou staging, por exemplo. Ou um cluster web e um cluster de serviços separado para coisas não-web, e assim por diante.</li>
<li>Um cluster tem um <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-architecture#master"target="_blank" rel="noopener">&ldquo;cluster-master&rdquo;</a>, que é o controlador de tudo o mais (os comandos <code>gcloud</code> e <code>kubectl</code> falam com suas APIs).</li>
<li>Um cluster tem muitas <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-architecture#nodes"target="_blank" rel="noopener">&ldquo;node instances&rdquo;</a>, as &ldquo;máquinas&rdquo; propriamente ditas (ou, mais precisamente, instâncias de VM).</li>
<li>Cada cluster também tem pelo menos um <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/node-pools"target="_blank" rel="noopener">&ldquo;node pool&rdquo;</a> (o &ldquo;default-pool&rdquo;), que é um conjunto de node instances com a mesma configuração, o mesmo <a href="https://cloud.google.com/compute/docs/machine-types"target="_blank" rel="noopener">&ldquo;machine-type&rdquo;</a>.</li>
<li>Por fim, cada node instance executa um ou mais &ldquo;pods&rdquo; que são containers leves como LXC. É aqui que sua aplicação realmente vive.</li>
</ul>
<p>Este é um exemplo de <a href="https://cloud.google.com/sdk/gcloud/reference/container/clusters/create"target="_blank" rel="noopener">criação de cluster</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud container clusters create my-web-production \
--enable-cloud-logging \
--enable-cloud-monitoring \
--machine-type n1-standard-4 \
--enable-autoupgrade \
--enable-autoscaling --max-nodes=5 --min-nodes=2 \
--num-nodes 2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como mencionei, ele também cria um <code>default-pool</code> com um <a href="https://cloud.google.com/compute/docs/machine-types"target="_blank" rel="noopener">machine-type</a> de <code>n1-standard-4</code>. Escolha qual combinação de CPU/RAM você vai precisar para sua aplicação específica com antecedência. O tipo que escolhi tem 4 vCPUs e 15GB de RAM.</p>
<p>Por padrão ele começa com 3 nodes, então escolhi 2 inicialmente mas com auto-scaling até 5 (você pode atualizar isso depois se precisar, mas garanta que tem espaço para crescimento inicial). E você pode continuar adicionando node-pools extras para node instances de tamanhos diferentes — digamos, para workers do Sidekiq fazerem processamento pesado em background. Então você deve criar um Node Pool separado com um machine-type diferente para seu conjunto de node instances, por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud container node-pools create large-pool \
--cluster=my-web-production \
--node-labels=pool=large \
--machine-type=n1-highcpu-8 \
--num-nodes 1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse outro pool controla 1 node do tipo <code>n1-highcpu-8</code> que tem 8 vCPUs com 7,2 GB de RAM. Mais CPUs, menos memória. Existe uma categoria <code>highmem</code> que tem menos CPUs com muito mais memória. De novo, saiba o que você quer com antecedência.</p>
<p>A parte importante aqui é o <code>--node-labels</code> — é assim que vou mapear o deployment para escolher entre Node Pools (neste caso, entre o <code>default-pool</code> e o <code>large-pool</code>).</p>
<p>Após criar um cluster, você precisa executar o seguinte comando para buscar suas credenciais:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud container clusters get-credentials my-web-production</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso configura o comando <code>kubectl</code> também. Se você tiver mais de um cluster (digamos, um <code>my-web-production</code> e <code>my-web-staging</code>), precisa ter muito cuidado de sempre fazer <code>get-credentials</code> para o cluster correto primeiro, caso contrário pode acabar executando um deploy de staging no cluster de produção.</p>
<p>Como isso é confuso, modifiquei meu ZSH PROMPT para sempre mostrar com qual cluster estou lidando. Adaptei do <a href="https://github.com/superbrothers/zsh-kubectl-prompt"target="_blank" rel="noopener">zsh-kubectl-prompt</a>:</p>
<p><img src="https://github.com/superbrothers/zsh-kubectl-prompt/raw/master/images/screenshot001.png" alt="zsh kubectl prompt"  loading="lazy" /></p>
<p>Como você acabará tendo múltiplos clusters em uma aplicação grande, recomendo fortemente adicionar esse PROMPT ao seu shell.</p>
<p>Agora, como você faz deploy da sua aplicação nos pods dentro dessas node instances?</p>
<p>Você precisa ter um <code>Dockerfile</code> no repositório do seu projeto de aplicação para gerar uma imagem Docker. Este é um exemplo para uma aplicação Ruby on Rails:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>FROM ruby:2.4.3
ENV RAILS_ENV production
ENV SECRET_KEY_BASE xpto
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get update &amp;&amp; apt-get install -y nodejs postgresql-client cron htop vim
ADD Gemfile* /app/
WORKDIR /app
RUN gem update bundler --pre
RUN bundle install --without development test
RUN npm install
ADD . /app
RUN cp config/database.yml.prod.example config/database.yml &amp;&amp; cp config/application.yml.example config/application.yml
RUN RAILS_GROUPS=assets bundle exec rake assets:precompile</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No Google Cloud Web Console, você encontrará um <a href="https://cloud.google.com/container-registry/"target="_blank" rel="noopener">&ldquo;Container Registry&rdquo;</a>, que é um Registry Docker Privado.</p>
<p>Você precisa adicionar a URL remota à sua configuração local assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git remote add gcloud https://source.developers.google.com/p/my-project/r/my-app</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora você pode fazer <code>git push gcloud master</code>. Recomendo também adicionar <a href="https://cloud.google.com/container-builder/docs/running-builds/automate-builds"target="_blank" rel="noopener">triggers</a> para taggear suas imagens. Adiciono 2 triggers: um para taggeá-la com <code>latest</code> e outro para taggeá-la com um número de versão aleatório. Você vai precisar deles mais tarde.</p>
<p>Depois de adicionar o repositório do registry como um remote na sua configuração git (<code>git remote add</code>) e fazer push nele, ele deve começar a construir sua imagem Docker com as tags adequadas que você configurou com os triggers.</p>
<p>Certifique-se de que sua aplicação Ruby on Rails não tem nada nos initializers que exija uma conexão com o banco de dados, pois ele não está disponível. Isso é algo com que você pode travar quando seu build Docker falha por causa da tarefa <code>assets:precompile</code> que carregou um initializer que acidentalmente chama um Model — e isso dispara o <code>ActiveRecord::Base</code> tentando se conectar.</p>
<p>Além disso, certifique-se de que a versão do Ruby no <code>Dockerfile</code> corresponde à do <code>Gemfile</code>, caso contrário também vai falhar.</p>
<p>Reparou no estranho <code>config/application.yml</code> acima? Esse é do <a href="https://github.com/laserlemon/figaro"target="_blank" rel="noopener">figaro</a>. Também recomendo usar algo para facilitar a configuração de variáveis ENV no seu sistema. Não gosto dos secrets do Rails, e ele não é exatamente amigável para a maioria dos sistemas de deploy depois que o Heroku popularizou as ENV vars. Fique com ENV vars. O Kubernetes também vai agradecer por isso.</p>
<p>Agora, você pode sobrescrever qualquer variável ENV do arquivo YAML de Deployment do Kubernetes. Agora é uma boa hora para mostrar um exemplo disso. Você pode nomeá-lo <code>deploy/web.yml</code> ou como preferir e — claro — comitá-lo no seu repositório de código-fonte.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">RollingUpdate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">rollingUpdate</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">maxSurge</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">maxUnavailable</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">minReadySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">gcr.io/my-project/my-app:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">imagePullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">4001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;passenger&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;start&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-p&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;4001&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-e&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;production&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="s2">&#34;--max-pool-size&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;2&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--min-instances&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;2&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--no-friendly-error-pages&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="s2">&#34;--max-request-queue-time&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;10&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--max-request-time&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;10&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="s2">&#34;--pool-idle-time&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;0&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--memory-limit&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;300&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;RAILS_LOG_TO_STDOUT&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;RAILS_ENV&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;production&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c"># ... obviamente reduzi as muitas ENV vars por brevidade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;REDIS_URL&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">REDIS_URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;SMTP_USERNAME&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">SMTP_USERNAME</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;SMTP_PASSWORD&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">SMTP_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c"># ... esta parte abaixo é obrigatória para Cloud SQL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DB_HOST</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">127.0.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DB_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-db-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DB_USER</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-db-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">username</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">gcr.io/cloudsql-docker/gce-proxy:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/cloud_sql_proxy&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--dir=/cloudsql&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s2">&#34;-instances=my-project:us-west1:my-db=tcp:5432&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s2">&#34;-credential_file=/secrets/cloudsql/credentials.json&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-instance-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/secrets/cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">readOnly</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ssl-certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/ssl/certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-instance-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">secret</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-instance-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ssl-certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">hostPath</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/ssl/certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="l">emptyDir:</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem muita coisa acontecendo aqui. Então deixa eu quebrar um pouco:</p>
<ul>
<li>O <code>kind</code> e o <code>apiVersion</code> são importantes — você precisa ficar de olho na documentação se eles mudarem. Isso é o que chamamos de <a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/"target="_blank" rel="noopener">Deployment</a>. Antigamente existia um Replication Controller (você encontrará em tutoriais antigos), mas não está mais em uso. A recomendação é usar um <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/"target="_blank" rel="noopener">ReplicaSet</a>.</li>
<li>Nomeie as coisas corretamente — aqui você tem <code>metadata:name</code> com <code>web</code>. Preste muita atenção também ao <code>spec:template:metadata:labels</code> onde estou rotulando cada pod com um label de <code>app: web</code>, você vai precisar disso para poder selecionar esses pods mais tarde na seção Service abaixo.</li>
<li>Em seguida tenho o <code>spec:strategy</code> onde configuramos o Rolling Update, então se você tem 10 pods, ele vai terminar um, inicializar o novo e continuar fazendo isso, sem nunca derrubar tudo de uma vez.</li>
<li><code>spec:replicas</code> declara quantos Pods quero de uma vez. Você terá que calcular manualmente o machine-type do node-pool e então dividir quantos CPUs/RAM totais você tem pelo quanto precisa para cada instância de aplicação.</li>
<li>Lembra da imagem Docker que geramos acima com a tag &rsquo;latest&rsquo;? Você a referencia em <code>spec:template:spec:containers:image</code></li>
<li>Estou usando o Passenger com configuração de produção (confira a <a href="https://www.phusionpassenger.com/library/config/reference/"target="_blank" rel="noopener">documentação do Phusion</a>, não copie isso só assim).</li>
<li>Na seção <code>spec:template:spec:containers:env</code> posso sobrescrever as ENV vars com os secrets reais de produção. E você vai notar que posso hard-codar valores ou usar essa estranha construção:</li>
</ul>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;SMTP_USERNAME&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">SMTP_USERNAME</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aqui está referenciando um armazenamento de <a href="https://kubernetes.io/docs/concepts/configuration/secret/"target="_blank" rel="noopener">&ldquo;Secret&rdquo;</a> que nomeei &ldquo;my-env&rdquo;. E é assim que você cria o seu:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl create secret generic my-env \
--from-literal=REDIS_URL=redis://foo.com:18821 \
--from-literal=SMTP_USERNAME=foobar</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Leia a documentação pois você pode carregar arquivos de texto em vez de declarar tudo pela linha de comando.</p>
<p>Como disse antes, prefiro usar um serviço gerenciado para o banco de dados. Você pode definitivamente carregar sua própria imagem Docker, mas realmente não recomendo. O mesmo vale para outros serviços similares a banco de dados como Redis, Mongo. Se você vem da AWS, o <a href="https://cloud.google.com/sql/docs/postgres/"target="_blank" rel="noopener">Google Cloud SQL</a> é como o RDS.</p>
<p>Após criar sua instância PostgreSQL você não consegue acessá-la diretamente da aplicação web. No final, você tem um boilerplate para uma segunda imagem Docker, um <a href="https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine"target="_blank" rel="noopener">&ldquo;CloudSQL Proxy&rdquo;</a>.</p>
<p>Para isso funcionar você precisa primeiro criar uma Service Account:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud sql users create proxyuser host --instance=my-db --password=abcd1234</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Após criar a instância PostgreSQL ela vai pedir para você baixar uma credencial JSON, então tenha cuidado e salve-a em algum lugar seguro. Não preciso dizer que você também deve gerar uma senha forte e segura. Em seguida precisa criar secrets extras:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl create secret generic cloudsql-instance-credentials \
--from-file=credentials.json=/home/myself/downloads/my-db-12345.json

kubectl create secret generic cloudsql-db-credentials \
--from-literal=username=proxyuser --from-literal=password=abcd1234</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esses são referenciados nesta parte do Deployment:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">gcr.io/cloudsql-docker/gce-proxy:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/cloud_sql_proxy&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--dir=/cloudsql&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s2">&#34;-instances=my-project:us-west1:my-db=tcp:5432&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s2">&#34;-credential_file=/secrets/cloudsql/credentials.json&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-instance-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/secrets/cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">readOnly</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ssl-certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/ssl/certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-instance-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql-instance-credentials</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ssl-certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hostPath</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/ssl/certs</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudsql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">emptyDir:</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja que você precisa adicionar o nome do banco de dados (&ldquo;my-db&rdquo; neste exemplo) na cláusula <code>-instance</code> no comando.</p>
<p>A propósito, o <code>gce-proxy:latest</code> se referia à versão 1.09 na época em que esse post foi publicado. Mas já havia uma versão 1.11. Essa me deu dores de cabeça, derrubando conexões e adicionando um timeout super longo. Então voltei para a 1.09 (latest) e tudo funcionou como esperado. Fique atento! Nem tudo que é novinho é bom. Em infraestrutura, você quer ficar com o que é estável.</p>
<p>Você também pode querer a opção de carregar uma instância CloudSQL separada em vez de tê-la em cada pod, para que os pods pudessem se conectar a apenas um proxy. Pode ser interessante ler <a href="https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/49"target="_blank" rel="noopener">essa thread</a> sobre o assunto.</p>
<p>Parece que nada é exposto a nada a menos que você diga explicitamente. Então precisamos expor esses pods através do que chamamos de <a href="https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport"target="_blank" rel="noopener">Node Port Service</a>. Vamos criar um arquivo <code>deploy/web-svc.yaml</code> também:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web-svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sessionAffinity</span><span class="p">:</span><span class="w"> </span><span class="l">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">4001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">NodePort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">web</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É por isso que destaquei a importância do <code>spec:template:metadata:labels</code>, para que possamos usá-lo aqui no <code>spec:selector</code> para selecionar os pods corretos.</p>
<p>Agora podemos fazer deploy desses 2 assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl create -f deploy/web.yml
kubectl create -f deploy/web-svc.yml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E você pode ver os pods sendo criados com <code>kubectl get pods --watch</code>.</p>
<h2>O Load Balancer<span class="hx:absolute hx:-mt-20" id="o-load-balancer"></span>
    <a href="#o-load-balancer" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Muitos tutoriais vão expor esses pods diretamente através de um Service diferente, chamado <a href="https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/"target="_blank" rel="noopener">Load Balancer</a>. Não tenho tanta certeza sobre como isso se comporta sob pressão e se tem terminação SSL, etc. Então decidi ir com tudo com um <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/"target="_blank" rel="noopener">Ingress</a> Load Balancer usando o <a href="https://www.nginx.com/products/nginx/kubernetes-ingress-controller/"target="_blank" rel="noopener">NGINX Controller</a>.</p>
<p>Primeiro de tudo, decidi criar um node-pool separado para ele, por exemplo, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud container node-pools create web-load-balancer \
--cluster=my-web-production \
--node-labels=role=load-balancer \
--machine-type=g1-small \
--num-nodes 1 \
--max-nodes 3 --min-nodes=1 \
--enable-autoscaling </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Assim como quando criamos o exemplo de <code>large-pool</code>, aqui você precisa cuidar de adicionar <code>--node-labels</code> para que o controller seja instalado aqui em vez do <code>default-pool</code>. Você vai precisar saber o nome da node instance, podemos fazer isso assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ gcloud compute instances list
NAME                                             ZONE        MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP      STATUS
gke-my-web-production-default-pool-123-123       us-west1-a  n1-standard-4               10.128.0.1   123.123.123.12   RUNNING
gke-my-web-production-large-pool-123-123         us-west1-a  n1-highcpu-8                10.128.0.2   50.50.50.50      RUNNING
gke-my-web-production-web-load-balancer-123-123  us-west1-a  g1-small                    10.128.0.3   70.70.70.70      RUNNING</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Vamos salvar assim por enquanto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export LB_INSTANCE_NAME=gke-my-web-production-web-load-balancer-123-123</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode reservar manualmente um IP externo e dar um nome a ele assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud compute addresses create ip-web-production \
        --ip-version=IPV4 \
        --global</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Para fins de exemplo, digamos que ele gerou um IP reservado &ldquo;111.111.111.111&rdquo;. Então vamos buscá-lo e salvá-lo por enquanto assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export LB_ADDRESS_IP=$(gcloud compute addresses list | grep &#34;ip-web-production&#34; | awk &#39;{print $3}&#39;)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, vamos vincular esse endereço à node instance do load balancer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export LB_INSTANCE_NAT=$(gcloud compute instances describe $LB_INSTANCE_NAME | grep -A3 networkInterfaces: | tail -n1 | awk -F&#39;: &#39; &#39;{print $2}&#39;)
gcloud compute instances delete-access-config $LB_INSTANCE_NAME \
    --access-config-name &#34;$LB_INSTANCE_NAT&#34;
gcloud compute instances add-access-config $LB_INSTANCE_NAME \
    --access-config-name &#34;$LB_INSTANCE_NAT&#34; --address $LB_ADDRESS_IP</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Feito isso, podemos adicionar o resto da configuração de Deployment do Ingress. Vai ser um pouco longo, mas é basicamente boilerplate. Vamos começar definindo outra aplicação web que chamaremos de <code>default-http-backend</code>, usada para responder a requisições HTTP caso nossos pods web não estejam disponíveis por algum motivo. Vamos chamá-la de <code>deploy/default-web.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default-http-backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">default-http-backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">terminationGracePeriodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default-http-backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># Any image is permissable as long as:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># 1. It serves a 404 page at /</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># 2. It serves 200 on a /healthz endpoint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">gcr.io/google_containers/defaultbackend:1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">livenessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/healthz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">scheme</span><span class="p">:</span><span class="w"> </span><span class="l">HTTP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">timeoutSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">10m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">20Mi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">10m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">20Mi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não precisa mudar nada aqui — e a essa altura você já deve estar familiarizado com o template de Deployment. Você já sabe que precisa expô-lo através de um NodePort, então vamos adicionar um <code>deploy/default-web-svc.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default-http-backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">default-http-backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">NodePort</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, não precisa mudar nada. Os próximos 3 arquivos são as partes importantes. Primeiro, criaremos um NGINX Load Balancer — vamos chamá-lo de <code>deploy/nginx.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-ingress-controller</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-ingress-lb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># that said, since hostPort is broken on CNI (https://github.com/kubernetes/kubernetes/issues/31307) we have to use hostNetwork where CNI is used</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">hostNetwork</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">terminationGracePeriodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">nodeSelector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">role</span><span class="p">:</span><span class="w"> </span><span class="l">load-balancer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">args</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">/nginx-ingress-controller</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;--default-backend-service=$(POD_NAMESPACE)/default-http-backend&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;--default-ssl-certificate=$(POD_NAMESPACE)/cloudflare-secret&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">POD_NAME</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">fieldRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">fieldPath</span><span class="p">:</span><span class="w"> </span><span class="l">metadata.name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">POD_NAMESPACE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">fieldRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">fieldPath</span><span class="p">:</span><span class="w"> </span><span class="l">metadata.namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">imagePullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">livenessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/healthz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">10254</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">scheme</span><span class="p">:</span><span class="w"> </span><span class="l">HTTP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">timeoutSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-ingress-controller</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">http</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">443</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">https</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/nginx-ssl/dhparam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">tls-dhparam-vol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">tls-dhparam-vol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">secret</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">tls-dhparam</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Preste atenção ao <code>nodeSelector</code> para usar o node label que adicionamos ao criar o novo node-pool.</p>
<p>Você pode querer mexer nos labels, no número de réplicas se precisar. Mas aqui você vai notar que ele monta um volume que nomeei como <code>tls-dhparam-vol</code>. Esses são os <a href="https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html#Forward_Secrecy_&amp;_Diffie_Hellman_Ephemeral_Parameters"target="_blank" rel="noopener">Parâmetros Efêmeros Diffie Hellman</a>. É assim que geramos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo openssl dhparam -out ~/documents/dhparam.pem 2048

kubectl create secret generic tls-dhparam --from-file=/home/myself/documents/dhparam.pem

kubectl create secret generic tls-dhparam --from-file=/home/myself/documents/dhparam.pem</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note também que estou usando a versão &ldquo;0.9.0-beta_5&rdquo; para a imagem do controller. Funciona bem, sem problemas até agora. Mas fique de olho nas release notes para versões mais novas e faça seus próprios testes.</p>
<p>De novo, vamos expor esse controller Ingress através do Load Balancer Service. Vamos chamá-lo de <code>deploy/nginx-svc.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">LoadBalancer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">loadBalancerIP</span><span class="p">:</span><span class="w"> </span><span class="m">111.111.111.111</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">http</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="l">http</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">https</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">443</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="l">https</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-ingress-lb</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembra do IP externo estático que reservamos acima e salvamos na ENV var <code>LB_INGRESS_IP</code>? Esse é o que precisa colocar na seção <code>spec:loadBalancerIP</code>. Esse também é o IP que você vai adicionar como &ldquo;registro A&rdquo; no seu serviço DNS (digamos, mapeando seu &ldquo;<a href="https://www.my-app.com.br"target="_blank" rel="noopener">www.my-app.com.br</a>&rdquo; no CloudFlare).</p>
<p>Por fim, podemos criar a configuração do Ingress em si — vamos criar um <code>deploy/ingress.yml</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/ingress.class</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;nginx&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nginx.org/ssl-services</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;web-svc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/ingress.global-static-ip-name</span><span class="p">:</span><span class="w"> </span><span class="l">ip-web-production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ingress.kubernetes.io/ssl-redirect</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ingress.kubernetes.io/rewrite-target</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">www.my-app.com.br</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">cloudflare-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">www.my-app.com.br</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">serviceName</span><span class="p">:</span><span class="w"> </span><span class="l">web-svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">servicePort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cuidado com as annotations acima que conectam tudo. Ele vincula o serviço NodePort que criamos para os pods web com o nginx ingress controller e adiciona terminação SSL através daquele <code>spec:tls:secretName</code>. Como você cria isso? Primeiro precisa comprar um certificado SSL — usando o CloudFlare como exemplo novamente.</p>
<p>Quando terminar a compra, o provedor deve te dar os arquivos secretos para download (guarde-os em segurança! uma pasta pública do Dropbox não é segura!). Então precisa adicioná-los à infraestrutura assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl create secret tls cloudflare-secret \
--key ~/downloads/private.pem \
--cert ~/downloads/fullchain.pem</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora que editamos um monte de arquivos, podemos fazer deploy de todo o stack do load balancer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl create -f deploy/default-web.yml
kubectl create -f deploy/default-web-svc.yml
kubectl create -f deploy/nginx.yml
kubectl create -f deploy/nginx-svc.yml
kubectl create -f deploy/ingress.yml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa configuração do NGINX Ingress é baseada no <a href="https://zihao.me/post/cheap-out-google-container-engine-load-balancer/"target="_blank" rel="noopener">post do blog do Zihao Zhang</a>. Existem também exemplos no <a href="https://github.com/kubernetes-incubator/external-dns/blob/master/docs/tutorials/nginx-ingress.md"target="_blank" rel="noopener">repositório kubernetes incubator</a>. Vale a pena conferir também.</p>
<p>Se você fez tudo certo até aqui, <code>https://www.my-app-com.br</code> deve carregar sua aplicação web. Pode ser interessante verificar o Time to First-Byte (TTFB). Você pode fazer isso passando pelo CloudFlare assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl -vso /dev/null -w &#34;Connect: %{time_connect} \n TTFB: %{time_starttransfer} \n Total time: %{time_total} \n&#34; https://www.my-app.com.br</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou, se estiver com TTFB lento, pode contornar o CloudFlare fazendo isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl --resolve www.my-app.com.br:443:111.111.111.111 https://www.my-app.com.br -svo /dev/null -k -w &#34;Connect: %{time_connect} \n TTFB: %{time_starttransfer} \n Total time: %{time_total} \n&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O TTFB deve ficar em torno de 1 segundo ou menos. Qualquer coisa muito acima disso pode indicar um problema na sua aplicação. Verifique os machine types da sua node instance, o número de workers carregados por pod, a versão do CloudSQL proxy, a versão do NGINX controller e assim por diante. Isso é um processo de tentativa e erro, pelo que sei. Cadastre-se em serviços como <a href="https://loader.io"target="_blank" rel="noopener">Loader</a> ou mesmo <a href="https://www.webpagetest.org"target="_blank" rel="noopener">Web Page Test</a> para ter insights.</p>
<h2>Rolling Updates<span class="hx:absolute hx:-mt-20" id="rolling-updates"></span>
    <a href="#rolling-updates" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora, com tudo funcionando, como realizamos o Rolling Update que mencionei no início? Primeiro você faz <code>git push</code> para o repositório do Container Registry e aguarda a construção da imagem Docker.</p>
<p>Lembra que disse para deixar um trigger tagear a imagem com um número de versão aleatório? Vamos usá-lo (você pode ver isso na lista de Build History no Container Registry, no console do Google Cloud):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl set image deployment web my-app=gcr.io/my-project/my-app:1238471234g123f534f543541gf5 --record</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você deve usar o mesmo nome e imagem declarados no <code>deploy/web.yml</code> acima. Isso vai começar a fazer rollout da atualização adicionando um novo pod, então encerrando um pod e assim por diante, sem downtime para seus usuários.</p>
<p>Rolling updates precisam ser executados com cuidado. Por exemplo, se seu novo deploy exige uma migração de banco de dados, você precisa adicionar uma janela de manutenção (ou seja: faça isso quando houver pouco ou nenhum tráfego, como de madrugada). Então você pode executar o comando migrate assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl get pods # para obter o nome de um pod

kubectl exec -it my-web-12324-121312 /app/bin/rails db:migrate

# você também pode entrar em bash em um pod assim, mas lembre que este é um container efêmero, então arquivos que você editar e escrever ali desaparecem no próximo restart:

kubectl exec -it my-web-12324-121312 bash</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Para fazer redeploy de tudo sem recorrer ao rolling update, você precisa fazer isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl delete -f deploy/web.yml &amp;&amp; kubectl apply -f deploy/web.yml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você encontrará uma explicação mais completa no <a href="https://tachingchen.com/blog/Kubernetes-Rolling-Update-with-Deployment/"target="_blank" rel="noopener">blog do Ta-Ching</a>.</p>
<h2>Bônus: Auto Snapshots<span class="hx:absolute hx:-mt-20" id="bônus-auto-snapshots"></span>
    <a href="#b%c3%b4nus-auto-snapshots" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um item que tinha na minha lista de &ldquo;queria/precisava&rdquo; no início é a capacidade de ter storage persistente montável com backups/snapshots automáticos. O Google Cloud fornece metade disso por enquanto. Você pode criar discos persistentes para montar nos seus pods, mas não tem um recurso para fazer backup automático dele. Pelo menos tem APIs para criar snapshots manualmente.</p>
<p>Para este exemplo, vamos criar um novo disco SSD e formatá-lo primeiro:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud compute disks create --size 500GB my-data --type pd-ssd

gcloud compute instances list</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O último comando é para copiarmos o nome de uma node instance. Digamos que seja <code>gke-my-web-app-default-pool-123-123</code>. Vamos anexar o disco <code>my-data</code> a ela:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud compute instances attach-disk gke-my-web-app-default-pool-123-123 --disk my-data --device-name my-data

gcloud compute ssh gke-my-web-app-default-pool-123-123</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O último comando faz SSH na instância. Podemos listar os discos anexados com <code>sudo lsblk</code> e você verá o disco de 500GB, provavelmente como <code>/dev/sdb</code>, mas certifique-se de que está correto porque vamos formatá-lo!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora podemos sair da sessão SSH e desanexar o disco:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gcloud compute instances detach-disk gke-my-web-app-default-pool-123-123 --disk my-data</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode montar esse disco nos seus pods adicionando o seguinte ao seu yaml de deployment:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>spec:
  containers:
    - image: ...
      name: my-app
      volumeMounts:
        - name: my-data
          mountPath: /data
          # readOnly: true
   # ...
   volumes:
     - name: my-data
       gcePersistentDisk:
         pdName: my-data
         fsType: ext4</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, vamos criar um arquivo de deployment CronJob como <code>deploy/auto-snapshot.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">batch/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">CronJob</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">auto-snapshot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 4 * * *&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">concurrencyPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Forbid</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">jobTemplate</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">restartPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">OnFailure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">auto-snapshot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grugnog/google-cloud-auto-snapshot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/opt/entrypoint.sh&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;GOOGLE_CLOUD_PROJECT&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;my-project&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;GOOGLE_APPLICATION_CREDENTIALS&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/credential/credential.json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span>- <span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/credential</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">editor-credential</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">editor-credential</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">secret</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">editor-credential</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como já fizemos antes, você precisará criar outra Service Account com permissões de editor na seção &ldquo;IAM &amp; admin&rdquo; do console do Google Cloud, depois baixar a credencial JSON e, por fim, fazer upload dela assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kubectl create secret generic editor-credential \
--from-file=credential.json=/home/myself/download/my-project-1212121.json</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note também que, como um cron job normal, existe um parâmetro de schedule que você pode querer alterar. No exemplo, &ldquo;0 4 * * *&rdquo; significa que o snapshot vai rodar todo dia às 4h da manhã.</p>
<p>Confira o <a href="https://github.com/grugnog/google-cloud-auto-snapshot"target="_blank" rel="noopener">repositório original</a> dessa solução para mais detalhes.</p>
<p>E por agora é isso!</p>
<p>Como disse no início, este não é um procedimento completo, apenas destaques de algumas das partes importantes. Se você é novo no Kubernetes, acabou de ler sobre Deployment, Service, Ingress, mas ainda tem ReplicaSet, DaemonSet e muito mais para explorar.</p>
<p>Acho que já está longo demais para adicionar uma explicação de configuração de <a href="https://cloud.google.com/sql/docs/mysql/high-availability"target="_blank" rel="noopener">Alta Disponibilidade multi-região</a>, então vamos deixar por aqui.</p>
<p>Correções ou sugestões são muito bem-vindas, pois ainda estou no processo de aprendizado, e tem um monte de coisas que eu mesmo ainda não sei.</p>
]]></content:encoded><category>rails</category><category>cloud</category><category>containers</category><category>devops</category></item><item><title>A Solução de Upload Nativa do Rails 5.2: ActiveStorage</title><link>https://akitaonrails.github.io/2017/07/07/a-solucao-de-upload-nativa-do-rails-5-2-activestorage/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/07/07/a-solucao-de-upload-nativa-do-rails-5-2-activestorage/</guid><pubDate>Fri, 07 Jul 2017 14:40:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;Atualização 07/11/2017&lt;/strong&gt;: Logo depois do nosso brainstorm no Twitter, DHH e @gauravtiwari se mobilizaram e iniciaram um &lt;a href="https://github.com/rails/activestorage/pull/35"target="_blank" rel="noopener"&gt;novo branch&lt;/a&gt; para adicionar suporte a Direct Upload para a nuvem imediatamente. Claro, ainda é trabalho em andamento, mas vai ser ótimo ter esse suporte nativo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atualização 07/20/2017:&lt;/strong&gt; Preciso esclarecer alguns pontos. Primeiro, estou recomendando a solução proprietária do Cloudinary, mas há ressalvas. Você está trocando facilidade de uso por vendor lock-in. E só recomendo para fotos e imagens. Se precisar fazer upload de arquivos gigantes (vídeos, ou binários arbitrários como tarballs pesados), você precisa pesquisar mais e talvez construir uma solução customizada com Shrine ou similar. Outro ponto que faço é que se você usa Heroku, sua única opção é Direct Upload para serviços de nuvem como o AWS S3. Já entreguei vários apps que bateram no &lt;a href="https://devcenter.heroku.com/articles/request-timeout#uploading-large-files"target="_blank" rel="noopener"&gt;H12 Routing Timeout&lt;/a&gt; — está na documentação oficial. Mas há pouco deployei um Rails 5.1 simples com Carrierwave padrão e um formulário HTML multipart. Consegui fazer upload de um vídeo de mais de 2GB e ele passou pela camada de roteamento do Heroku. Parece que o roteador absorve o arquivo, deixa o upload terminar e só então passa para a aplicação Rails. Agora, se você bloqueia a requisição (por exemplo, fazendo upload do Rails para o S3 ou qualquer operação demorada), a camada de roteamento vai dar H12/timeout após 30 segundos. Então, se você permitir uploads de arquivos grandes, vai acabar tendo desconexões aleatórias. Mesmo que você consiga receber o arquivo, não pode mantê-lo no sistema de arquivos (que é volátil num Dyno do Heroku), então ainda precisa enviá-lo para algum lugar — e essa é a parte que pode dar timeout. Portanto, o Direct Upload continua sendo a abordagem correta no Heroku.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>Atualização 07/11/2017</strong>: Logo depois do nosso brainstorm no Twitter, DHH e @gauravtiwari se mobilizaram e iniciaram um <a href="https://github.com/rails/activestorage/pull/35"target="_blank" rel="noopener">novo branch</a> para adicionar suporte a Direct Upload para a nuvem imediatamente. Claro, ainda é trabalho em andamento, mas vai ser ótimo ter esse suporte nativo.</p>
<p><strong>Atualização 07/20/2017:</strong> Preciso esclarecer alguns pontos. Primeiro, estou recomendando a solução proprietária do Cloudinary, mas há ressalvas. Você está trocando facilidade de uso por vendor lock-in. E só recomendo para fotos e imagens. Se precisar fazer upload de arquivos gigantes (vídeos, ou binários arbitrários como tarballs pesados), você precisa pesquisar mais e talvez construir uma solução customizada com Shrine ou similar. Outro ponto que faço é que se você usa Heroku, sua única opção é Direct Upload para serviços de nuvem como o AWS S3. Já entreguei vários apps que bateram no <a href="https://devcenter.heroku.com/articles/request-timeout#uploading-large-files"target="_blank" rel="noopener">H12 Routing Timeout</a> — está na documentação oficial. Mas há pouco deployei um Rails 5.1 simples com Carrierwave padrão e um formulário HTML multipart. Consegui fazer upload de um vídeo de mais de 2GB e ele passou pela camada de roteamento do Heroku. Parece que o roteador absorve o arquivo, deixa o upload terminar e só então passa para a aplicação Rails. Agora, se você bloqueia a requisição (por exemplo, fazendo upload do Rails para o S3 ou qualquer operação demorada), a camada de roteamento vai dar H12/timeout após 30 segundos. Então, se você permitir uploads de arquivos grandes, vai acabar tendo desconexões aleatórias. Mesmo que você consiga receber o arquivo, não pode mantê-lo no sistema de arquivos (que é volátil num Dyno do Heroku), então ainda precisa enviá-lo para algum lugar — e essa é a parte que pode dar timeout. Portanto, o Direct Upload continua sendo a abordagem correta no Heroku.</p>
<p>O DHH acabou de anunciar uma funcionalidade nova para o Rails 5.2 que está por vir. É o <a href="https://github.com/rails/activestorage"target="_blank" rel="noopener">ActiveStorage</a>.</p>
<p>Ele deve se tornar a solução padrão para suporte a uploads de arquivos, basicamente substituindo o Paperclip, o Carrierwave, e algumas funcionalidades do Dragonfly e do Shrine (que fazem bem mais do que isso).</p>
<p>Escrevo este post não para apresentar a solução, mas para esclarecer algumas críticas que fiz no Twitter. Acontece que o Twitter é uma plataforma péssima para discussões mais aprofundadas, daí este texto.</p>
<p>A thread original no Twitter pode ser encontrada <a href="https://twitter.com/AkitaOnRails/status/882998977754537984"target="_blank" rel="noopener">aqui</a>.</p>
<p>O processo tem pelo menos 3 pontos importantes a considerar:</p>
<ol>
<li>O upload do arquivo, do usuário final até o seu controller Rails.</li>
<li>Uma etapa opcional de transformação (que você deveria fazer, para redimensionar imagens e servi-las para diferentes dispositivos e resoluções de tela).</li>
<li>Servir o blob do arquivo de volta ao usuário final como uma URL de imagem, por exemplo.</li>
</ol>
<h3>A Etapa de Upload<span class="hx:absolute hx:-mt-20" id="a-etapa-de-upload"></span>
    <a href="#a-etapa-de-upload" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A maioria das soluções simples de upload — como o ActiveStorage e as antigas como o Paperclip original e o Carrierwave numa instalação padrão — basicamente configuram o formulário HTML como multipart e adicionam um campo de arquivo HTML simples. Isso vai enviar o formulário diretamente para uma action do controller Rails, que receberá o arquivo no hash <code>params</code> e você pode lidar com o blob binário a partir daí.</p>
<p>Numa implantação ingênua, isso vai <strong>bloquear</strong> o MRI durante todo o upload. Se o arquivo for muito grande, pode bloquear qualquer outra requisição entrante pela duração desse upload. (Tecnicamente, como o Rails suporta threads MRI e threads MRI são teoricamente não-bloqueantes para operações de IO, não deveria ser tão grave quanto parece.)</p>
<p>Felizmente, acredito que ninguém em sã consciência expõe um processo MRI diretamente para a internet. Normalmente ficamos atrás de um <a href="https://www.digitalocean.com/community/tutorials/how-to-deploy-a-rails-app-with-puma-and-nginx-on-ubuntu-14-04"target="_blank" rel="noopener">proxy reverso</a>, como Haproxy, NGINX, Apache HTTPD ou algo similar.</p>
<p>E geralmente esses proxies reversos são os que recebem o upload e só fazem proxy da requisição quando o upload está concluído. Assim, a aplicação MRI/Rails pode continuar respondendo às outras requisições enquanto isso.</p>
<p>Então, se você tem uma infraestrutura customizada, está bem.</p>
<p>Se você usa algo como Heroku, está basicamente ferrado. A camada de roteamento do Heroku tem um timeout fixo de 30 segundos. Acho isso uma coisa boa, porque você não deveria ter requisições que demoram tanto para processar. Ora, você não deveria ter uma requisição respondendo em 1 segundo, quanto mais 30 segundos.</p>
<p>Mas upload de arquivos é a exceção. Um arquivo grande demora para transferir pela internet. E aí o timeout do Heroku entra em ação e interrompe a transmissão. O usuário tenta de novo, e se você tiver gente suficiente acessando, começa a encher a fila HTTP até ter timeouts em cascata.</p>
<p>É por isso que a única solução disponível para Heroku é fazer &ldquo;Direct Upload&rdquo; para um serviço de nuvem como o AWS S3. Você pode adicionar o add-on <a href="https://github.com/dwilkie/carrierwave_direct"target="_blank" rel="noopener">Carrierwave Direct</a>, ou usar uma solução completa de terceiros como o <a href="http://www.akitaonrails.com/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads"target="_blank" rel="noopener">Cloudinary</a>, com sua biblioteca cliente Attachinary para facilitar as coisas. E pronto!</p>
<p>O Active Storage, como está agora, vai funcionar bem para qualquer deploy customizado razoável (NGINX + Rails/Puma) <del>mas não vai funcionar de jeito nenhum no Heroku</del> e quando o <a href="https://github.com/rails/activestorage/pull/35"target="_blank" rel="noopener">novo branch</a> estiver pronto, será uma boa opção para usar no Heroku também.</p>
<h3>A Etapa de Transformação<span class="hx:absolute hx:-mt-20" id="a-etapa-de-transformação"></span>
    <a href="#a-etapa-de-transforma%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Isso pode ser feito logo após o upload ou logo antes de servir o arquivo de volta ao usuário.</p>
<p>A primeira opção pode ser feita de forma síncrona ou assíncrona.</p>
<p>Síncrona é &ldquo;ruim&rdquo; (quer dizer, na própria action do controller, porque essa etapa é CPU-intensiva e leva tempo). É basicamente transformar a imagem (usando algo como Rmagick ou MiniMagick) em outras versões de tamanhos diferentes (thumbnail, versão mobile, versão high-dpi, etc.) e armazenar os caminhos para as diferentes versões no storage.</p>
<p>Assíncrona é delegar essa transformação custosa para o ActiveJob, para que um worker do Sidekiq pegue a tarefa mais tarde e faça o processamento. Enquanto isso, você pode servir um placeholder se a versão específica ainda não estiver pronta.</p>
<p>Uma ressalva é que se você tem cloud storage e transformação assíncrona em jobs, vai ter muito tráfego, porque você vai gastar tempo fazendo upload para o cloud storage, depois o job vai ter que baixar de lá, fazer a transformação e fazer novos uploads.</p>
<p>A outra solução é não fazer nenhum processamento e delegar o processamento para ser feito sob demanda. É isso que o <a href="http://cloudinary.com/documentation/image_transformations"target="_blank" rel="noopener">Cloudinary/Attachinary</a> de terceiros faz — você pode fazer transformações customizadas usando parâmetros URI. Ele faz a transformação uma vez e faz cache dos resultados para uso futuro. Um exemplo de URL de transformação de imagem do Cloudinary:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>http://res.cloudinary.com/demo/image/upload/w_400,h_400,c_crop,g_face,r_max/w_200/lady.jpg</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso também é o que você obtém se implementar o <a href="https://github.com/refile/refile"target="_blank" rel="noopener">Refile</a> ou o <a href="https://github.com/janko-m/shrine"target="_blank" rel="noopener">Shrine</a>. Eles adicionam um endpoint Rack à sua aplicação Rails que vai buscar o binário da imagem, executar a transformação de acordo com os parâmetros recebidos na URI, fazer cache da transformação e então enviar o binário.</p>
<h3>A Etapa de Servir os Arquivos<span class="hx:absolute hx:-mt-20" id="a-etapa-de-servir-os-arquivos"></span>
    <a href="#a-etapa-de-servir-os-arquivos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui é fazer sua aplicação Rails servir o blob armazenado. O arquivo pode ser armazenado localmente (ou em um mount NFS remoto) ou na internet a partir de qualquer cloud storage como o AWS S3 (caso em que você simplesmente linka diretamente para o endpoint HTTP deles).</p>
<p>Quando sua aplicação Rails serve um arquivo local, pode apenas enviar um Header especial para o proxy reverso (NGINX ou Apache — <code>X-SendFile</code> ou <code>X-Accel-Redirect</code>, que é a diferença entre <code>send_file</code> e <code>send_data</code> no <code>ActionController::DataStreaming</code>, por sinal) e eles vão servir o arquivo diretamente, evitando bloquear o MRI durante a transferência.</p>
<p>Se está em cloud storage, é ainda mais fácil porque você simplesmente imprime a URL do arquivo diretamente no template HTML e não há processamento nenhum.</p>
<p>Haverá algum processamento se você fizer o Rails ler o blob e fazer streaming diretamente (às vezes isso é necessário porque você tem acesso restrito aos arquivos e não quer arriscar usar uma URL aleatória para o arquivo).</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O DHH está certo: o Basecamp serve muitos arquivos e o ActiveStorage (assim como o Paperclip e o Carrierwave) funciona bem, desde que você tenha um proxy reverso NGINX adequado na frente e tenha adicionado um CDN para fazer cache dos arquivos.</p>
<p>Se você não quer ter que gerenciar seu próprio storage, use um Cloud Storage (AWS S3, Google Cloud, Azure, etc.). O ActiveStorage ou outras soluções vão receber o arquivo no nível do controller Rails e você DEVE usar o ActiveJob para fazer POST do blob para o serviço de nuvem em background — sem bloquear sua aplicação no processo. Mas o trade-off é que se você adicionar transformação assíncrona em jobs, vai acabar tendo que buscar a imagem original do cloud storage para fazer as transformações.</p>
<p>Você deveria fazer transformações nas suas imagens para enviar a imagem no tamanho ideal de volta aos seus usuários. Novamente, se fizer isso na sua aplicação, considere um worker do ActiveJob ou as soluções Rack de transformação em tempo real com cache disponíveis no Refile ou Shrine. Pelo menos como está agora, o ActiveStorage não fornece uma solução para as transformações de imagem.</p>
<p><a href="http://www.akitaonrails.com/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads"target="_blank" rel="noopener">Minha recomendação</a> dos posts anteriores sobre o assunto continua valendo: se é sua primeira vez ou seu negócio está começando, não se estresse. Use uma solução completa como Cloudinary/Attachinary. Ela vai cuidar de tudo da forma mais otimizada possível.</p>
<p>Mas isso NÃO é uma recomendação definitiva. Se você tem deploys customizados e conhece seus requisitos e restrições, uma solução como ActiveStorage, Carrierwave vanilla, etc. funciona bem. Sempre há trade-offs, e ter muitas partes móveis sempre soma complexidade. A recomendação do Cloudinary é apenas para que você possa começar com o mínimo de partes móveis possível e depois migrar para cenários mais complexos se precisar.</p>
<p>Lidar com todas as combinações possíveis de gerenciamento de upload de arquivos não é tarefa simples. E você provavelmente tem preocupações mais urgentes na sua lógica de negócios do que lidar com arquivos.</p>
<p>Ah, e repetindo o mesmo de sempre: use um CDN, pelo amor de Deus! Independentemente da solução, você sempre deveria <a href="https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn"target="_blank" rel="noopener">adicionar um CDN</a> para servir seus assets (javascripts, stylesheets, imagens, etc.).</p>
]]></content:encoded><category>rails</category><category>backend</category><category>cloud</category></item><item><title>Aplicativo Elixir Phoenix em Deploy num Setup com Balanceamento de Carga no DigitalOcean</title><link>https://akitaonrails.github.io/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean/</guid><pubDate>Fri, 23 Dec 2016 15:07:00 -0200</pubDate><description>&lt;p&gt;Uma das principais vantagens de construir uma aplicação web com Websockets usando Phoenix é como é &amp;ldquo;fácil&amp;rdquo; para o Erlang se conectar num cluster.&lt;/p&gt;
&lt;p&gt;Para começar, Erlang não precisa de múltiplos processos como Ruby (que fica limitado a uma conexão por processo, ou por thread se você usa um servidor com threads como o Puma). Um único processo Erlang vai tomar conta de toda a máquina, se necessário. Internamente ele mantém uma thread real por core da máquina. E cada thread tem seu próprio Scheduler para gerenciar quantos micro-processos você precisar. Você pode ler tudo sobre isso no meu post &lt;a href="http://www.akitaonrails.com/2015/11/25/yocto-services-e-meu-primeiro-mes-com-elixir"target="_blank" rel="noopener"&gt;&amp;ldquo;Yocto Services&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Uma das principais vantagens de construir uma aplicação web com Websockets usando Phoenix é como é &ldquo;fácil&rdquo; para o Erlang se conectar num cluster.</p>
<p>Para começar, Erlang não precisa de múltiplos processos como Ruby (que fica limitado a uma conexão por processo, ou por thread se você usa um servidor com threads como o Puma). Um único processo Erlang vai tomar conta de toda a máquina, se necessário. Internamente ele mantém uma thread real por core da máquina. E cada thread tem seu próprio Scheduler para gerenciar quantos micro-processos você precisar. Você pode ler tudo sobre isso no meu post <a href="http://www.akitaonrails.com/2015/11/25/yocto-services-e-meu-primeiro-mes-com-elixir"target="_blank" rel="noopener">&ldquo;Yocto Services&rdquo;</a>.</p>
<p>Além disso, Erlang tem capacidades nativas de formar um cluster, onde cada instância Erlang age como um Node peer-to-peer, sem precisar de um coordenador centralizado. Você pode ler tudo sobre isso no meu post sobre <a href="http://www.akitaonrails.com/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir"target="_blank" rel="noopener">Nodes</a>. O poder do Erlang está em como é &ldquo;fácil&rdquo; formar sistemas distribuídos confiáveis.</p>
<p>Você pode subir várias instâncias Phoenix e, a partir de uma delas, transmitir mensagens para usuários inscritos em Channels mesmo que seus sockets estejam conectados a instâncias diferentes. É transparente e você não precisa fazer nada especial no seu código. Phoenix, Elixir e Erlang fazem todo o trabalho pesado por você nos bastidores.</p>
<h3>Sem Heroku para você :-(<span class="hx:absolute hx:-mt-20" id="sem-heroku-para-você--"></span>
    <a href="#sem-heroku-para-voc%c3%aa--" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como você quer aproveitar esse recurso de escalabilidade e alta disponibilidade para sistemas distribuídos (no pequeno exemplo de um sistema de chat em tempo real), vai precisar ter mais controle sobre a infraestrutura. Esse requisito descarta a maioria das ofertas de Platform as a Service (PaaS), como o Heroku. O modelo do Heroku gira em torno de processos únicos e voláteis em containers isolados. Esses processos enjaulados (dynos) não têm consciência de outros processos ou da rede interna, então você não consegue subir Dynos e fazer com que formem um cluster porque eles não vão se encontrar.</p>
<p>Se você já sabe configurar coisas no Linux: Postgresql, HAproxy, etc., vá direto para a <a href="#phoenix-setup">seção específica do Phoenix</a>.</p>
<h3>IaaS (DigitalOcean) ao resgate!<span class="hx:absolute hx:-mt-20" id="iaas-digitalocean-ao-resgate"></span>
    <a href="#iaas-digitalocean-ao-resgate" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Você precisa de processos de longa duração em servidores alcançáveis pela rede (seja por rede privada, VPN, ou simplesmente - inseguro! - redes públicas).</p>
<p>Neste exemplo quero mostrar um deploy bem simples usando DigitalOcean (você pode escolher qualquer IaaS, como AWS, Google Cloud, Azure ou o que você se sentir mais confortável).</p>
<p>Criei 4 droplets (todos usando o menor tamanho de 512Mb de RAM):</p>
<ul>
<li>1 banco de dados Postgresql (ponto único de falha: não é o foco deste artigo construir um setup de banco de dados altamente disponível e replicado);</li>
<li>1 servidor HAProxy (ponto único de falha: novamente, não é o foco criar um esquema de balanceamento de carga altamente disponível);</li>
<li>2 servidores Phoenix - um no datacenter de NYC e outro no de Londres, para demonstrar como é fácil para o Erlang formar clusters mesmo com máquinas geograficamente separadas.</li>
</ul>
<h3>Configuração básica do Ubuntu 16.04<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-ubuntu-1604"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-ubuntu-1604" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivos: configurar o locale, garantir que as atualizações automáticas estejam ativas, atualizar pacotes, instalar e configurar Elixir e Node.</li>
<li>Você também deveria: mudar o SSH para outra porta e instalar [fail2ban](<a href="https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-14-04"target="_blank" rel="noopener">https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-14-04</a>, desabilitar login por senha.</li>
</ul>
<p>Recomendo ler meu post sobre <a href="http://www.akitaonrails.com/2016/09/21/ubuntu-16-04-lts-xenial-no-vagrant-com-vmware-fusion"target="_blank" rel="noopener">configurar o Ubuntu 16.04</a>. Resumindo, comece configurando o UTF-8 corretamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo locale-gen &#34;en_US.UTF-8&#34;
sudo dpkg-reconfigure locales
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
echo &#39;LC_ALL=en_US.UTF-8&#39; | sudo tee -a /etc/environment
echo &#39;LANG=en_US.UTF-8&#39; | sudo tee -a /etc/environment</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Adicione um usuário adequado ao grupo sudo e, a partir de agora, não use mais o usuário root. Vou criar um usuário chamado <code>pusher</code> e vou explicar em outro post o porquê. Você deve criar um nome de usuário que faça sentido para a sua aplicação.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>adduser pusher
usermod -aG sudo pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora saia e entre novamente com esse usuário. <code>ssh pusher@server-ip-address</code>. Se você estiver num Mac, copie a chave pública do seu certificado SSH assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-copy-id -i ~/.ssh/id_ed25519.pub pusher@server-ip-address</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso cria o <code>.ssh/authorized_keys</code> se não existir, define as permissões corretas e adiciona sua chave pública. Você pode fazer isso manualmente também, claro.</p>
<p>Os droplets do DigitalOcean não vêm com swap file e eu recomendaria adicionar um, especialmente se você quiser começar com as máquinas menores com menos de 1GB de RAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo cp /etc/fstab /etc/fstab.bak
echo &#39;/swapfile none swap sw 0 0&#39; | sudo tee -a /etc/fstab
sudo sysctl vm.swappiness=10
sudo sysctl vm.vfs_cache_pressure=50
echo &#39;vm.swappiness=10&#39; | sudo tee -a /etc/sysctl.conf
echo &#39;vm.vfs_cache_pressure=50&#39; | sudo tee -a /etc/sysctl.conf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Certifique-se de que as atualizações automáticas estejam configuradas. No mínimo você vai querer que as atualizações de segurança sejam instaladas automaticamente quando disponíveis.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt install unattended-upgrades</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, vamos instalar Elixir e Node (Phoenix precisa de Node.js):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb &amp;&amp; sudo dpkg -i erlang-solutions_1.0_all.deb
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential nodejs esl-erlang elixir erlang-eunit erlang-base-hipe
sudo npm install -g brunch
mix local.hex
mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez # opcional, instale se quiser testar phoenix manualmente na sua máquina</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora você tem uma máquina pronta para Elixir. Crie um snapshot de imagem no DigitalOcean, mova para as regiões onde quer criar seus outros droplets e use essa imagem para criar quantos droplets precisar.</p>
<p>Para este exemplo, criei um segundo droplet na região de Londres, um terceiro para postgresql na região NYC1 e um quarto na região NYC3 para o HAProxy.</p>
<p>Vou me referir aos endereços IP públicos deles como <strong>&ldquo;nyc-ip-address&rdquo;</strong>, <strong>&ldquo;lon-ip-address&rdquo;</strong>, <strong>&ldquo;pg-ip-address&rdquo;</strong>, e <strong>&ldquo;ha-ip-address&rdquo;</strong>.</p>
<h3>Configuração básica do PostgreSQL<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-postgresql"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-postgresql" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivo: configuração básica do Postgresql para permitir que os servidores Phoenix se conectem.</li>
<li>A fazer: criar uma role secundária só para conectar ao banco da aplicação e outra superusuário para criar o banco e migrar o schema. Também trancar a máquina e configurar SSH tunnels ou outro método seguro, pelo menos uma rede privada, em vez de permitir conexões TCP na porta 5432 abertas para a Internet.</li>
</ul>
<p>Agora conecte em <code>ssh pusher@pg-ip-address</code> e siga isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt-get install postgresql postgresql-contrib</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crie uma nova role com o mesmo nome do usuário que você adicionou acima (&ldquo;pusher&rdquo; no nosso exemplo):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ sudo -u postgres createuser --interactive

Enter name of role to add: pusher
Shall the new role be a superuser? (y/n) y

$ sudo -u postgres createdb pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Postgresql espera encontrar um banco de dados com o mesmo nome da role e a role deve ter o mesmo nome do usuário Linux. Agora você pode usar <code>psql</code> para definir uma senha para essa nova role:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ sudo -u postgres psql
\password pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Registre uma senha segura, anote-a e vamos em frente.</p>
<p>O Postgresql vem bloqueado para conexões externas. Uma forma de conectar de fora é configurar seus servidores para criar um <a href="https://www.postgresql.org/docs/9.1/static/ssh-tunnels.html"target="_blank" rel="noopener">SSH tunnel</a> ao servidor de banco de dados e manter as conexões TCP externas pela porta 5432 proibidas.</p>
<p>Mas neste exemplo, vamos simplesmente permitir conexões da Internet pública na porta TCP 5432. <strong>Atenção:</strong> isso é MUITO inseguro!</p>
<p>Edite o <code>/etc/postgresql/9.5/main/postgresql.conf</code> e encontre a linha de configuração <code>listen_addresses</code> e permita:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>listen_addresses = &#39;*&#39;    # what IP address(es) to listen on;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso deve vincular o servidor à porta TCP. Agora edite <code>/etc/postgresql/9.5/main/pg_hba.conf</code> e edite o final para ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># IPv4 local connections:
host    all             all             127.0.0.1/32            trust
host    all             all             your-local-machine-ip-address/24        trust
host    all             all             nyc-ip-address/24       trust
host    all             all             lon-ip-address/24       trust
# IPv6 local connections:
host    all             all             ::1/128                 trust</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Salve o arquivo de configuração e reinicie o servidor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo service postgresql restart</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Viu o que fiz aí? Só permiti conexões vindas dos IPs públicos dos servidores Phoenix. Isso não torna o servidor seguro, só um pouco menos vulnerável. Se você está atrás de uma rede DHCP/NAT, só pesquise &ldquo;what&rsquo;s my IP&rdquo; no Google para ver seu IP público - que provavelmente é compartilhado por muitos outros usuários; lembre-se que você está permitindo conexões de um IP inseguro ao seu servidor de banco de dados! Depois de fazer os testes iniciais e criar seu schema, remova a linha <code>your-local-machine-ip-address/24</code> da configuração.</p>
<p>Na sua aplicação Phoenix, edite o arquivo local <code>config/prod.secret.exs</code> para ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Configure your database</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:your_app_name</span><span class="p">,</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Repo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">adapter</span><span class="p">:</span> <span class="no">Ecto</span><span class="o">.</span><span class="n">Adapters</span><span class="o">.</span><span class="n">Postgres</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">username</span><span class="p">:</span> <span class="s2">&#34;pusher&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">password</span><span class="p">:</span> <span class="s2">&#34;your-super-secure-pg-password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">database</span><span class="p">:</span> <span class="s2">&#34;your-app-database-name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">hostname</span><span class="p">:</span> <span class="s2">&#34;pg-ip-address&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">pool_size</span><span class="p">:</span> <span class="mi">20</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Substitua as informações pelo seu servidor e banco de dados, e agora você pode testar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>MIX_ENV=prod iex -S mix phoenix.server</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você ver uma mensagem <code>:econnrefused</code> do postgrex, tem algo errado. Revise sua configuração, reinicie o servidor e tente novamente. Se tudo conectar, rode <code>MIX_ENV=prod mix do ecto.create, ecto.migrate</code> para preparar seu banco de dados.</p>
<p>Por fim, você vai querer fechar o restante do seu servidor com UFW, pelo menos. UFW já vem instalado no Ubuntu 16, então basta fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ufw allow 5432
ufw allow ssh
ufw enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto. E de novo, isso não torna seu servidor seguro, só o torna menos inseguro. Há uma diferença enorme!</p>
<p>E por falar nisso, se você é fã de Docker:</p>
<blockquote>
  <p>NÃO INSTALE UM BANCO DE DADOS DENTRO DE UM CONTAINER DOCKER!</p>

</blockquote>
<p><a href="http://patrobinson.github.io/2016/11/07/thou-shalt-not-run-a-database-inside-a-container/"target="_blank" rel="noopener">Você foi avisado</a>!</p>
<h3>Configuração básica do HAProxy<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-haproxy"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-haproxy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivos: fornecer uma solução simples de balanceamento de carga entre nossos 2 servidores Phoenix.</li>
<li>A fazer: há algo errado com a verificação de sessão ou algo assim, pois às vezes preciso atualizar o browser para não ser mandado de volta ao formulário de login na minha aplicação. Phoenix usa sessões baseadas em cookie, então não acho que esteja perdendo sessões.</li>
</ul>
<p>Agora vamos <code>ssh pusher@ha-ip-address</code>. Essa parte é fácil, basta instalar o HAProxy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt-get install haproxy</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Edite <code>/etc/haproxy/haproxy.cfg</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
listen your-app-name
  bind 0.0.0.0:80
  mode http
  stats enable
  stats uri /haproxy?stats
  stats realm Strictly\ Private
  stats auth admin:some-secure-password-for-admin
  option forwardfor
  option http-server-close
  option httpclose
  balance roundrobin
  cookie SERVERID insert indirect nocache
  server us your_us_server_IP:8080 check cookie us1
  server uk your_uk_server_IP:8080 check cookie uk1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode omitir as linhas de <code>stats</code> se tiver outros meios de monitoramento, caso contrário defina uma senha segura para o usuário <code>admin</code>. Uma parte muito importante é o <code>option http-server-close</code>, conforme explicado <a href="http://blog.silverbucket.net/post/31927044856/3-ways-to-configure-haproxy-for-websockets"target="_blank" rel="noopener">neste outro post</a>, caso contrário você pode ter problemas com Websockets.</p>
<p>Por algum motivo estou tendo problemas com minha aplicação depois de fazer login e ela definir a sessão - às vezes preciso atualizar para ser redirecionado à página correta, ainda não sei o porquê e acredito que seja algo na configuração do HAProxy. Se alguém souber o que é, me avise nos comentários. Por ora, estou recorrendo a Sticky sessions (afinidade de servidor) fazendo o HAProxy escrever um cookie de volta com o servidor.</p>
<p>Agora reinicie o servidor e habilite o UFW também:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo service haproxy restart
sudo ufw allow http
sudo ufw allow https
sudo ufw allow ssh
sudo ufw enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode facilmente adicionar suporte a SSL na sua aplicação configurando o HAProxy (e não os nodes Phoenix). A <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-haproxy-with-let-s-encrypt-on-ubuntu-14-04"target="_blank" rel="noopener">documentação do DigitalOcean</a> é abrangente, é só seguir. No fim, meu <code>haproxy.cfg</code> ficou assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>global
   log /dev/log    local0
   log /dev/log    local1 notice
   chroot /var/lib/haproxy
   stats socket /run/haproxy/admin.sock mode 660 level admin
   stats timeout 30s
   user haproxy
   group haproxy
   daemon
   maxconn 2048
   tune.ssl.default-dh-param 2048

defaults
   log global
   mode http
   option httplog
   option dontlognull
   option redispatch
   option forwardfor
   option http-server-close
   timeout connect 5000
   timeout client  50000
   timeout server  50000

frontend www-http
   bind your_ha_proxy_IP:80
   reqadd X-Forwarded-Proto:\ http
   default_backend www-backend

frontend www-https
   bind your_ha_proxy_IP:443 ssl crt /etc/haproxy/certs/your_domain.pem
   reqadd X-Forwarded-Proto:\ https
   acl letsencrypt-acl path_beg /.well-known/acme-challenge/
   use_backend letsencrypt-backend if letsencrypt-acl
   default_backend www-backend

backend www-backend
   redirect scheme https if !{ ssl_fc }
   # setting session stickiness
   cookie SERVERID insert indirect nocache
   server us your_us_server_IP:8080 check cookie us1
   server uk your_uk_server_IP:8080 check cookie uk1

backend letsencrypt-backend
   server letsencrypt 127.0.0.1:54321</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, vou assumir que você tem um servidor/serviço de DNS em algum lugar onde pode registrar o IP deste servidor HAProxy como um registro A para acessá-lo por um nome completo como &ldquo;your-app-name.mydomain.com&rdquo;.</p>
<p><a name="phoenix-setup"></a></p>
<h3>Configuração básica do Phoenix<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-phoenix"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-phoenix" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivo: configurar a app Phoenix para ser deployável. Configurar os servidores com os arquivos de configuração necessários.</li>
<li>A fazer: descobrir uma forma de reduzir os tempos absurdamente longos de deploy.</li>
</ul>
<p>Finalmente, temos quase tudo no lugar.</p>
<p>Vou assumir que você já tem uma aplicação Phoenix funcionando, caso contrário crie uma a partir de qualquer um dos muitos tutoriais disponíveis.</p>
<p>Montei estas informações a partir de <a href="https://medium.com/@diamondgfx/deploying-phoenix-applications-with-exrm-97a3867ebd04#.7qyuplncx"target="_blank" rel="noopener">posts</a> como <a href="http://engineering.pivotal.io/post/how-to-set-up-an-elixir-cluster-on-amazon-ec2/"target="_blank" rel="noopener">este muito útil</a> do Pivotal sobre deploy baseado em AWS. Em resumo, você precisa fazer uma série de alterações na sua configuração.</p>
<p>Quando você está desenvolvendo a aplicação, vai notar que toda vez que roda, ela compila incrementalmente o que mudou. Os binários ficam em <code>_build/dev</code> ou <code>_build/test</code> na forma de binários <code>.beam</code> (similar ao que <code>.class</code> são para Java).</p>
<p>Diferente de Ruby, Python ou PHP, você não está fazendo deploy de código-fonte para servidores de produção. É mais parecido com Java, onde você precisa ter tudo compilado em binários e empacotado no que chamamos de <strong>release</strong>. É como um &ldquo;.war&rdquo; ou &ldquo;.ear&rdquo; se você vem do mundo Java.</p>
<p>Para criar esse pacote as pessoas costumavam usar &ldquo;exrm&rdquo;, mas está sendo substituído por <a href="https://github.com/bitwalker/distillery"target="_blank" rel="noopener">&ldquo;distillery&rdquo;</a>, então vamos usá-lo.</p>
<p>Aí, se você vem do Ruby está familiarizado com Capistrano. Ou se vem do Python, conhece o clone do Capistrano, Fabric. Elixir tem uma ferramenta similar (bem mais simples por enquanto), chamada <a href="https://github.com/boldpoker/edeliver"target="_blank" rel="noopener">&ldquo;edeliver&rdquo;</a>. É basicamente uma ferramenta de automação SSH.</p>
<p>Você adiciona eles ao <code>mix.exs</code> como qualquer outra dependência:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="ss">mod</span><span class="p">:</span> <span class="p">{</span><span class="no">ExPusherLite</span><span class="p">,</span> <span class="o">[]</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="ss">applications</span><span class="p">:</span> <span class="o">[...</span><span class="p">,</span> <span class="ss">:edeliver</span><span class="o">]]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="ss">:edeliver</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.4.0&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="ss">:distillery</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0&#34;</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Do post do Pivotal, o detalhe importante para não esquecer é editar esta parte no arquivo <code>config/prod.exs</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ss">http</span><span class="p">:</span> <span class="o">[</span><span class="ss">port</span><span class="p">:</span> <span class="mi">8080</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="ss">url</span><span class="p">:</span> <span class="o">[</span><span class="ss">host</span><span class="p">:</span> <span class="s2">&#34;your-app-name.yourdomain.com&#34;</span><span class="p">,</span> <span class="ss">port</span><span class="p">:</span> <span class="mi">80</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:phoenix</span><span class="p">,</span> <span class="ss">:serve_endpoints</span><span class="p">,</span> <span class="kp">true</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você PRECISA deixar fixo o PORT padrão do servidor web Phoenix e o domínio permitido (lembra do nome de domínio que você associou ao seu servidor HAProxy acima? Esse mesmo). E PRECISA descomentar a linha <code>:serve_endpoints, true</code>!</p>
<p>Para o edeliver funcionar, você precisa criar um arquivo <code>.deliver/config</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>USING_DISTILLERY=true

# change this to your app name:
APP=&#34;your-app-name&#34;

# change this to your own servers IP and add as many as you want
US=&#34;nyc-ip-address&#34;
UK=&#34;lon-ip-address&#34;

# the user you created in your Ubuntu machines above
USER=&#34;pusher&#34;

# which server do you want to build the first release?
BUILD_HOST=$US
BUILD_USER=$USER
BUILD_AT=&#34;/tmp/edeliver/$APP/builds&#34;

# list the production servers declared above:
PRODUCTION_HOSTS=&#34;$US $UK&#34;
PRODUCTION_USER=$USER
DELIVER_TO=&#34;/home/$USER&#34;

# do not change here

LINK_VM_ARGS=&#34;/home/$USER/vm.args&#34;

# For *Phoenix* projects, symlink prod.secret.exs to our tmp source
pre_erlang_get_and_update_deps() {
  local _prod_secret_path=&#34;/home/$USER/prod.secret.exs&#34;
  if [ &#34;$TARGET_MIX_ENV&#34; = &#34;prod&#34; ]; then
    __sync_remote &#34;
      ln -sfn &#39;$_prod_secret_path&#39; &#39;$BUILD_AT/config/prod.secret.exs&#39;

      cd &#39;$BUILD_AT&#39;

      mkdir -p priv/static

      mix deps.get

      npm install

      brunch build --production

      APP=&#39;$APP&#39; MIX_ENV=&#39;$TARGET_MIX_ENV&#39; $MIX_CMD phoenix.digest $SILENCE
    &#34;
  fi
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembra de todas as informações que fomos juntando desde o início desta longa receita? São essas opções que você PRECISA mudar para as suas. Basta seguir os comentários no conteúdo do arquivo acima e adicionar ao seu repositório git. Aliás, seu projeto está num repositório GIT adequado, né??</p>
<p>Se você gosta de usar chaves SSH privadas protegidas por passphrase, vai ser uma dor de cabeça enorme para fazer deploy porque, para cada comando, o edeliver vai emitir um comando SSH que vai ficar pedindo sua passphrase, umas dez vezes ao longo de todo o processo. Você foi avisado! Se ainda assim não se importar, e estiver num Mac, vai ter um problema extra porque o Terminal não consegue criar um prompt para você digitar sua passphrase. Você precisa criar um <a href="https://github.com/markcarver/mac-ssh-askpass"target="_blank" rel="noopener">script</a> <code>/usr/local/bin/ssh-askpass</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#!/bin/bash
# Script: ssh-askpass
# Author: Mark Carver
# Created: 2011-09-14
# Licensed under GPL 3.0

# A ssh-askpass command for Mac OS X
# Based from author: Joseph Mocker, Sun Microsystems
# http://blogs.oracle.com/mock/entry/and_now_chicken_of_the

# To use this script:
#   Install this script running INSTALL as root
#
# If you plan on manually installing this script, please note that you will have
# to set the following variable for SSH to recognize where the script is located:
#   export SSH_ASKPASS=&#34;/path/to/ssh-askpass&#34;

TITLE=&#34;${SSH_ASKPASS_TITLE:-SSH}&#34;;
TEXT=&#34;$(whoami)&#39;s password:&#34;;
IFS=$(printf &#34;\n&#34;);
CODE=(&#34;on GetCurrentApp()&#34;);
CODE=(${CODE[*]} &#34;tell application \&#34;System Events\&#34; to get short name of first process whose frontmost is true&#34;);
CODE=(${CODE[*]} &#34;end GetCurrentApp&#34;);
CODE=(${CODE[*]} &#34;tell application GetCurrentApp()&#34;);
CODE=(${CODE[*]} &#34;activate&#34;);
CODE=(${CODE[*]} &#34;display dialog \&#34;${@:-$TEXT}\&#34; default answer \&#34;\&#34; with title \&#34;${TITLE}\&#34; with icon caution with hidden answer&#34;);
CODE=(${CODE[*]} &#34;text returned of result&#34;);
CODE=(${CODE[*]} &#34;end tell&#34;);
SCRIPT=&#34;/usr/bin/osascript&#34;
for LINE in ${CODE[*]}; do
      SCRIPT=&#34;${SCRIPT} -e $(printf &#34;%q&#34; &#34;${LINE}&#34;)&#34;;
done;
eval &#34;${SCRIPT}&#34;;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora faça isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod &#43;x /usr/local/bin/ssh-askpass
sudo ln -s /usr/local/bin/ssh-askpass /usr/X11R6/bin/ssh-askpass</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembre-se, apenas para Macs. E agora toda vez que tentar fazer deploy você vai receber vários prompts gráficos pedindo a passphrase da chave privada SSH. É de enlouquecer! E você precisa ter o <a href="https://www.xquartz.org/"target="_blank" rel="noopener">XQuartz</a> instalado, por sinal.</p>
<p>Agora você precisa criar manualmente 3 arquivos em todos os servidores Phoenix. Comece com <code>your-app-name/vm.args</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-name us@nyc-ip-address
-setcookie @bCd&amp;fG
-kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155
-config /home/pusher/your-app-name.config
-smp auto</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>/home/pusher/your-app-name</code> é o diretório onde a release vai ser descomprimida depois que o edeliver fizer o deploy do tarball.</p>
<p>Você precisa criar este arquivo em todas as máquinas Phoenix, mudando o bit <code>-name</code> pelo mesmo nome que declarou no arquivo <code>.deliver/config</code>. O <code>-setcookie</code> pode ser qualquer nome, desde que seja o mesmo em todos os servidores.</p>
<p>Viu o <code>-config /home/pusher/your-app-name.config</code>? Crie esse arquivo com o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[{kernel,
  [
    {sync_nodes_optional, [&#39;uk@lon-ip-address&#39;]},
    {sync_nodes_timeout, 30000}
  ]}
].</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é código-fonte Erlang. Na máquina de NYC você deve declarar o nome de Londres, e vice-versa. Se você tiver várias máquinas, todas elas exceto a que você está agora. Entendeu?</p>
<p>Por fim, para a própria app Phoenix, você sempre tem um <code>config/prod.secret.exs</code> que nunca deve ser adicionado com <code>git add</code> ao repositório, lembra? É aqui que você coloca as informações do servidor Postgresql e a chave secreta aleatória para assinar os cookies de sessão:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>use Mix.Config

config :your_app_name, YourAppName.Endpoint,
  secret_key_base: &#34;...&#34;

# Configure your database
config :your_app_name, YourAppName.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: &#34;pusher&#34;,
  password: &#34;your-super-secure-pg-password&#34;,
  database: &#34;your-app-database-name&#34;,
  hostname: &#34;pg-ip-address&#34;,
  pool_size: 20

# if you have Guardian, for example:
config :guardian, Guardian,
  secret_key: &#34;...&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como você cria uma nova chave secreta aleatória? Na sua máquina de desenvolvimento, basta rodar: <code>mix phoenix.gen.secret</code> e copiar a string gerada para o arquivo acima.</p>
<p>Então agora você deve ter esses 3 arquivos em cada servidor Phoenix, na pasta home <code>/home/pusher</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~/your-app-name/vm.args
~/prod.secret.exs
~/your-app-name.config</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Conforme a <a href="https://hexdocs.pm/distillery/runtime-configuration.html#vm-args"target="_blank" rel="noopener">documentação do distillery</a>, você precisa definir uma variável de ambiente para informar a localização do arquivo <code>vm.args</code>, e isso é <strong>SUPER</strong> importante - caso contrário ele vai gerar um padrão que não define o nome e cookie corretos, então os nodes não vão se encontrar depois de subir.</p>
<p>Usando <code>sudo</code>, edite o <code>/etc/environment</code> e adicione a linha:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RELEASE_CONFIG_DIR=/home/pusher/your-app-name
VMARGS_PATH=/home/pusher/your-app-name/vm.args</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Faça isso em todos os servidores phoenix.</p>
<p>No seu diretório de desenvolvimento local você ainda precisa rodar isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix release.init</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai gerar um arquivo padrão <code>rel/config.exs</code> que você precisa adicionar ao repositório git com as seguintes alterações no final:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">environment</span> <span class="ss">:prod</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">plugin</span> <span class="no">Releases</span><span class="o">.</span><span class="n">Plugin</span><span class="o">.</span><span class="n">LinkConfig</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">release</span> <span class="ss">:your_app_name</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">set</span> <span class="ss">version</span><span class="p">:</span> <span class="n">current_version</span><span class="p">(</span><span class="ss">:your_app_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">set</span> <span class="ss">applications</span><span class="p">:</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="ss">your_app_name</span><span class="p">:</span> <span class="ss">:permanent</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O plugin deve habilitar a release a encontrar o arquivo <code>vm.args</code> local no servidor (isso é super importante, caso contrário os comandos remotos como start, stop, ping, etc. não vão funcionar e os nodes não vão carregar as informações corretas para subir e formar um cluster).</p>
<p>Finalmente, tudo pronto, você pode executar este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ mix edeliver build release production --skip-mix-clean --verbose</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se terminar sem erros você vai ter uma mensagem parecida com esta no final:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
==&gt; Release successfully built!
    You can run it in one of the following ways:
      Interactive: _build/prod/rel/your-app-name/bin/your-app-name console
      Foreground: _build/prod/rel/your-app-name/bin/your-app-name foreground
      Daemon: _build/prod/rel/your-app-name/bin/your-app-name start
--&gt; Copying release 0.0.1 to local release store
--&gt; Copying your-app-name.tar.gz to release store

RELEASE BUILD OF YOUR-APP-NAME WAS SUCCESSFUL!</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, isso vai demorar um tempo absurdamente longo para fazer o deploy. Isso porque vai clonar o código-fonte da sua app com git, buscar todas as dependências Elixir (toda vez!), compilar tudo, depois rodar o lentíssimo <code>npm install</code> (toda vez!), processar os assets com brunch, criar a tal &ldquo;release&rdquo;, comprimir em tar e gzip, baixar e enviar via SCP para as outras máquinas configuradas.</p>
<p>No arquivo <code>.deliver/config</code> você definiu a opção <code>BUILD_HOST</code>. É a máquina onde todo esse processo acontece, então você vai querer que pelo menos essa máquina seja mais robusta que as outras. Como estou usando droplets pequenos de 512Mb, o processo leva uma eternidade.</p>
<p>O último comando vai baixar o tarball gerado. Ele precisa fazer isso para garantir que os <a href="http://erlang.org/doc/tutorial/nif.html"target="_blank" rel="noopener">NIFs</a> estejam compilados no ambiente nativo onde vai rodar, porque se você usa Mac, binários de Mac não vão rodar no Linux.</p>
<p>Agora precisamos fazer o upload e descompressão desse tarball em cada servidor com o seguinte comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix edeliver deploy release to production</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quanto mais lenta sua rede, mais tempo vai levar, já que está enviando um tarfile grande pela internet pública, então certifique-se de estar numa conexão rápida. Quando terminar, podemos reiniciar os servidores (se você já tinha instâncias rodando):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix edeliver stop production
mix edeliver start production</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você fizer tudo certo, o processo do edeliver termina sem erros e deixa um daemon rodando no seu servidor, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/home/pusher/your-app-name/erts-8.2/bin/beam -- -root /home/pusher/your-app-name -progname home/pusher/your-app-name/releases/0.0.1/your-app-name.sh -- -home /home/pusher -- -boot /home/pusher/your-app-name/releases/0.0.1/your-app-name -config /home/pusher/your-app-name/running-config/sys.config -boot_var ERTS_LIB_DIR /home/pusher/your-app-name/erts-8.2/../lib -pa /home/pusher/your-app-name/lib/your-app-name-0.0.1/consolidated -name us@nyc-ip-address -setcookie ex-push&amp;r-l!te -kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155 -config /home/pusher/your-app-name.config -mode embedded -user Elixir.IEx.CLI -extra --no-halt &#43;iex -- console</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ainda vai demorar bastante, mas deve ser mais fácil. Então aqui vai uma dica pro para usuários Linux. Siga <a href="https://gist.github.com/mattweldon/2e8ecb953216438ad168"target="_blank" rel="noopener">este Gist</a> para mais detalhes; você precisa emular o que é executado na segunda metade do arquivo <code>.deliver/config</code>.</p>
<p>Note também que rodei as migrations manualmente, mas você pode fazer isso usando <code>mix edeliver migrate</code>.</p>
<p>Leia a documentação deles para mais comandos e configurações.</p>
<p>E não esqueça de habilitar o UFW:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo ufw allow ssh
sudo ufw allow 8080
sudo ufw allow 4369
sudo ufw allow proto tcp from any to any port 9100:9155
sudo ufw default allow outgoing
sudo ufw enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Depurando bugs em produção<span class="hx:absolute hx:-mt-20" id="depurando-bugs-em-produção"></span>
    <a href="#depurando-bugs-em-produ%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Logo depois que fiz o deploy, obviamente falhou. E o problema é que os arquivos <code>/home/pusher/your-app-name/log/erlang.log</code> (eles são rotacionados automaticamente, então você pode encontrar vários arquivos terminando em número) não vão mostrar muita coisa.</p>
<p>O que eu recomendo é alterar o arquivo <code>config/prod.exs</code> APENAS na sua máquina de desenvolvimento e mudar o nível de log para <code>config :logger, level: :debug</code>, usar o mesmo <code>prod.secret.exs</code> que você editou nos servidores e rodar localmente com <code>MIX_ENV=prod iex -S mix phoenix.server</code>.</p>
<p>Por exemplo, em modo de desenvolvimento eu tinha um código no controller que verificava a existência de um parâmetro de query string opcional assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="n">params</span><span class="o">[</span><span class="s2">&#34;some_parameter&#34;</span><span class="o">]</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Funcionava bem em desenvolvimento mas travava em produção, então tive que mudar para:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Map</span><span class="o">.</span><span class="n">has_key?</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="s2">&#34;some_parameter&#34;</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Outro problema foi que Guardian funcionava normalmente em desenvolvimento, mas em produção precisei declarar sua aplicação no <code>mix.exs</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="ss">mod</span><span class="p">:</span> <span class="p">{</span><span class="no">ExPusherLite</span><span class="p">,</span> <span class="o">[]</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="ss">applications</span><span class="p">:</span> <span class="o">[...</span><span class="p">,</span> <span class="ss">:guardian</span><span class="p">,</span> <span class="ss">:edeliver</span><span class="o">]]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estava recebendo erros <code>:econnrefused</code> porque esqueci de rodar <code>MIX_ENV=prod mix do ecto.create, ecto.migrate</code> como instruí acima. Depois de resolver essas questões, minha aplicação estava funcionando em <code>http://your-app-name.yourdomain.com</code>, o HAProxy estava encaminhando corretamente para a porta 8080 nos servidores e tudo roda bem, incluindo as conexões WebSocket.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como mencionei acima, esse tipo de procedimento me faz sentir falta de uma solução de deploy fácil como o Heroku.</p>
<p>O único problema que estou enfrentando agora é que quando faço login pela página de sign in do Coherence, não sou redirecionado para o URI correto que estava tentando acessar (&quot;/admin&quot; no meu caso) - às vezes recarregar depois do login funciona, às vezes não. Às vezes estou dentro de uma página &ldquo;/admin&rdquo; mas quando clico em um dos links ele me manda de volta para a página de login mesmo estando logado. Não sei se é um bug no Coherence, ExAdmin, no próprio Phoenix ou uma configuração errada do HAProxy. Vou atualizar este post se descobrir.</p>
<p>O Edeliver também leva um tempo obsceno para fazer deploy. Até esperar os sprockets processarem num <code>git push heroku master</code> parece muito mais rápido em comparação. E isso para uma app Phoenix bem enxuta. Ter que buscar tudo (porque o Hex não mantém um cache global local, todas as dependências são vendorizadas estaticamente no diretório do projeto) e ter que rodar o lentíssimo npm não ajuda em nada.</p>
<p>Ainda preciso pesquisar se há opções mais rápidas, mas por ora o que tenho &ldquo;funciona&rdquo;.</p>
<p>E mais importante: agora tenho um cluster escalável para WebSockets bidirecionais em tempo real, que é a principal razão pela qual alguém pode querer usar Phoenix.</p>
<p>Se você quer construir um site &ldquo;normal&rdquo;, mantenha simples e faça em Rails, Django, Express ou qual for seu framework web preferido. Se você quer comunicações em tempo real de forma fácil, talvez eu tenha uma solução melhor. Fique de olho no blog para novidades que vêm por aí! ;-)</p>
]]></content:encoded><category>elixir-e-erlang</category><category>devops</category><category>cloud</category><category>sistemas-distribuidos</category></item><item><title>Atualizando Meus Posts Antigos Sobre Uploads</title><link>https://akitaonrails.github.io/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads/</guid><pubDate>Thu, 28 Jul 2016 11:56:00 -0300</pubDate><description>&lt;p&gt;Felizmente as coisas costumam mudar para melhor, e práticas que &amp;ldquo;funcionavam ok&amp;rdquo; no passado podem estar obsoletas hoje.&lt;/p&gt;
&lt;p&gt;Isso acontece com posts antigos de blog, incluindo os meus, já que venho postando há vários anos.&lt;/p&gt;
&lt;p&gt;Neste post vou corrigir o que escrevi antes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2014/12/18/small-bites-direct-upload-para-s3-a-solucao-definitiva"target="_blank" rel="noopener"&gt;Direct Upload para S3: a Solução Definitiva!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2014/03/26/heroku-tips-s3-direct-upload-carrierwave-sidekiq"target="_blank" rel="noopener"&gt;S3 Direct Upload + Carrierwave + Sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2010/06/23/akita-responde-upload-de-arquivos"target="_blank" rel="noopener"&gt;Upload de Arquivos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener"&gt;Enciclopédia do Heroku&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eles ainda são bons posts para entender a mecânica do que vou propor aqui, então vale a pena ler se você tem interesse em como as coisas funcionam por baixo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Felizmente as coisas costumam mudar para melhor, e práticas que &ldquo;funcionavam ok&rdquo; no passado podem estar obsoletas hoje.</p>
<p>Isso acontece com posts antigos de blog, incluindo os meus, já que venho postando há vários anos.</p>
<p>Neste post vou corrigir o que escrevi antes:</p>
<ul>
<li><a href="http://www.akitaonrails.com/2014/12/18/small-bites-direct-upload-para-s3-a-solucao-definitiva"target="_blank" rel="noopener">Direct Upload para S3: a Solução Definitiva!</a></li>
<li><a href="http://www.akitaonrails.com/2014/03/26/heroku-tips-s3-direct-upload-carrierwave-sidekiq"target="_blank" rel="noopener">S3 Direct Upload + Carrierwave + Sidekiq</a></li>
<li><a href="http://www.akitaonrails.com/2010/06/23/akita-responde-upload-de-arquivos"target="_blank" rel="noopener">Upload de Arquivos</a></li>
<li><a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener">Enciclopédia do Heroku</a></li>
</ul>
<p>Eles ainda são bons posts para entender a mecânica do que vou propor aqui, então vale a pena ler se você tem interesse em como as coisas funcionam por baixo.</p>
<p><strong>TL;DR</strong>: use Cloudinary (com Attachinary) e instale um CDN (Cloudfront) agora mesmo, porque é trivial demais de fazer.</p>
<blockquote>
  <p>Upload de arquivos é uma tarefa que merece atenção séria. É um daqueles assuntos onde você não pode achar que &ldquo;funciona&rdquo; só porque funcionou na sua máquina de desenvolvimento, com meia dúzia de uploads triviais. O comportamento em produção muda drasticamente e pode derrubar seu projeto inteiro, dependendo do quanto ele depende de conteúdo gerado por usuário.</p>

</blockquote>
<p>São muitas peças móveis para entender. Mas nas soluções mais básicas você simplesmente cria um form HTML multipart com um campo de arquivo e submete direto para algum endpoint do controller. O controller recebe o blob binário e você salva em algum diretório local no servidor.</p>
<p>Essa é a <strong>pior solução possível</strong> e, infelizmente, é a que você mais encontra pela web.</p>
<p>Existem vários problemas para lidar com essa implementação ingênua:</p>
<ul>
<li>Estamos na era das câmeras de 12+ megapixels. Esses arquivos têm vários megabytes. Cada upload demora bastante, principalmente se o usuário está tentando subir o arquivo por uma rede sem fio lenta ou instável.</li>
</ul>
<p>Quando o navegador do usuário se conecta à aplicação web, ele segura aquela conexão e bloqueia o processo inteiro durante todo o ciclo de request-response. Para deixar simples: se você só tem 1 processo no servidor, ele fica incapaz de responder a qualquer outra requisição até o upload terminar.</p>
<p>Felizmente a maioria dos deploys usa NGINX na frente da aplicação web - como deveriam -, então esse efeito é minimizado, porque o NGINX recebe o blob inteiro antes de passar para o processo da aplicação por baixo, o que torna a coisa menos problemática na maioria dos casos.</p>
<p>Mas se você está no Heroku, a camada de router tem um limite de <strong>timeout de 30 segundos</strong>. Se o upload demorar mais que isso (o que é comum com usuários em redes ruins) essa camada vai cortar a conexão antes do upload terminar. Os usuários vão tentar de novo e isso pode fazer suas filas de requisição crescerem muito rápido.</p>
<p>Evite fazer upload de qualquer coisa diretamente para o Heroku. Eu considero isso uma <strong>feature</strong>, porque força aplicações deployadas no Heroku a usarem <strong>boas práticas</strong>, como vou explicar abaixo.</p>
<ul>
<li>Você também NÃO deve salvar arquivos no filesystem local se quer escalar horizontalmente, porque um servidor não vai conseguir enxergar o que está no outro, a menos que você esteja na AWS EC2 ou outro IaaS, com volumes montados compartilhados, por exemplo. No Heroku você não pode confiar em nada que esteja no filesystem, porque as máquinas são voláteis e sempre que um dyno reinicia ele perde tudo o que não estava lá no momento do deploy.</li>
</ul>
<p>Então você precisa subir o que recebeu para um storage externo, como o AWS S3, e isso também é lento se você recebe muitos uploads. Você pode contar com o NGINX ou com a camada de router do Heroku para receber os arquivos, mas o ciclo de request-response vai ficar lentíssimo se você processar e subir os arquivos para o S3 de forma síncrona.</p>
<p>A próxima opção que vem à cabeça é adicionar Sidekiq ou outro mecanismo de Async Job para fazer esse trabalho pesado. Agora você precisa criar placeholders no front-end para mostrar para os usuários enquanto o background job ainda não terminou de mandar os arquivos para o S3.</p>
<p>A última opção, se você cavar mais fundo, é o <strong>direct upload</strong> do navegador direto para o S3, postando só as URLs para sua aplicação web. Essa é a solução ideal, mas não é fácil de implementar na sua stack.</p>
<ul>
<li>Depois de acertar tudo isso, você ainda comete o erro de jogar as URLs do S3 direto nas tags IMG do seu HTML, o que não é recomendado pela própria Amazon, já que o S3 é recomendado apenas para fins de &ldquo;storage&rdquo;. Essa é a parte fácil de corrigir, mas a maioria dos projetos esquece dela.</li>
</ul>
<h3>Cloudinary ou Carrierwave Direct<span class="hx:absolute hx:-mt-20" id="cloudinary-ou-carrierwave-direct"></span>
    <a href="#cloudinary-ou-carrierwave-direct" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nos meus posts anteriores eu expliquei cada um dos problemas acima em mais detalhe e ofereci algumas soluções (complicadas), como usar o projeto Refile. Naquela época os plugins do Carrierwave para Direct Upload ainda não estavam prontos.</p>
<p>Hoje em dia a solução é absurdamente fácil e é o que eu recomendo: vá direto para o <a href="http://cloudinary.com/documentation/rails_integration"target="_blank" rel="noopener">Cloudinary</a>. É uma solução Software as a Service para direct upload de verdade e processamento dinâmico de imagens. Vale muito explorar a gem <a href="https://github.com/assembler/attachinary"target="_blank" rel="noopener">Attachinary</a> para facilitar o processo.</p>
<p>Se você tem legado em Carrierwave, Paperclip, Shrine, Dragonfly, Refile ou qualquer outro, uma alternativa meio gambiarra - mas razoável - é simplesmente adicionar o Attachinary junto.</p>
<p>Digamos que você tem um uploader antigo <code>user.avatar</code>. Você pode simplesmente criar um novo <code>user.new_avatar</code> com Attachinary/Cloudinary. No upload do form HTML você passa a usar só os helpers do Attachinary. E no HTML onde você mostra a imagem em si, você pode criar um helper que checa por <code>@user.new_avatar?</code> e mostra usando <code>cl_image_tag(@user.avatar.path)</code>, caso contrário mostra as URLs antigas.</p>
<p>Você não precisa migrar tudo de uma vez para o Cloudinary. Basta deixar os assets antigos onde estão e começar a colocar os novos assets na configuração do Cloudinary. Ele tem um Free Tier que comporta até 75 mil imagens e permite tráfego de até 5GB. E com apenas USD 44 por mês você tem 10 vezes isso. Então é bem barato, se sua aplicação leva upload de arquivos a sério.</p>
<p>Se você ainda não quer se comprometer com um serviço externo, e está usando Carrierwave, outra opção que vale considerar é testar o <a href="https://github.com/dwilkie/carrierwave_direct"target="_blank" rel="noopener">CarrierwaveDirect</a> para experimentar direct upload do browser para o S3. Isso pelo menos vai te livrar das complexidades de configurar uploads assíncronos da aplicação web para o S3.</p>
<p>Update: o <a href="https://github.com/janko-m"target="_blank" rel="noopener">Janko Marohnić</a> postou um comentário muito bom que vale citar diretamente:</p>
<blockquote>
  <p>Great writeup, I wholeheartedly agree that we probably don&rsquo;t &ldquo;just want something simple&rdquo;, especially concerning synchronous uploads and CDNs.</p>

</blockquote>
<blockquote>
  <p>Shrine actually has a <a href="https://github.com/janko-m/shrine-cloudinary"target="_blank" rel="noopener">Cloudinary integration</a>, and it&rsquo;s pretty advanced too; it supports direct uploads, setting upload options, storing Cloudinary metadata (hello <a href="http://cloudinary.com/blog/introducing_intelligent_responsive_image_breakpoints_solutions"target="_blank" rel="noopener">responsive breakpoints</a>) etc :). Also, the Cloudinary gem ships with a CarrierWave integration. I think it&rsquo;s beneficial to use Shrine or CarrierWave with Cloudinary, because then you get to keep your file attachments library, and just change the underlying storage service.</p>

</blockquote>
<blockquote>
  <p>As for CarrierwaveDirect, I don&rsquo;t think it&rsquo;s actually very useful, because backgrounding you need to do all by yourself anyway, so basically all it gives you is the ability to generate a direct-upload form to S3. But generating request parameters and URL to S3 is something that&rsquo;s already built in into the official <a href="https://github.com/aws/aws-sdk-ruby"target="_blank" rel="noopener">aws-sdk</a> gem, CarrierwaveDirect just seems to be reimplementing that logic. And the advantage of generating this through aws-sdk is that it&rsquo;s not HTML-specific, so you could setup an endpoint which returns this information as JSON, and now you have your multiple file uploads directly to S3 :)</p>

</blockquote>
<p>Como disclaimer: eu mesmo não testei o CarrierwaveDirect e lembro que ele estava bem quebrado uns 2 anos atrás. E ouvi coisas boas sobre o Shrine ao longo do caminho, mas nunca fiz um teste decente. Recomendo experimentar se você quer migrar para longe do Carrierwave.</p>
<h3>Não sirva assets do S3, use um CDN, qualquer CDN<span class="hx:absolute hx:-mt-20" id="não-sirva-assets-do-s3-use-um-cdn-qualquer-cdn"></span>
    <a href="#n%c3%a3o-sirva-assets-do-s3-use-um-cdn-qualquer-cdn" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você vai continuar fazendo upload para o S3, pelo menos evite servir esses arquivos a partir do S3. No meu post da &ldquo;Enciclopédia do Heroku&rdquo; eu recomendava usar o &ldquo;S3 Assets Sync&rdquo;. NÃO FAÇA ISSO: use um CDN no lugar, é mais rápido, mais fácil e é a solução correta.</p>
<p>A primeira coisa a fazer é se cadastrar no serviço de CDN da AWS, o Cloudfront. Use sempre um CDN para todos os seus assets, não apenas os <a href="http://www.algonauti.com/posts/speed-up-ugc-with-cloudfront"target="_blank" rel="noopener">internos da sua aplicação web</a>, mas também todo conteúdo que o usuário sobe. É <a href="https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn"target="_blank" rel="noopener">super simples</a> criar um novo endpoint de CDN apontando para o seu bucket S3.</p>
<p>O Carrierwave por padrão usa o host do bucket S3, mas uma vez que você tem o endpoint do Cloudfront, dá para <a href="https://dzone.com/articles/carrierwave-heroku-cloudfront"target="_blank" rel="noopener">trocar facilmente</a> todas as URLs dos arquivos enviados assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># ./config/initializers/carrierwave.rb</span>
</span></span><span class="line"><span class="cl"><span class="no">CarrierWave</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">fog_credentials</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:provider</span> <span class="o">=&gt;</span> <span class="s2">&#34;AWS&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:aws_access_key_id</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_ACCESS_KEY_ID&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:aws_secret_access_key</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_SECRET_ACCESS_KEY&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">fog_directory</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_BUCKET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># use apenas uma das 2 configurações abaixo</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># config.fog_host = &#34;http://#{ENV[&#39;S3_BUCKET&#39;]}.s3.amazonaws.com&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">fog_host</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_CDN&#39;</span><span class="o">]</span> <span class="c1"># para cloudfront</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você obviamente precisa configurar a variável de ambiente <code>S3_CDN</code> para apontar para o endpoint do Cloudfront específico do bucket S3 que está usando.</p>
<p>A parte importante é: sirva TODOS OS ASSETS de um CDN, não importa o quê. Nunca direto da sua aplicação web, evite servir direto do S3. É quase trivial de implementar e, se você não gosta da AWS por qualquer motivo, dá para escolher Fastly, Cloudflare e vários outros.</p>
<p>Todos funcionam da mesma forma. NÃO existe desculpa para não usar um CDN, não importa o tamanho da sua aplicação.</p>
]]></content:encoded><category>cloud</category><category>desenvolvimento-web</category></item><item><title>[Small Bites] Adicionando um CDN ao seu site (a forma fácil!)</title><link>https://akitaonrails.github.io/2015/08/25/small-bites-adicionando-um-cdn-ao-seu-site-a-forma-facil/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/08/25/small-bites-adicionando-um-cdn-ao-seu-site-a-forma-facil/</guid><pubDate>Tue, 25 Aug 2015 16:37:00 -0300</pubDate><description>&lt;p&gt;Se você desenvolve front-end com Ruby on Rails o excelente &lt;a href="http://guides.rubyonrails.org/asset_pipeline.html"target="_blank" rel="noopener"&gt;Asset Pipeline&lt;/a&gt; faz tudo por você. Praticamente não há configuração que você precisa fazer, nenhum arquivo para scriptar seu próprio pipeline caseiro, nenhuma &lt;a href="http://www.akitaonrails.com/2014/12/02/small-bites-em-defesa-do-asset-pipeline-tudo-que-voce-precisa"target="_blank" rel="noopener"&gt;discussão&lt;/a&gt; sobre como fazer requires.&lt;/p&gt;
&lt;p&gt;Para 80% dos casos (ou mais!), tudo que você precisa é conseguir desenvolver seus stylesheets (Sass!) de forma organizada podendo até usar Javascript &lt;a href="https://github.com/TannerRogalsky/sprockets-es6"target="_blank" rel="noopener"&gt;ES6&lt;/a&gt;. No final o framework vai automaticamente concatenar tudo, minificar, adicionar controles corretos de versão (fingerprint/hash) para que ninguém precise se preocupar em expirar caches. E em desenvolvimento, tudo vai funcionar separado para ficar fácil debugar.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você desenvolve front-end com Ruby on Rails o excelente <a href="http://guides.rubyonrails.org/asset_pipeline.html"target="_blank" rel="noopener">Asset Pipeline</a> faz tudo por você. Praticamente não há configuração que você precisa fazer, nenhum arquivo para scriptar seu próprio pipeline caseiro, nenhuma <a href="http://www.akitaonrails.com/2014/12/02/small-bites-em-defesa-do-asset-pipeline-tudo-que-voce-precisa"target="_blank" rel="noopener">discussão</a> sobre como fazer requires.</p>
<p>Para 80% dos casos (ou mais!), tudo que você precisa é conseguir desenvolver seus stylesheets (Sass!) de forma organizada podendo até usar Javascript <a href="https://github.com/TannerRogalsky/sprockets-es6"target="_blank" rel="noopener">ES6</a>. No final o framework vai automaticamente concatenar tudo, minificar, adicionar controles corretos de versão (fingerprint/hash) para que ninguém precise se preocupar em expirar caches. E em desenvolvimento, tudo vai funcionar separado para ficar fácil debugar.</p>
<p>Em produção, todos os seus arquivos Javascript, por exemplo, serão pré-compilados e no seu layout ele vai renderizar algo assim em produçao:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/assets/application-92d3fd2d9ebe06a3a45e1ee88109c64f.js&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu já escrevi sobre o <a href="http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes"target="_blank" rel="noopener">Asset</a> <a href="http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes-parte-2"target="_blank" rel="noopener">Pipeline</a>, como usar o <a href="http://www.akitaonrails.com/2013/12/13/rails-assets"target="_blank" rel="noopener">Rails Assets</a> para consumir pacotes Bower sem o Bower. Anos atrás uma última coisa que eu fazia era enviar assets pré-compilados para o AWS S3 usando o Asset Sync, mas atualmente essa é a <strong>forma errada</strong> de fazer isso. Em vez disso vamos colocar tudo num CDN de verdade como o AWS CloudFront.</p>
<p>Um CDN como o CloudFront funciona da seguinte forma. Ao terminar a configuração que vou explicar abaixo, o HTML que carrega o Javascript acima vai ser renderizado da seguinte forma na sua aplicação:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;http://d1g6lioiw8beil.cloudfront.net/assets/application-92d3fd2d9ebe06a3a45e1ee88109c64f.js&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou seja, ele vai pedir para esse subdomínio &ldquo;d1g6lioiw8beil&rdquo; no domínio do cloudfront.net. Ao não encontrar ele vai pedir ao domínio que você configurou. No meu caso, ele vai pedir para:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>http://www.akitaonrails.com/assets/application-92d3fd2d9ebe06a3a45e1ee88109c64f.js</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então ele fará o cache esse arquivo e qualquer nova requisição pelo mesmo arquivo vai devolver do cache do CloudFront e sua aplicação não vai mais receber nenhuma requisição por esse arquivo. O mesmo vai acontecer com todos os demais assets do site, incluindo os stylesheets e imagens.</p>
<p>Quando fizer uma atualização que muda os assets, o fingerprint/hash do arquivo vai mudar. Daí o ciclo reinicia: o CloudFront não vai ter, ele pede pra sua aplicação - que precisa servir o novo arquivo apenas uma vez - e uma vez gravado novamente no cache, sua aplicação não recebe mais nenhuma requisição do mesmo arquivo outra vez.</p>
<p>Se você usou corretamente helpers como <tt>image_tag</tt>, <tt>asset_path</tt> e associados, então já tem tudo que precisa. Sua aplicação já pré-compila os assets corretamente e mostra as URLs corretas com os devidos hashes de cada arquivo concatenado e minificado.</p>
<p>Melhor ainda, tecnicamente o CloudFront vai puxar o arquivo do servidor de cache mais próximo, geograficamente, de onde o usuário que pediu sua página estiver, melhorando a performance geral do seu site do ponto de vista do usuário final.</p>
<h2>Como Configurar e Instalar<span class="hx:absolute hx:-mt-20" id="como-configurar-e-instalar"></span>
    <a href="#como-configurar-e-instalar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com sua conta no AWS criado, vá até o console do CloudFront e já pode começar imediatamente criando uma nova distribuição:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/497/AWS_CloudFront_Management_Console.png" alt="CloudFront"  loading="lazy" /></p>
<p>Guarde o domain name aleatório e único que ele vai criar pra você sobre &ldquo;Domain Name&rdquo;, você vai precisar disso depois. Ele é no formato <tt>[id do domínio].cloudfront.net</tt>.</p>
<p>Só tem duas coisas que você precisa configurar. O primeiro é o domínio correto da sua aplicação em produção e um ID qualquer:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/498/Screen_Shot_2015-08-25_at_15.46.44.png" alt="Origin Settings"  loading="lazy" /></p>
<p>A segunda coisa é um único Whitelist Header, adicionando o header &ldquo;Origin&rdquo;:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/499/Screen_Shot_2015-08-25_at_16.10.21.png" alt="Whitelist Header"  loading="lazy" /></p>
<p>Pronto, é só isso que você precisa fazer no CloudFront. Você vai perceber que ele vai ficar com um spinner rodando e você precisa esperar ele terminar. Ele está configurando todos os edges.</p>
<p>Só de fazer isso você já pode testar. Basta pegar uma URL de produção de um asset seu e trocar seu domínio (no meu caso, &ldquo;<a href="https://www.akitaonrails.com"target="_blank" rel="noopener">www.akitaonrails.com</a>&rdquo;) pelo Domain Name com o ID aleatório que falei acima (no meu caso, &ldquo;d1g6lioiw8beil.cloudfront.net&rdquo;). Ele vai fazer o processo que expliquei antes: puxar do seu site o arquivo, fazer o cache, e passar a servir do cache.</p>
<p>Se o teste funcionar, basta adicionar a seguinte linha no arquivo <tt>config/environments/production.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_controller</span><span class="o">.</span><span class="n">asset_host</span> <span class="o">=</span> <span class="s1">&#39;http://d1g6lioiw8beil.cloudfront.net&#39;</span> <span class="c1"># trocar esse domain pelo seu</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Obviamente, colocando o Domain Name da sua distribuição do CloudFront. Faça um novo deployment e pronto! O Heroku tem uma <a href="https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn"target="_blank" rel="noopener">documentação com mais detalhes</a>, este meu post é um resumo simples que qualquer site em Rails pode seguir imediatamente.</p>
<p>Por muito tempo, muitos <a href="http://www.akitaonrails.com/2014/12/02/small-bites-em-defesa-do-asset-pipeline-tudo-que-voce-precisa"target="_blank" rel="noopener">criticaram o Asset Pipeline</a> por causa dos bugs que as primeiras versões tinham, mas até hoje todos estão tentando resolver um problema que o ecossistema Rails já resolveu desde 2009. E adicionar um CDN ao seu site todo com esforço quase zero, é prova da versatilidade que já temos pré-instalado em toda aplicação Rails moderna.</p>
]]></content:encoded><category>rails</category><category>cloud</category><category>performance</category></item><item><title>[Small Bites] Brincando com o Jelastic Cloud da Locaweb</title><link>https://akitaonrails.github.io/2015/04/28/small-bites-brincando-com-o-jelastic-cloud-da-locaweb/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/04/28/small-bites-brincando-com-o-jelastic-cloud-da-locaweb/</guid><pubDate>Tue, 28 Apr 2015 12:00:00 -0300</pubDate><description>&lt;p&gt;Se você participou da &lt;a href="https://www.eventials.com/locaweb/groups/rubyconf-brasil-2014-by-locaweb-2/"target="_blank" rel="noopener"&gt;Rubyconf Brasil em 2014&lt;/a&gt; teve a oportunidade de ver em primeira mão o lançamento do produto Jelastic Cloud, da Locaweb, na época com suporte beta a Ruby.&lt;/p&gt;
&lt;p&gt;Ontem brinquei com o Jelastic com a ajuda do camarada &lt;a href="https://twitter.com/kemelzaidan"target="_blank" rel="noopener"&gt;Kemel Zaidan&lt;/a&gt;, evangelista de tecnologia da Locaweb e colocamos o site do Call for Papers para a Rubyconf Brasil deste ano no ar por lá.&lt;/p&gt;
&lt;p&gt;Como há alguns truques, resolvi fazer um pequeno post para dar uma idéia do que é possível fazer. Diferente de VPS ou similares, o Jelastic é um Platform as a Service (PaaS) que oferece tanto escalabilidade horizontal (mais servidores embaixo de um balanceador de carga) ou também escalabilidade vertical (mais recursos como memória e CPU mais rápido na mesma máquina). Além disso ele pré-configura quase tudo para que você não tenha que lidar com coisas como configurar o NGINX como proxy reverso para balancear carga ou configurar o PostgreSQL.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você participou da <a href="https://www.eventials.com/locaweb/groups/rubyconf-brasil-2014-by-locaweb-2/"target="_blank" rel="noopener">Rubyconf Brasil em 2014</a> teve a oportunidade de ver em primeira mão o lançamento do produto Jelastic Cloud, da Locaweb, na época com suporte beta a Ruby.</p>
<p>Ontem brinquei com o Jelastic com a ajuda do camarada <a href="https://twitter.com/kemelzaidan"target="_blank" rel="noopener">Kemel Zaidan</a>, evangelista de tecnologia da Locaweb e colocamos o site do Call for Papers para a Rubyconf Brasil deste ano no ar por lá.</p>
<p>Como há alguns truques, resolvi fazer um pequeno post para dar uma idéia do que é possível fazer. Diferente de VPS ou similares, o Jelastic é um Platform as a Service (PaaS) que oferece tanto escalabilidade horizontal (mais servidores embaixo de um balanceador de carga) ou também escalabilidade vertical (mais recursos como memória e CPU mais rápido na mesma máquina). Além disso ele pré-configura quase tudo para que você não tenha que lidar com coisas como configurar o NGINX como proxy reverso para balancear carga ou configurar o PostgreSQL.</p>
<p>Criar uma conta trial é simples, você recebe sua senha por email e pode entrar no dashboard. De lá basta configurar a topologia da sua aplicação que te dá opção de escolher a tecnologia (Java, PHP, Ruby, Node.js, Python, .NET). No caso de Ruby você pode escolher direto a opção do <a href="http://www.akitaonrails.com/2014/10/19/o-novo-garoto-da-pesada-para-servidores-ruby-raptor/"target="_blank" rel="noopener">Raptor Beta</a>. Então pode configurar quantos cloudlets quer (pelo que entendi 1 cloudlet é uma unidade de processamento que representa 128Mb e 200Mhz e pode escalar verticalmente até 64 cloudlets ou 8GB e 12.80Ghz) por node. E horizontalmente você pode escolher ter até 18 nodes. Em termos de preço isso pode ir de até <strong>R$15</strong> por mês (lembrando que é pagamento por utilização e não preço fixo) até <strong>R$10.179</strong> na configuração máxima.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/488/Screen_Shot_2015-04-28_at_09.21.04.png" alt="Dashboard"  loading="lazy" /></p>
<p>Uma configuração média que deve servir pra grande maioria das aplicações pequenas/médias de 8 reserved cloudlet de 768Mb e 1.2GHZ, 2 nodes sob balanceamento, com 256MB de Memcached, 1Gb e 1.6Ghz de PostgreSQL (sempre deixe seu banco maior que sua máquina Web) dá em torno de R$ 280 por mês, ou o equivalente a USD 96, que eu achei até competitivo com alternativas internacionais (sim, é <a href="http://meiobit.com/301551/receita-cobranca-impostos-empresas-usam-servicos-datacenters-exterior/"target="_blank" rel="noopener">controverso</a> mas levar impostos em consideração).</p>
<h2>Dicas de Configuração<span class="hx:absolute hx:-mt-20" id="dicas-de-configuração"></span>
    <a href="#dicas-de-configura%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Você pode fazer o deployment diretamente de um repositório Github ou subir de um zip. Depois disso você pode entrar em qualquer das máquinas diretamente via SSH. Esse passo será necessário pois não há outra forma de configurar ambiente (especificamente variáveis de ambiente que é o correto para configurações). Entre via SSH e configure um arquivo <tt>.env</tt> com seus dados de produção usando a gem <a href="http://www.akitaonrails.com/2013/10/19/iniciante-configuracoes-de-ambiente-com-dotenv#.VT9_ZM5Hlp8"target="_blank" rel="noopener"><tt>dotenv-rails</tt></a>.</p>
<p>Uma coisa importante: se usar a configuração normal de banco de dados via <tt>config/database.yml</tt> vai ter problemas no caso do PostgreSQL pois parece que há diferença de versão entre o libpg nos nodes web com o PostgreSQL na máquina separada. Para bypassar isso, configure no arquivo <tt>.env</tt> uma variável chamada <tt>DATABASE_URL</tt> (que por padrão, bypassa o <tt>database.yml</tt> e conecta por TCP em vez de Unix Sockets). O formato dessa variável deve ser assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>DATABASE_URL: &#39;postgres://[usuario]:[senha]@[host]:5432/[nome do banco]&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ao criar o servidor PostgreSQL você também deve receber um email com um link para o PHPPGAdmin que é uma interface web para configurar o banco, você pode entrar por lá e criar um novo usuário (não use o usuário &lsquo;webadmin&rsquo;, obviamente). Outra opção é entrar via SSH direto no servidor de banco e usar comandos normais como <tt>createuser</tt> ou <tt>createdb</tt>. Não esqueça de adicionar sua chave pública de SSH na aba de configurações da aplicação, é simples.</p>
<p>O deployment automaticamente via colocar os arquivos da sua aplicação no lugar correto e já vai executar o <tt>bundle install</tt>. Mas depois do deployment você ainda vai precisar entrar via SSH no node NGINX para executar tasks como <tt>rake db:migrate</tt> e <tt>rake assets:precompile</tt>. Se estiver usando Memcache (com Rack::Cache, por exemplo), às vezes talvez precise entrar via SSH para acessar o <tt>rails console</tt> para poder zerar o cache via <tt>Rails.cache.clear</tt>. No meu caso, mesmo depois do deployment dizer que já rodou o Bundler ainda precisei entrar no diretório da aplicação e rodar <tt>bundle install</tt> manualmente. Nada de mais, mas vale lembrar disso.</p>
<p>Obs: O Kemel me avisou depois que publiquei o post que o Jelastic (que parece que usa a estrutura do OpenShift por trás) suporta um arquivo <tt>rake_deploy</tt> justamente para adicionar configurações pós-deployment que podem ser executados automaticamente sem precisar entrar por SSH toda vez. Veja <a href="http://docs.jelastic.com/ruby-post-deploy-configuration"target="_blank" rel="noopener">esta documentação</a>.</p>
<p>Finalmente, se fez tudo certo, você já pode acessar sua aplicação pelo endereço <tt>[nome da app].jelasticlw.com</tt>. Daí você pode configurar a aplicação para aceitar um Custom Domain (domínio que você registrou) e no seu serviço de DNS (seja na própria Locaweb ou serviços como Zerigo, SimpleDNS) configurar o CNAME para apontar para o endereço Jelastic.</p>
<p>Não cheguei a testar mas o Jelastic também oferece opções como adicionar SSL e também Auto Scaling (escalabilidade horizontal), de tal maneira que se sua aplicação ultrapassar certas barreiras de consumo de CPU e/ou de Memória, ele automaticamente pode adicionar e remover nodes (você configura números máximos e mínimos de nodes). Assim você pode ficar despreocupado se seu site tiver picos inesperados de acesso e vai gastar apenas o que realmente precisa.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/489/Screen_Shot_2015-04-28_at_09.48.23.png" alt="Auto Scale"  loading="lazy" /></p>
<p>De pontos negativos, comparado ao que um PaaS de Ruby poderia ser, infelizmente ele não tem o conceito de buildpacks abertos, não tem como configurar um pipeline de deployment que já execute corretamente as Rake Tasks do Assets Precompile, Db:Migrate, etc. Não tem suporte a Procfile para subir o servidor de aplicação correto (recomendo já usar o Raptor direto). A escalabilidade vertical não ajuda tanto porque mesmo aumentando a capacidade da máquina, precisaríamos entrar via SSH para configurar a quantidade de worker processes de um Unicorn, por exemplo. E no meu teste, adicionar o balanceador de carga depois que já tinha um node deu problema (acho que precisa já deixar adicionar logo que cria a aplicação e não adicionar depois). Um último ponto importante: não há uma máquina para workers (background jobs), somente nodes web. Mesmo se precisar rodar cronjobs, ele vai rodar em concorrência com seu processo Web, o que é ruim para o tempo de resposta da aplicação web. Ainda não parei para ver se há outra forma (se alguém souber como, não deixe de colocar nos comentários).</p>
<p>De ponto positivo, a aplicação que coloquei no ar <a href="http://rubyconfbrcfp.com.br"target="_blank" rel="noopener">funcionou</a> (Hurray!), teve alguns caveats que mencionei acima, mas o importante é que no final ele realmente funcionou. Ainda é cedo pra dizer o comportamento (foi meu primeiro teste), mas vamos ver o que teremos no futuro. O Jelastic continua em evolução: a própria adição do Raptor é um sinal disso, o que é muito positivo.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Jelastic não é para completos iniciantes, para isso são planos de hospedagem compartilhada que já vem pré-configurados se você precisa subir um mero Wordpress ou Magento. Ele é para desenvolvedores com alguma experiência e que não deveriam estar responsabilizados pela infraestrutura do cliente. Configurar VPS não é difícil, mas a manutenção pode ser, especialmente se for necessário atualizar os recursos da máquina ou, principalmente, se for necessário configurar escalabilidade horizontal e recursos mais avançados como auto escalabilidade horizontal.</p>
<p>O custo do Jelastic é definitivamente maior que qualquer hospedagem compartilhada, porém é como comparar maçãs e bananas. Hospedagens compartilhadas são para coisas pequenas, de preferência sites completamente estáticos, que exigem quase nenhum processamento. Qualquer coisa maior que um pequeno blog vai sofrer. O único próximo passo que as hospedagens brasileiras ofereciam eram ou um Cloud Server (VPS) ou diretamente hardware bruto.</p>
<p>O Jelastic é o meio do caminho e da forma como está caminhando pode ser um ótimo meio do caminho que vale a pena ser explorado. O primeiro deployment pode ser meio doloroso até aprender o que é possível fazer, mas depois disso tudo fica menos complicado e definitivamente o custo-benefício entre manter um administrador de sistemas experiente full time na sua folha de pagamentos vai compensar algumas horas do seu desenvolvedor aprendendo a configurar a plataforma.</p>
<p>Num mundo onde ainda parece &ldquo;normal&rdquo; fazer deployment via FTP (protip: TOSCO!) e editar arquivos diretamente em produção, um produto como o Jelastic pode começar a ajudar a evangelizar conceitos mais corretos de deployment em produção.</p>
<p>Disclaimer: eu não sou da Locaweb e este post não é um marketing pago ou coisa do tipo. O Jelastic é um produto novo então muitos já devem ter enfrentado dores de cabeça. Se tiver experiências que possam ajudar outros desenvolvedores, não deixem de comentar abaixo.</p>
]]></content:encoded><category>cloud</category><category>rails</category><category>devops</category></item><item><title>[Small Bites] Direct Upload para S3: a Solução Definitiva!</title><link>https://akitaonrails.github.io/2014/12/18/small-bites-direct-upload-para-s3-a-solucao-definitiva/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/12/18/small-bites-direct-upload-para-s3-a-solucao-definitiva/</guid><pubDate>Thu, 18 Dec 2014 14:28:00 -0200</pubDate><description>&lt;p&gt;&lt;strong&gt;Atualização Jul/2016:&lt;/strong&gt; As recomendações mudaram. Leiam este post mas usem o que explico no post mais recente &lt;a href="http://www.akitaonrails.com/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads"target="_blank" rel="noopener"&gt;Updating my Old Posts on Uploads&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Um problema que nos persegue há muito tempo são uploads. À primeira vista é algo simples:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Coloque um form com multipart configura&lt;/li&gt;
&lt;li&gt;Coloque um campo de input tipo &amp;ldquo;file&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Dê POST, o upload vai iniciar com o web server&lt;/li&gt;
&lt;li&gt;Quando o upload terminar o web server passa o arquivo pra aplicação, agora é só salvar no disco&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mas esse é o jeito ruim. Funciona muito bem pra coisas pequenas, mas tenha muitos usuários fazendo uploads de dezenas de megabytes (que é o tamanho de qualquer foto tirada em smartphones) e de repente seu servidor fica de joelhos. Tenha muitos usuários fazendo uploads em conexões toscas (3G) e de repente seu web server fica travado à merce de conexões lentas. E no caso do Heroku, com timeout fixo em 30 segundos, tudo é cancelado depois de um tempo.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>Atualização Jul/2016:</strong> As recomendações mudaram. Leiam este post mas usem o que explico no post mais recente <a href="http://www.akitaonrails.com/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads"target="_blank" rel="noopener">Updating my Old Posts on Uploads</a>.</p>
<p>Um problema que nos persegue há muito tempo são uploads. À primeira vista é algo simples:</p>
<ol>
<li>Coloque um form com multipart configura</li>
<li>Coloque um campo de input tipo &ldquo;file&rdquo;</li>
<li>Dê POST, o upload vai iniciar com o web server</li>
<li>Quando o upload terminar o web server passa o arquivo pra aplicação, agora é só salvar no disco</li>
</ol>
<p>Mas esse é o jeito ruim. Funciona muito bem pra coisas pequenas, mas tenha muitos usuários fazendo uploads de dezenas de megabytes (que é o tamanho de qualquer foto tirada em smartphones) e de repente seu servidor fica de joelhos. Tenha muitos usuários fazendo uploads em conexões toscas (3G) e de repente seu web server fica travado à merce de conexões lentas. E no caso do Heroku, com timeout fixo em 30 segundos, tudo é cancelado depois de um tempo.</p>
<p>Daí tem o problema de onde colocar os arquivos: se colocar em arquivos no disco do servidor, a hora que você precisar colocar um segundo servidor em load balancing, vai ter problemas. Ainda tem gente que acha uma boa idéia colocar binários em campos BLOB num banco de dados (NUNCA FAÇA ISSO!!!). A única solução decente é jogar em buckets do S3 ou outros storages em cloud, mas sua aplicação fica mais lenta ainda: primeiro esperando o upload do usuário e depois fazendo o upload pro cloud. Tudo parece piorar o problema!</p>
<p>Pensando nisso fiz um artigo explicando em detalhes o problema, chamado <a href="http://www.akitaonrails.com/2014/03/26/heroku-tips-s3-direct-upload-carrierwave-sidekiq"target="_blank" rel="noopener">S3 Direct Upload + Carrierwave + Sidekiq</a>. Não é uma forma simples mas funciona. Porém o criador do Carrierwave, <a href="https://github.com/jnicklas"target="_blank" rel="noopener">Jonas Nicklas</a> parece que se cansou desse e outros problemas mais inerentes ao Carrierwave e resolveu começar do zero. Ele criou e lançou recentemente a excelente gem <a href="https://github.com/elabs/refile"target="_blank" rel="noopener">REFILE</a>.</p>
<p>TL;DR: Esqueça Paperclip, Carrierwave ou Dragonfly. Use Refile! Ele tem dezenas de opções como qual storage você quer usar e muito mais, mas neste post vou me limitar ao caso de uso mais comum: Direct Upload para o S3, sem carregar seu servidor/aplicação. Tão fácil que cabe num Small Bites.</p>
<p>Mas repito: leia a documentação e faça provas de conceito, vou reduzir o exemplo da documentação num passo a passo para o cenário de Direct Upload para S3. Assumindo que você tem um model <tt>User</tt> e quer colocar uma imagem de perfil nela. Comece editando a <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s2">&#34;mini_magick&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s2">&#34;refile&#34;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;refile/rails&#34;</span><span class="p">,</span> <span class="s2">&#34;refile/image_processing&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s2">&#34;aws-sdk&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vamos criar a configuração do Refile para o AWS-S3, crie o arquivo <tt>config/initializer/refile.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;refile/backend/s3&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">aws</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="ss">access_key_id</span><span class="p">:</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_ACCESS_KEY&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">secret_access_key</span><span class="p">:</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_SECRET_ACCESS_KEY&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">bucket</span><span class="p">:</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_BUCKET&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">Refile</span><span class="o">.</span><span class="n">cache</span> <span class="o">=</span> <span class="no">Refile</span><span class="o">::</span><span class="no">Backend</span><span class="o">::</span><span class="no">S3</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">prefix</span><span class="p">:</span> <span class="s2">&#34;cache&#34;</span><span class="p">,</span> <span class="o">**</span><span class="n">aws</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Refile</span><span class="o">.</span><span class="n">store</span> <span class="o">=</span> <span class="no">Refile</span><span class="o">::</span><span class="no">Backend</span><span class="o">::</span><span class="no">S3</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">prefix</span><span class="p">:</span> <span class="s2">&#34;store&#34;</span><span class="p">,</span> <span class="o">**</span><span class="n">aws</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como podem ver estou assumindo que você usa corretamente o <a href="http://www.akitaonrails.com/2013/10/19/iniciante-configuracoes-de-ambiente-com-dotenv"target="_blank" rel="noopener">dotenv-rails</a> para colocar as configurações de S3. Assumindo também que você saber configurar um bucket, incluindo habilitar a configuração de Cross-Origin Resource Sharing (CORS) onde você vai colocar algo parecido com isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;CORSConfiguration&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;CORSRule&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;AllowedOrigin&gt;</span>*<span class="nt">&lt;/AllowedOrigin&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;AllowedMethod&gt;</span>GET<span class="nt">&lt;/AllowedMethod&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;AllowedMethod&gt;</span>POST<span class="nt">&lt;/AllowedMethod&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;MaxAgeSeconds&gt;</span>3000<span class="nt">&lt;/MaxAgeSeconds&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;AllowedHeader&gt;</span>Authorization<span class="nt">&lt;/AllowedHeader&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;AllowedHeader&gt;</span>Content-Type<span class="nt">&lt;/AllowedHeader&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/CORSRule&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/CORSConfiguration&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vamos criar o campo na tabela de usuários:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rails generate migration add_profile_image_to_users profile_image_id:string
rake db:migrate</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Adicione o Refile no seu model <tt>config/model/user.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">  <span class="n">attachment</span> <span class="ss">:profile_image</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E edite a view do formulário de edição que, se você criou via scaffold, provavelmente vai ser algo como <tt>app/views/users/_form.html.erb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="err">&lt;</span>%= form_for(@user) do |f| %&gt;
</span></span><span class="line"><span class="cl">  ...
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;field&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.attachment_field :profile_image, direct: true, presigned: true %&gt;
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;actions&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.submit %&gt;
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>% end %&gt;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note a opção de <tt>direct</tt> que indica que será um Direct Upload, ou seja, seu browser vai fazer o upload diretamente para o S3 e trazer a chave de identificação pra montar a URL depois. No caso o Refile primeiro joga numa chave &ldquo;cache/&rdquo; e quando você faz o POST do formulário ele manda o S3 copiar para a pasta &ldquo;store/&rdquo; que é o que seu model vai usar. Dessa forma, se você escolher o arquivo no browser, fizer o upload mas desistir de dar POST, não vai poluir a pasta final.</p>
<p>Como o controller vai receber campos novos, precisamos permitir que esses parâmetros cheguem no model, então vamos editar o controller responsável que, nesse exemplo, é o <tt>app/controllers/users_controller.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UsersController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">	<span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Never trust parameters from the scary internet, only allow the white list through.</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">user_params</span>
</span></span><span class="line"><span class="cl">      <span class="n">params</span><span class="o">.</span><span class="n">require</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span><span class="o">.</span><span class="n">permit</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:email</span><span class="p">,</span> <span class="ss">:profile_image</span><span class="p">,</span> <span class="ss">:profile_image_cache_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No trecho acima deixei de exemplo possíveis campos como &ldquo;name&rdquo; e &ldquo;email&rdquo; mas, claro, configure de acordo com os campos que seu formulário realmente envia, o importante são os parâmetros &ldquo;profile_*&rdquo;. Com isso a aplicação já recebe tudo que precisa, mas como o Direct Upload acontece no browser significa que precisamos de algum Javascript para controlar a chamada Ajax e os eventos associados então vamos adicionar a dependência do Refile editando o arquivo <tt>app/assets/javascripts/application.js</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="c1">//= require jquery
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require jquery_ujs
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require turbolinks
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require refile
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require_tree .
</span></span></span><span class="line"><span class="cl"><span class="p">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é um exemplo então, novamente, edite conforme o que você tem na sua aplicação. A documentação explica como lidar com os eventos como &ldquo;upload:start&rdquo; ou &ldquo;upload:progress&rdquo; para que você tenha a opção de mostrar coisas como uma barra de progresso ou outra notificação ao usuário indicando se ele está fazendo o upload e quando terminar. Para este exemplo vamos fazer algo simples: apenas desabilitar o botão de submit e reabilitar quando o upload terminar. Para isso vamos editar o arquivo <tt>app/assets/javascripts/users.js.coffee</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">on</span> <span class="s2">&#34;upload:start&#34;</span><span class="p">,</span> <span class="s2">&#34;form&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="s2">&#34;input[type=submit]&#34;</span><span class="p">).</span><span class="nx">attr</span> <span class="s2">&#34;disabled&#34;</span><span class="p">,</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">on</span> <span class="s2">&#34;upload:complete&#34;</span><span class="p">,</span> <span class="s2">&#34;form&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="s2">&#34;input[type=submit]&#34;</span><span class="p">).</span><span class="nx">removeAttr</span> <span class="s2">&#34;disabled&#34;</span>  <span class="nx">unless</span> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="s2">&#34;input.uploading&#34;</span><span class="p">).</span><span class="nx">length</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto! Reinicie seu servidor e de agora em diante o upload vai acontecer diretamente do browser para o S3 e quando fizer o POST ele vai guardar a chave de identificação no campo &ldquo;profile_image_id&rdquo; e quando for puxar a imagem ele vai passar por um filtro Rack que vai mostrar algo parecido com isso no seu console:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/attachments/store/fill/300/300/365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8/profile_image&#34; for 192.168.47.2 at 2014-12-18 15:53:14 &#43;0000
Refile: GET /store/fill/300/300/365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8/profile_image
Refile: serving &#34;365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8&#34; from store backend which is of type Refile::Backend::S3
[AWS S3 200 3.345285 0 retries] get_object(:bucket_name=&gt;&#34;testing-bucket-akitaonrails&#34;,:key=&gt;&#34;store/365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8&#34;)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Significa que você pode dar override nisso e colocar coisas como autenticação para acessar certos buckets e assim por diante. Com o tempo você pode querer limpar a pasta de cache e isso você pode configurar diretamente no S3 como <a href="https://github.com/elabs/refile#cache-expiry"target="_blank" rel="noopener">ensina a documentação</a>.</p>
<p>Finalmente, o correto é sempre configurar o Amazon Cloudfront na frente do storage que estiver usando (no nosso caso, o S3) e <a href="http://www.happybearsoftware.com/use-cloudfront-and-the-rails-asset-pipeline-to-speed-up-your-app.html"target="_blank" rel="noopener">este blog post</a> explica como é simples fazer isso. O conceito é basicamente trocar o domínio e passar a URL para o Cloudfront que, se não tiver a imagem em cache vai pedir pra sua aplicação e a partir daí vai usar do seu próprio cache, deixando a sua aplicação e seu storage mais leves. E para que sua aplicação gere tags de imagens apontando pro CDN, apenas configure o Refile assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Refile</span><span class="o">.</span><span class="n">host</span> <span class="o">=</span> <span class="s2">&#34;//your-dist-url.cloudfront.net&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto! A solução definitiva para gerenciar seus assets da <strong>FORMA CORRETA</strong>! Servir assets diretamente da sua aplicação está errado. Fazer sua aplicação fazer o upload para storages está errado. Não usar storages separados da sua aplicação está errado!</p>
]]></content:encoded><category>rails</category><category>cloud</category><category>tutoriais</category></item><item><title>[Heroku Tips] Usando Followers do Heroku Postgresql</title><link>https://akitaonrails.github.io/2014/11/10/heroku-tips-usando-followers-do-heroku-postgresql/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/11/10/heroku-tips-usando-followers-do-heroku-postgresql/</guid><pubDate>Mon, 10 Nov 2014 19:51:00 -0200</pubDate><description>&lt;p&gt;Uma das grandes vantagens de usar o Heroku é poder rapidamente aumentar o tamanho dos dynos para aumentar a capacidade de requisições simultâneas que sua aplicação pode responder. Porém existe um grande problema: você só vai pode aumentar suas dynos até o limite de conexões do seu banco de dados.&lt;/p&gt;
&lt;p&gt;Para isso veja direito os planos de &lt;a href="https://addons.heroku.com/heroku-postgresql"target="_blank" rel="noopener"&gt;Postgresql&lt;/a&gt;. Para aplicações médias, você provavelmente vai querer algo do tamanho do plano Standard 2, com até 400 conexões simultâneas e 3.5Gb de RAM. Na teoria, você poderia tranquilamente subir mais de 400 instâncias de Ruby (web e worker), considerando o pool de conexões. Na prática isso não deve acontecer, e isso depende muito do perfil da sua aplicação.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Uma das grandes vantagens de usar o Heroku é poder rapidamente aumentar o tamanho dos dynos para aumentar a capacidade de requisições simultâneas que sua aplicação pode responder. Porém existe um grande problema: você só vai pode aumentar suas dynos até o limite de conexões do seu banco de dados.</p>
<p>Para isso veja direito os planos de <a href="https://addons.heroku.com/heroku-postgresql"target="_blank" rel="noopener">Postgresql</a>. Para aplicações médias, você provavelmente vai querer algo do tamanho do plano Standard 2, com até 400 conexões simultâneas e 3.5Gb de RAM. Na teoria, você poderia tranquilamente subir mais de 400 instâncias de Ruby (web e worker), considerando o pool de conexões. Na prática isso não deve acontecer, e isso depende muito do perfil da sua aplicação.</p>
<p>Especialmente considerando aplicações que precisam ou fazer queries muito complexas (relatórios, pré-cálculos que envolvam uma tabela inteira e vários joins) ou então uma aplicação onde escritas (inserts e updates) aconteçam em muito volume (uma aplicação como um Google Spreadsheet ou Twitter-like). Sempre acompanhe o dashboard do Heroku Postgresql para ver quais são os comandos mais lentos. Idealmente cada query deve estar na faixa das dezenas de milissegundos. Quando mais lenta for e quanto mais dados ele precisar carregar em memória, menos dessas você pode fazer concorrentemente no mesmo banco de dados.</p>
<p>Em particular, se você tem muitas tarefas pesadas, então já deveria estar usando workers de <a href="https://github.com/mperham/sidekiq/wiki/Deployment"target="_blank" rel="noopener">Sidekiq</a>. Porém se lembre que esses workers estarão usando o mesmo banco que os dynos web também vão usar. Nesse caso se você rodar uma query estupidamente pesada num worker, a experiência do usuário na web vai ser prejudicada.</p>
<p>Uma solução para isso é usar o recurso de <a href="https://devcenter.heroku.com/articles/heroku-postgres-follower-databases"target="_blank" rel="noopener">followers</a> que já está bem documentado. Quando você cria uma instância de Heroku Postgresql ele vai criar duas variáveis de ambiente baseado numa cor, por exemplo, HEROKU_POSTGRESQL_RED_URL e DATABASE_URL apontando pro endereço da máquina do Postgresql na Amazon AWS EC2.</p>
<p>Um follower é um banco slave do Postgresql, cujo endereço vai ser apontado numa outra variável de ambiente como, por exemplo, HEROKU_POSTGRESQL_AMBER_URL (digite o comando <tt>heroku config</tt> para ver essas variáveis). Um follower sempre vai estar pelo menos alguns milissegundos atrasado em relação ao banco master. Você sempre precisa se lembrar que nunca deve depender de um dado lido de um follower logo depois de ter feito o comando de Insert/Update.</p>
<p>No conceito básico, toda escrita deve ser feita somente no banco master (e ela sempre será gargalo de escrita, caso você tenha volumes muito estúpidos de escrita - e nesse caso vai ter que cair em outras opções que não é escopo deste post). E as leituras podem ser divididas em múltiplos slaves/followers (&ldquo;slave&rdquo; se você for old-school, &ldquo;follower&rdquo; se você for politicamente correto).</p>
<p>Porém uma aplicação Rails normal só sabe do banco que está apontado na variável DATABASE_URL, então como fazer para dividir as leituras no follower? Para isso existe a gem <a href="https://github.com/tchandy/octopus"target="_blank" rel="noopener">Octopus</a>, criada pelo Thiago Pradi e <a href="https://devcenter.heroku.com/articles/distributing-reads-to-followers-with-octopus"target="_blank" rel="noopener">bem documentada</a> pelo próprio Heroku. Resumindo o que já está documentado, comece adicionando a gem no <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;ar-octopus&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;octopus&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Rode o <tt>bundle install</tt> e agora baixe os seguintes arquivos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl -L -o config/shards.yml https://gist.github.com/catsby/6923840/raw/0aaf94ccc383951118c43b9b794fc62e427c2e51/shards.yml
curl -L -o config/initializers/octopus.rb https://gist.github.com/catsby/6923632/raw/87b5abba2e22c3acf8ed35d06e0ab9ca1bd9f0d0/octopus.rb</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com isso feito, em desenvolvimento o master e o follower vão ser o seu banco normal de desenvolvimento. Em produção no Heroku ele vai usar os bancos HEROKU_POSTGRESQL_*_URL que não são o DATABASE_URL como followers.</p>
<p>Abra o arquivo <tt>config/initializers/octopus.rb</tt> e adicione o seguinte método:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Octopus</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">random_follower</span>
</span></span><span class="line"><span class="cl">    <span class="n">followers</span><span class="o">.</span><span class="n">sample</span><span class="o">.</span><span class="n">to_sym</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que acontece é o seguinte, você pode simplesmente adicionar o seguinte método a um model que queira dividir a carga entre escrita no master e leitura no follower:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span>
</span></span><span class="line"><span class="cl">  <span class="n">replicated_model</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nunca coloque o método acima em <strong>todos</strong> os models indiscriminadamente. Você quer controlar em quais followers quer ler o que. No caso comum basta dividir entre todos os followers, mas caso tenha queries hiper-pesadas e queira dedicar um follower só pra isso, você também pode.</p>
<p>Um caso de uso comum é pegar workers de Sidekiq que façam muitos pré-cálculos, gerem relatórios ou denormalize tabelas grandes e faça o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># query pesada normal:</span>
</span></span><span class="line"><span class="cl"><span class="no">ModelPesado</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:abc</span><span class="p">,</span> <span class="ss">:xyz</span><span class="p">)</span><span class="o">.</span><span class="n">scope_pesado</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">find_each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="o">.</span><span class="n">algo_pesado</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># query modificada pra rodar num follower:</span>
</span></span><span class="line"><span class="cl"><span class="no">ModelPesado</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="no">Octopus</span><span class="o">.</span><span class="n">random_follower</span><span class="p">)</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:abc</span><span class="p">,</span> <span class="ss">:xyz</span><span class="p">)</span><span class="o">.</span><span class="n">scope_pesado</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">find_each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="o">.</span><span class="n">algo_pesado</span> <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sem o método <tt>Octopus.random_follower</tt> você teria que manualmente digitar o nome do follower que quer usar baseado na cor de sua variável de ambiente, por exemplo o HEROKU_POSTGRESQL_AMBER_URL você faria: <tt>.using(:amber_follower)</tt>. Com o método ele vai devolver o único follower que você tem ou aleatoriamente entre a lista de followers que você tem.</p>
<p>Outra forma é o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Octopus</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="no">Octopus</span><span class="o">.</span><span class="n">random_follower</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">ModelPesado</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:abc</span><span class="p">,</span> <span class="ss">:xyz</span><span class="p">)</span><span class="o">.</span><span class="n">scope_pesado</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">find_each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="o">.</span><span class="n">algo_pesado</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Uma coisa que parece necessária, como foi no caso de uma <a href="https://github.com/tchandy/octopus/issues/241"target="_blank" rel="noopener">issue do DelayedJob</a> se por algum motivo você precisar rodar um comando como Update dentro de um <tt>find_by_sql</tt>, lembre-se de explicitamente colocá-la no escopo do banco master:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Octopus</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="ss">:master</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Model</span><span class="o">.</span><span class="n">find_by_sql</span><span class="p">(</span><span class="s2">&#34;UPDATE ...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como disse antes, isso não é uma solução para que você jogue tudo pro follower. Avalie no próprio dashboard do seu banco de dados quais são as queries pesadas e no New Relic e comece movendo somente essas queries primeiro. Performance não é algo que você codifica sem medir: senão como saber se o que você fez realmente fez alguma diferença? Meça antes e meça depois. E continue nesse ciclo.</p>
<p>A gem Octopus e o recurso de Followers do Heroku Postgresql são excelentes opções para mover queries pesadas e descarregar seu banco master principal, o que vai garantir que sua aplicação possa escalar muito mais com poucas mudanças de código.</p>
]]></content:encoded><category>bancos-de-dados</category><category>sistemas-distribuidos</category><category>cloud</category></item><item><title>Ruby, a linguagem do mundo Cloud</title><link>https://akitaonrails.github.io/2013/10/29/ruby-a-linguagem-do-mundo-cloud/</link><guid isPermaLink="true">https://akitaonrails.github.io/2013/10/29/ruby-a-linguagem-do-mundo-cloud/</guid><pubDate>Tue, 29 Oct 2013 10:49:00 -0200</pubDate><description>&lt;p&gt;Ruby on Rails é definitivamente o primeiro motivo para qualquer novo programador se interessar por Ruby. É um framework muito estável que conquistou uma posição de destaque. Atualmente criou-se um ecossistema que expandiu além do framework e gerou uma coleção de bibliotecas, ferramentas e serviços. O pacote básico oferece muito: desde um excelente suporte a MVC-Web. O sistema de roteamento ainda é excepcional mesmo comparado a outros frameworks. Apesar de controverso o ORM ActiveRecord evoluiu e se tornou muito estável. Novos patterns de organização como &lt;a href="http://stackoverflow.com/questions/7860301/rails-patterns-decorator-vs-presenter"target="_blank" rel="noopener"&gt;Presenters&lt;/a&gt;, &lt;a href="http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/"target="_blank" rel="noopener"&gt;Service Objects&lt;/a&gt;, &lt;a href="http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns"target="_blank" rel="noopener"&gt;Concerns&lt;/a&gt; surgiram para facilitar aplicativos maiores. Pacotes como &lt;a href="http://blog.crowdint.com/2013/06/14/testing-rails-with-minitest.html"target="_blank" rel="noopener"&gt;Minitest&lt;/a&gt;, &lt;a href="https://github.com/rspec/rspec-rails"target="_blank" rel="noopener"&gt;RSpec&lt;/a&gt;, &lt;a href="https://github.com/thoughtbot/factory_girl"target="_blank" rel="noopener"&gt;Factory Girl&lt;/a&gt;, &lt;a href="http://www.fabricationgem.org"target="_blank" rel="noopener"&gt;Fabricator&lt;/a&gt; e outros continuam a tornar os testes mais e mais completos. Também controverso o &lt;a href="http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes"target="_blank" rel="noopener"&gt;Asset Pipeline&lt;/a&gt; faz o que todo framework deveria fazer como básico: minificar e otimizar assets.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ruby on Rails é definitivamente o primeiro motivo para qualquer novo programador se interessar por Ruby. É um framework muito estável que conquistou uma posição de destaque. Atualmente criou-se um ecossistema que expandiu além do framework e gerou uma coleção de bibliotecas, ferramentas e serviços. O pacote básico oferece muito: desde um excelente suporte a MVC-Web. O sistema de roteamento ainda é excepcional mesmo comparado a outros frameworks. Apesar de controverso o ORM ActiveRecord evoluiu e se tornou muito estável. Novos patterns de organização como <a href="http://stackoverflow.com/questions/7860301/rails-patterns-decorator-vs-presenter"target="_blank" rel="noopener">Presenters</a>, <a href="http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/"target="_blank" rel="noopener">Service Objects</a>, <a href="http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns"target="_blank" rel="noopener">Concerns</a> surgiram para facilitar aplicativos maiores. Pacotes como <a href="http://blog.crowdint.com/2013/06/14/testing-rails-with-minitest.html"target="_blank" rel="noopener">Minitest</a>, <a href="https://github.com/rspec/rspec-rails"target="_blank" rel="noopener">RSpec</a>, <a href="https://github.com/thoughtbot/factory_girl"target="_blank" rel="noopener">Factory Girl</a>, <a href="http://www.fabricationgem.org"target="_blank" rel="noopener">Fabricator</a> e outros continuam a tornar os testes mais e mais completos. Também controverso o <a href="http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes"target="_blank" rel="noopener">Asset Pipeline</a> faz o que todo framework deveria fazer como básico: minificar e otimizar assets.</p>
<p>O mundo front-end evoluiu além do JQuery. É trivial integrar os novos frameworks como <a href="https://github.com/codebrew/backbone-rails"target="_blank" rel="noopener">Backbone</a>, <a href="https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4"target="_blank" rel="noopener">Angular</a>, <a href="http://hashrocket.com/blog/posts/setting-up-an-ember-app-with-a-rails-backend"target="_blank" rel="noopener">Ember</a>. O mundo de CSS também evoluiu além do <a href="https://github.com/rails/sass-rails"target="_blank" rel="noopener">Sass</a>, temos facilmente integrado <a href="http://railsapps.github.io/rails-bootstrap/"target="_blank" rel="noopener">Twitter Bootstrap</a>, Foundation e outros. E o mundo de templates evoluiu além do ERB e <a href="http://haml.info"target="_blank" rel="noopener">HAML</a> com exemplos como <a href="http://slim-lang.com"target="_blank" rel="noopener">Slim</a>. Enfim, existem opções simples para todos os gostos.</p>
<p>E para facilitar a vida durante o desenvolvimento temos servicos como <a href="http://codeclimate.com/"target="_blank" rel="noopener">CodeClimate</a> para análise estática de código, <a href="https://travis-ci.org"target="_blank" rel="noopener">Travis-CI</a> para integração contínua. New Relic continua sendo uma opções completa e robusta de monitoramento em produção e melhoria contínua.</p>
<p>E se quisermos explorar além do básico ainda temos o mundo de NoSQL, <a href="http://mongoid.org/en/mongoid/docs/rails.html"target="_blank" rel="noopener">Mongoid</a> é a melhor opção para MongoDB. O melhor sistema de execução de tarefas assíncronas é o <a href="http://sidekiq.org"target="_blank" rel="noopener">Sidekiq</a>, que usa Redis. Integrar cache com Memcached chega a ser trivial. E tudo isso numa arquitetura que continua se mantendo &ldquo;shared nothing&rdquo;, garantindo nossa opções de escalabilidade horizontal.</p>
<p>E se o Ruby MRI não é mais suficiente, sempre temos a opção de usar <a href="https://devcenter.heroku.com/articles/moving-an-existing-rails-app-to-run-on-jruby"target="_blank" rel="noopener">JRuby</a> e ganhar alta performance com poucas (ou às vezes nenhuma) mudança de código. E com isso temos diversas opções de servidores de aplicação com <a href="https://devcenter.heroku.com/articles/rails-unicorn"target="_blank" rel="noopener">Unicorn</a>, <a href="https://www.eventials.com/pt-br/locaweb/superalimentando-seus-deployments-com-phusion-passenger-4/"target="_blank" rel="noopener">Passenger</a> e <a href="http://puma.io"target="_blank" rel="noopener">Puma</a>.</p>
<p>Mais do que isso, depois que o tema &ldquo;assíncrono&rdquo; ficou na moda por causa de node.js, surgiram diversos serviços que solucionam todos os use cases. Não há mais necessidade de se preocupar com comunicação assíncrona com serviços como <a href="https://www.eventials.com/pt-br/locaweb/do-loop-de-eventos-a-sistemas-distribuidos/"target="_blank" rel="noopener">Pusher</a>, <a href="http://www.pubnub.com"target="_blank" rel="noopener">PubNub</a> e <a href="https://www.quora.com/What-are-alternatives-to-pusher-com"target="_blank" rel="noopener">diversos outros</a>.</p>
<p>O stack está bastante completo, é uma combinação muito difícil de equiparar. E além disso o mundo Ruby está evoluindo para outras direções. Uma constatação importante: hoje o mundo está se movendo para a virtualização de servidores e automação de infraestrutura. O primeiro grande exemplo e ainda a melhor opção da categoria que ficou conhecida como IaaS (Infrastructure as a Service) é Amazon Web Services (AWS) EC2. Temos outros como Rackspace Cloud, Digital Ocean e no mundo não-open source temos Microsoft Azure.</p>
<p>Mas nós do mundo Ruby usamos uma categoria acima faz alguns anos: o chamado PaaS (Platform as a Service). A opção mais conhecida é sem dúvida o <a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener">Heroku</a> onde com um mero &ldquo;git push&rdquo; fazemos deployment em quantos servidores quisermos. Além dele ainda temos o <a href="https://www.engineyard.com/products/cloud"target="_blank" rel="noopener">Engine Yard Cloud</a>. Agora outra coisa interessante, dentre as maiores opções temos ainda os recém-chegados <a href="http://openshift.github.io"target="_blank" rel="noopener">RedHat OpenShift</a>, <a href="http://cloudfoundry.org"target="_blank" rel="noopener">VMWare Cloud Foundry</a>, até certo ponto também o Microsoft Azure, a própria Amazon com o <a href="http://ruby.awsblog.com/post/Tx2AK2MFX0QHRIO/Deploying-Ruby-Applications-to-AWS-Elastic-Beanstalk-with-Git"target="_blank" rel="noopener">AWS Elastic Beanstalk</a>, <a href="http://docs.aws.amazon.com/opsworks/latest/userguide/workinglayers-rails.html"target="_blank" rel="noopener">AWS Opsworks</a>, e o Google com uma opção mais restrita que é o Google App Engine. Existem outros mas vamos ficar com esses mais conhecidos, 7 opções e delas 5 utilizam ferramental e tecnologias Ruby.</p>
<p>O mundo Ruby está evoluindo isso faz algum tempo, começando com ferramentas de automação de infraestrutura que tentam tratar o mundo de hardware como se fosse software e nessa categoria temos <a href="http://puppetlabs.com/blog/ruby-dsl"target="_blank" rel="noopener">Puppet</a>, <a href="http://www.opscode.com/chef/"target="_blank" rel="noopener">Chef</a>. Sobre esse ferramental, novas tecnologias como <a href="https://github.com/cloudfoundry/bosh"target="_blank" rel="noopener">Bosh</a> foram criadas. Essas tecnologias permitiram Heroku, Engine Yard, OpenShift, Cloud Foundry, e o próprio AWS OpsWorks (que é uma interface Web sobre Chef).</p>
<p>Essa evolução no mundo de ferramental de Cloud retorna ao mundo de desenvolvimento mesmo no desktop com ferramentas como <a href="https://github.com/cloudfoundry/bosh"target="_blank" rel="noopener">Vagrant</a>, que automatiza a montagem do seu ambiente de desenvolvimento. Integrado com ferramentas como o Chef e facilitadores como <a href="http://berkshelf.com"target="_blank" rel="noopener">Berkshelf</a>, temos uma enorme biblioteca para montar ambiente complicados, podemos inclusive configurar rapidamente um ambiente inteiro <a href="https://github.com/Altoros/cf-vagrant-installer"target="_blank" rel="noopener">Cloud Foundry sobre Vagrant</a>.</p>
<p>O que quero dizer com isso? O mundo Cloud não está chegando: ele já está aqui. O ecossistema Ruby colaborou muito no bootstrap do novo mundo Front-end, influenciando práticas, tecnologias e agora faz o mesmo no mundo Back-end, diretamente no motor do Cloud. Além disso o ecossistema permeia hoje todas as etapas de uma solução completa: front-end, back-end, infraestrutura, além de Java, poucos ecossistemas são tão verticais.</p>
<p>Dicas? Comece a estudar ecossistemas como Chef e evoluções de ferramentas como Vagrant. Saber Rails, saber fazer testes automatizados, saber fazer front-end com pipeline automatizado, já pode ser considerado o básico.</p>
]]></content:encoded><category>ruby</category><category>rails</category><category>cloud</category><category>devops</category></item><item><title>[Heroku Tips] Enciclopédia do Heroku</title><link>https://akitaonrails.github.io/2012/04/20/heroku-tips-enciclopedia-do-heroku/</link><guid isPermaLink="true">https://akitaonrails.github.io/2012/04/20/heroku-tips-enciclopedia-do-heroku/</guid><pubDate>Fri, 20 Apr 2012 15:37:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;Atualização Jul/2016:&lt;/strong&gt; Sobre uploads e S3, leia meu post mais recente &lt;a href="http://www.akitaonrails.com/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads"target="_blank" rel="noopener"&gt;Updating my Old Posts on Uploads&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atualização 11/9:&lt;/strong&gt; Fiz alguns ajustes (troquei Unicorn por Passenger) e também escrevi &lt;a href="http://www.akitaonrails.com/2013/09/11/heroku-tips-problemas-iniciais-com-rails-4-no-heroku#.UjDR-WRgZ3Y"target="_blank" rel="noopener"&gt;outro post com mais dicas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atualização 05/06:&lt;/strong&gt; Depois que escrevi este artigo, encontrei um outro muito bom que recomendo a leitura por ter mais detalhes para complementar. O artigo se chama &lt;a href="http://rdegges.com/heroku-isnt-for-idiots"target="_blank" rel="noopener"&gt;Heroku isn’t for Idiots&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Se você quer lançar uma aplicação Rails rapidamente, não existe melhor solução do que o Heroku. Para quem não conhece, o Heroku é um Paas (Platform as a Service) que roda sobre o Amazon EC2 (que é um IaaS ou Infrastructure as a Service). O Heroku automatiza a criação de uma nova máquina virtual (volátil! isso é importante) e configura todo o ambiente para rodar Ruby.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>Atualização Jul/2016:</strong> Sobre uploads e S3, leia meu post mais recente <a href="http://www.akitaonrails.com/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads"target="_blank" rel="noopener">Updating my Old Posts on Uploads</a></p>
<p><strong>Atualização 11/9:</strong> Fiz alguns ajustes (troquei Unicorn por Passenger) e também escrevi <a href="http://www.akitaonrails.com/2013/09/11/heroku-tips-problemas-iniciais-com-rails-4-no-heroku#.UjDR-WRgZ3Y"target="_blank" rel="noopener">outro post com mais dicas</a>.</p>
<p><strong>Atualização 05/06:</strong> Depois que escrevi este artigo, encontrei um outro muito bom que recomendo a leitura por ter mais detalhes para complementar. O artigo se chama <a href="http://rdegges.com/heroku-isnt-for-idiots"target="_blank" rel="noopener">Heroku isn’t for Idiots</a></p>
<p>Se você quer lançar uma aplicação Rails rapidamente, não existe melhor solução do que o Heroku. Para quem não conhece, o Heroku é um Paas (Platform as a Service) que roda sobre o Amazon EC2 (que é um IaaS ou Infrastructure as a Service). O Heroku automatiza a criação de uma nova máquina virtual (volátil! isso é importante) e configura todo o ambiente para rodar Ruby.</p>
<p>O Heroku usa uma unidade de máquina virtual chamada “Dyno”, a grosso modo, considere um Dyno como uma máquina virtual “pequena” com 4 cores e até 512Mb de RAM sem swap file e sem suporte a persistência de arquivos (não faça uploads diretamente no diretório <tt>public/uploads</tt> ou algo assim, sempre configure para mandar para a Amazon S3, aprenda como <a href="https://devcenter.heroku.com/articles/s3"target="_blank" rel="noopener">neste tutorial</a>). Configurar um novo ambiente é simples, o próprio Heroku tem uma <a href="https://devcenter.heroku.com/articles/rails3"target="_blank" rel="noopener">boa documentação</a> ensinando como e recomendo ler antes de continuar.</p>
<p>Subir uma única dyno usando um banco de dados compartilhado PostgreSQL é de graça, o que é excelente para testar sua aplicação. Obviamente apenas um único dyno é pouco para qualquer aplicação séria lançada em produção para o público.</p>
<p>O Heroku fornece “stacks” padrão que é o perfil pré-configurado de um dyno para uma determinada plataforma. Para Ruby e Rails a mais atual (na data de publicação deste post) é a <a href="https://devcenter.heroku.com/articles/cedar"target="_blank" rel="noopener">Celadon Cedar</a>, a anterior era a <a href="https://devcenter.heroku.com/articles/bamboo"target="_blank" rel="noopener">Badious Bamboo</a> portanto se encontrar um tutorial qualquer de Heroku por aí, cheque sobre qual stack estamos falando, só use se for para Cedar.</p>
<h2>Concorrência num Dyno<span class="hx:absolute hx:-mt-20" id="concorrência-num-dyno"></span>
    <a href="#concorr%c3%aancia-num-dyno" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A primeira coisa que me chamou a atenção é que a configuração recomendada é executar uma aplicação Ruby usando o servidor <a href="http://code.macournoyer.com/thin/"target="_blank" rel="noopener">Thin</a>. Pense no Thin como uma evolução do venerado Mongrel mas que suporta executar Eventmachine internamente. Na prática é um Mongrel melhorado, o que significa que cada Dyno, por padrão, não suporta mais do que 1 única execução concorrente (não confundir com “requisições por segundo”!! Muita gente erra isso. Um único processo com uma única execução concorrente pode executar várias requisições por segundo, basta cada requisição demorar menos de 1 segundo).</p>
<p>Executar múltiplos Thins poderia ser possível mas se queremos mais processos rodando simultaneamente para responder mais requisições ao mesmo tempo, a melhor opção é usar <a href="https://www.phusionpassenger.com/"target="_blank" rel="noopener">Phusion Passenger</a>. Leiam a documentação para aprender as peculiaridades do Passenger, na prática pense nele como um controlador de processos Ruby. O melhor tutorial para usar Passenger no Heroku é da própria <a href="https://github.com/phusion/passenger-ruby-heroku-demo"target="_blank" rel="noopener">Phusion</a>. Não vou repetir tudo que ele disse, mas as partes importantes são:</p>
<p>Substituir a gem <tt>thin</tt> pela <tt>passenger</tt> na sua Gemfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:production</span><span class="p">,</span> <span class="ss">:staging</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;passenger&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="err">…</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O ideal é entre 3 e 4 processos, dependendo se sua dyno for a padrão <a href="https://blog.heroku.com/archives/2013/4/5/2x-dynos-beta"target="_blank" rel="noopener">1X ou 2X</a>. A informação não-oficial que eu tenho é que cada dyno tem até 4 CPUs, o que justifica esse número de processos. Mais do que isso, chequem sempre quanto de memória cada processo consome (ferramentas como <a href="http://newrelic.com/"target="_blank" rel="noopener">NewRelic</a> ajudam nisso) pois a somatória precisa ser menor que 512Mb ou você terá problemas.</p>
<p>Finalmente, a stack Cedar permite configurar perfis de dynos num arquivo chamado <tt>Procfile</tt> que fica na raíz do seu projeto. Para que a dyno levante com Unicorn coloque o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">web: bundle <span class="nb">exec</span> passenger start p <span class="nv">$PORT</span> —max-pool-size <span class="m">3</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Seu projeto precisa obrigatoriamente estar em Git pois isso vai criar um repositório remoto chamado ‘heroku’ no seu arquivo <tt>.git/config</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
[core]  
 repositoryformatversion = 0  
 filemode = true  
 bare = false  
 logallrefupdates = true  
 ignorecase = true  
[remote &#34;origin&#34;]  
 fetch = &#43;refs/heads/ **:refs/remotes/origin/**  
 url = git@github.com:Codeminer42/test.git  
[branch &#34;master&#34;]  
 remote = origin  
 merge = refs/heads/master  
[remote &#34;heroku&#34;]  
 url = git@heroku.com:test.git  
 fetch = &#43;refs/heads/ **:refs/remotes/heroku/**  </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Desta forma você faz o normal <tt>git push origin master</tt> para continuar subindo código para seu repositório de desenvolvimento e faz <tt>git push heroku master</tt> para realizar o deployment no Heroku. Isso reinicia sua Dyno, faz ela atualizar gems com <tt>bundle install</tt> executa a rake <tt>assets:precompile</tt>, caso esteja usando Rails 3.1 ou superior. Enfim, tudo que precisa para sua aplicação iniciar limpa.</p>
<h2>Rodando com Ruby 1.9.3 ou Ruby 2.0.0<span class="hx:absolute hx:-mt-20" id="rodando-com-ruby-193-ou-ruby-200"></span>
    <a href="#rodando-com-ruby-193-ou-ruby-200" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Outra peculiaridade da <a href="https://devcenter.heroku.com/articles/cedar"target="_blank" rel="noopener">stack Cedar</a> é que por padrão ele instala Ruby 1.9.2. Muitos novos projetos já usam 1.9.3 e é realmente a melhor versão para utilizar hoje (embora o 2.0.0 para projetos novos já é recomendado e estável o suficiente pra usar em produção). Na prática, ele inicia mais rápido que o 1.9.2, consome muito menos memória e executa mais rápido no geral.</p>
<p>Para escolher basta adicionar a seguinte linha no arquivo Gemfile do seu projeto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ruby</span> <span class="s2">&#34;1.9.3&#34;</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Obviamente, garanta que seu projeto em desenvolvimento e produção estão rodando com as mesmas versões!</p>
<h2>Delegando suas tarefas pesadas para Resque (ou Sidekiq)<span class="hx:absolute hx:-mt-20" id="delegando-suas-tarefas-pesadas-para-resque-ou-sidekiq"></span>
    <a href="#delegando-suas-tarefas-pesadas-para-resque-ou-sidekiq" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não vou chover no molhado explicando porque coisas como gerar relatórios pesados, enviar emails, consumir APIs externas e outras coisas devem ser separadas em tarefas para rodar em background numa fila. A fila mais simples atualmente para usar num ambiente Rails é o Resque, que utiliza banco de dados NoSQL Redis para organizar as filas.</p>
<p>Como você precisa necessariamente de uma instância de Redis no Heroku, execute o seguinte a partir da raíz do seu projeto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">heroku addons:add redistogo  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pesquise na página de add-ons do Heroku sobre outras opções e planos para entender quanto o pacote padrão de Redis suporta. Importante além de capacidade (que não precisa ser muito), é a quantidade de conexões simultâneas que é a quantidade de processos Ruby web e processos Ruby Resque Workers que você tem ao mesmo tempo.</p>
<p>Agora, adicione as gems que você precisa no arquivo <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="ss">mínimo</span><span class="p">:</span>  
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;resque&#39;</span><span class="p">,</span> <span class="ss">:require</span> <span class="o">=&gt;</span> <span class="s1">&#39;resque/server&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">alguns</span> <span class="n">opcionais</span>  
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;resque-scheduler&#39;</span><span class="p">,</span> <span class="ss">:require</span> <span class="o">=&gt;</span> <span class="s1">&#39;resque_scheduler&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;resque-lock&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;resque_mailer&#39;</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Rode o Bundler para atualizar, depois vamos configurar as seguinte tasks Rake num arquivo <tt>lib/tasks/resque.rake</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;resque/tasks&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;resque_scheduler/tasks&#39;</span> <span class="c1"># opcional se utilizar resque_scheduler</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">task</span> <span class="s2">&#34;resque:setup&#34;</span> <span class="o">=&gt;</span> <span class="ss">:environment</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;QUEUE&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="s1">&#39;*&#39;</span> <span class="k">if</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;QUEUE&#39;</span><span class="o">].</span><span class="n">blank?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;resque&#39;</span> <span class="nb">require</span> <span class="s1">&#39;resque_scheduler&#39;</span> <span class="nb">require</span> <span class="s1">&#39;resque/scheduler&#39;</span> <span class="no">Resque</span><span class="o">.</span><span class="n">redis</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;REDISTOGO_URL&#34;</span><span class="o">]</span> <span class="no">Resque</span><span class="o">.</span><span class="n">schedule</span> <span class="o">=</span> <span class="no">YAML</span><span class="o">.</span><span class="n">load_file</span><span class="p">(</span><span class="s1">&#39;config/scheduler.yml&#39;</span><span class="p">)</span> <span class="c1"># opcional se utilizar resque_scheduler Resque::Worker.all.each {|w| w.unregister_worker}</span>
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="no">Fix</span> <span class="k">for</span> <span class="n">handling</span> <span class="n">resque</span> <span class="n">jobs</span> <span class="n">on</span> <span class="no">Heroku</span> <span class="n">cedar</span>
</span></span><span class="line"><span class="cl"><span class="mi">2</span><span class="o">.</span> <span class="ss">http</span><span class="p">:</span><span class="sr">//s</span><span class="n">tackoverflow</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">questions</span><span class="o">/</span><span class="mi">2611747</span><span class="o">/</span><span class="n">rails</span><span class="o">-</span><span class="n">resque</span><span class="o">-</span><span class="n">workers</span><span class="o">-</span><span class="nb">fail</span><span class="o">-</span><span class="n">with</span><span class="o">-</span><span class="n">pgerror</span><span class="o">-</span><span class="n">server</span><span class="o">-</span><span class="n">closed</span><span class="o">-</span><span class="n">the</span><span class="o">-</span><span class="n">connection</span><span class="o">-</span><span class="n">unexpectedly</span>  
</span></span><span class="line"><span class="cl"> <span class="no">Resque</span><span class="o">.</span><span class="n">after_fork</span> <span class="k">do</span> <span class="o">|</span><span class="n">job</span><span class="o">|</span>  
</span></span><span class="line"><span class="cl"> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">establish_connection</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">desc</span> <span class="s2">&#34;EC2 instance name changes every time, so run this before a new deployment&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="n">task</span> <span class="s2">&#34;resque:clean_workers&#34;</span> <span class="o">=&gt;</span> <span class="ss">:environment</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"> <span class="no">Resque</span><span class="o">::</span><span class="no">Worker</span><span class="o">.</span><span class="n">all</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span><span class="o">|</span><span class="n">w</span><span class="o">|</span> <span class="n">w</span><span class="o">.</span><span class="n">unregister_worker</span><span class="p">}</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Resque sobe workers realizando fork, cada worker se inicia subindo seu próprio ambiente Rails, incluindo coisas como se conectar com o banco de dados. Mas ao fazer fork, essas conexões precisam ser refeitas, por isso existe o bloco <tt>Resque.after_fork</tt>. Atenção a todo sistema que realiza forks (Passenger é um exemplo).</p>
<p>A gem <a href="https://github.com/simple10/resque-heroku"target="_blank" rel="noopener">resque-heroku</a> faz a mesma coisa, mas no caso como no exemplo onde estou configurando muitas outras coisas, não é necessário adicionar essa gem só por causa dessas 3 linhas.</p>
<p>Agora, nos arquivos <tt>config/environments/development.rb</tt>, <tt>config/environments/test.rb</tt> e outras que não são a de produção para o Heroku, adicione no início do arquivo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;REDISTOGO_URL&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s1">&#39;redis://localhost:6379&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="no">TestApp</span><span class="o">::</span><span class="no">Application</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl"><span class="err">…</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Também crie um arquivo <tt>config/initializers/resque.rb</tt> com:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Resque</span><span class="o">.</span><span class="n">redis</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;REDISTOGO_URL&#34;</span><span class="o">]</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você sabe usar Resque, certamente tem um Redis instalado localmente na sua máquina de desenvolvimento e essa é a URL padrão. Modifique se precisar.</p>
<p>Finalmente, adicione novas linhas ao arquivo <tt>Procfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">web: bundle <span class="nb">exec</span> unicorn p <span class="nv">$PORT</span> -c ./config/unicorn.rb  
</span></span><span class="line"><span class="cl">scheduler: bundle <span class="nb">exec</span> rake resque:scheduler  
</span></span><span class="line"><span class="cl">worker: bundle <span class="nb">exec</span> rake resque:workers <span class="nv">QUEUE</span><span class="o">=</span>* <span class="nv">COUNT</span><span class="o">=</span><span class="m">2</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estes são exemplos onde estou utilizando a gem <a href="https://github.com/bvandenbos/resque-scheduler"target="_blank" rel="noopener">resque-scheduler</a>. Se você precisa de uma funcionalidade semelhante a um “crontab”, esta gem serve para isso. Mas está fora do escopo deste artigo falar sobre ela, então simplesmente ignorem quando menciono o scheduler se não precisar dela.</p>
<p>Novamente, quero aproveitar o máximo cada dyno. O arquivo Procfile determina o que cada perfil de dyno vai fazer. No meu exemplo, as dynos chamadas “web” sobem com 4 processos Unicorn como mostrei anteriormente. A chamada “scheduler” se responsabiliza só pelo processo de criar tarefas no Resque. E finalmente as “worker” sobem processos <em>trabalhadores</em> que ficam “ouvindo” as filas do Resque no Redis e executam as tarefas que aparecem lá. No caso dá para subir um único worker por dyno se usar a task rake <tt>resque:worker</tt> ou múltiplos usando a <tt>resque:workers</tt> como está no exemplo. Novamente suas limitações são: a quantidade de memória consumida por worker e o “peso” de execução de cada tarefa. Se as tarefas forem muito pesadas, melhor ter poucos worker por dyno.</p>
<p>Outra dica importante: especialmente se estiver utilizando o scheduler, que vai adicionando novas tarefas baseado num intervalo de tempo. Imagine uma tarefa que leva 1 minuto para ser executada e que o scheduler recoloca no Redis a cada 2 minutos. Se você tiver múltiplos workers em paralelo, somente um deles vai executar e tudo funciona.</p>
<p>Mas imagine se por qualquer razão a tarefa levar 5 minutos. Significa que nesse tempo o scheduler terá colocado pelo menos 2 tarefas iguais no Redis e haverá dois workers executando a mesma tarefa em paralelo! Você corre o risco de duplicar dados, ou qualquer outro efeito colateral de ter a mesma tarefa executando encavalado ao mesmo tempo.</p>
<p>Para isso serve a gem <a href="https://github.com/defunkt/resque-lock"target="_blank" rel="noopener">resque-lock</a> que declarei como opcional acima. Somente se existir tarefas que não podem ser executadas em paralelo e você tem a possibilidade disso acontecer. Normalmente você terá classes Ruby no PATH, por exemplo, em <tt>app/resque/example.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;resque/plugins/lock&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Example</span>  
</span></span><span class="line"><span class="cl"> <span class="kp">extend</span> <span class="no">Resque</span><span class="o">::</span><span class="no">Plugins</span><span class="o">::</span><span class="no">Lock</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">perform</span><span class="p">(</span><span class="n">repo_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">heavy_lifting</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É só o que você precisa fazer para garantir que esta tarefa não terá o perigo de ser executada em paralelo por acidente. Cuidado: nem toda tarefa em paralelo é ruim, por isso avalie cada situação. Agora, precisamos iniciar os dynos, para isso faça:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">heroku scale <span class="nv">web</span><span class="o">=</span><span class="m">4</span> <span class="nv">scheduler</span><span class="o">=</span><span class="m">1</span> <span class="nv">worker</span><span class="o">=</span><span class="m">2</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Neste exemplo, estou subindo 4 dynos perfil “web”, o que significa capacidade para receber até 16 requisições <em>simultaneamente</em> (neste exemplo, se cada requisição leva em média 100ms para responder, esse número de dynos pode responder até 160 requisições simultaneamente, o que é bastante). Em seguida estou subindo uma única instância do controlador de scheduler (novamente, se você não precisa, ignore). E finalmente subindo 2 dynos perfil “worker”, cada um com 2 workers Resque, totalizando 4 trabalhadores escutando a fila do Redis e podendo executar até 4 tarefas em paralelo. Para o Redis, significa que precisamos ter no mínimo capacidade para receber 16 + 1 + 4 = 21 conexões simultâneas. A mesma quantidade de conexões no banco de dados PostgreSQL (lembre-se sempre disso!)</p>
<h2>Assets na Amazon S3<span class="hx:absolute hx:-mt-20" id="assets-na-amazon-s3"></span>
    <a href="#assets-na-amazon-s3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A <a href="https://devcenter.heroku.com/s3"target="_blank" rel="noopener">própria documentação do Heroku</a> explica como você faz seus assets serem servidos por CDNs como CloudFront ou Amazon. Leia atentamente para ter mais detalhes, aqui vou resumir algumas coisas importantes.</p>
<p>Primeiro, obviamente, crie sua conta na <a href="http://aws.amazon.com/"target="_blank" rel="noopener">Amazon Web Services</a>, coloque seu cartão de crédito e tenha acesso ao <a href="https://console.aws.amazon.com/console/home"target="_blank" rel="noopener">Dashboard</a> que permite controlar os diversos serviços que quer usar. No caso abra o S3 e configure “Buckets” que é como se fossem “diretórios” para seus assets.</p>
<p>Se quiser, pode criá-los diretamente usando as APIs da Amazon, diretamente num console Ruby como IRB ou Pry, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;aws/s3&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="no">AWS</span><span class="o">::</span><span class="no">S3</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">establish_connection!</span><span class="p">(</span>  
</span></span><span class="line"><span class="cl"> <span class="ss">:access_key_id</span> <span class="o">=&gt;</span> <span class="s1">&#39;…&#39;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="ss">:secret_access_key</span> <span class="o">=&gt;</span> <span class="s1">&#39;…&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="no">AWS</span><span class="o">::</span><span class="no">S3</span><span class="o">::</span><span class="no">Bucket</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s1">&#39;uploads.mysite&#39;</span><span class="p">,</span> <span class="ss">:access</span> <span class="o">=&gt;</span> <span class="ss">:public_read</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="no">AWS</span><span class="o">::</span><span class="no">S3</span><span class="o">::</span><span class="no">Bucket</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s1">&#39;assets.mysite&#39;</span><span class="p">,</span> <span class="ss">:access</span> <span class="o">=&gt;</span> <span class="ss">:public_read</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="no">AWS</span><span class="o">::</span><span class="no">S3</span><span class="o">::</span><span class="no">Bucket</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s1">&#39;staging.assets.mysite&#39;</span><span class="p">,</span> <span class="ss">:access</span> <span class="o">=&gt;</span> <span class="ss">:public_read</span><span class="p">)</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Obviamente, instale primeiro a gem <tt>aws-s3</tt>. E claro, estou criando 3 buckets de exemplo, crie com quaisquer nomes (únicos, <a href="http://support.rightscale.com/06-FAQs/FAQ_0094_-_What_are_valid_S3_bucket_names%3F"target="_blank" rel="noopener">válidos e compatíveis com formato de DNS</a>).</p>
<p>Quando você cria sua conta, também ganha um <strong>Access Key ID</strong> e um <strong>Secret Access Key</strong> que sempre usará para ter permissões nos seus buckets. Mas só isso não basta, você precisa – a partir do Dashboard via web – configurar <strong>policies</strong> para seus buckets. Aprenda como a partir <a href="http://s3browser.com/working-with-amazon-s3-bucket-policies.php#amazon-s3-bucket-policies"target="_blank" rel="noopener">deste artigo</a>.</p>
<p>Uma policy tem mais ou menos este formato:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2008-10-17&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl"> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Sid&#34;</span><span class="p">:</span> <span class="s2">&#34;AllowPublicRead&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;AWS&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span>  
</span></span><span class="line"><span class="cl"> <span class="p">},</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;s3:GetObject&#34;</span>  
</span></span><span class="line"><span class="cl"> <span class="p">],</span>  
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;arn:aws:s3:::assets.mysite/*&#34;</span>  
</span></span><span class="line"><span class="cl"> <span class="p">]</span>  
</span></span><span class="line"><span class="cl"> <span class="p">}</span>  
</span></span><span class="line"><span class="cl"> <span class="p">]</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não mude a “Version” e coloque o nome correto da sua bucket em “Resource”. Isso vai garantir que quando qualquer assets gravado nessas buckets possam ser devidamente acessadas.</p>
<p>Isso foi só para configurar a Amazon. Agora você precisa configurar sua aplicação para que contenha os dados corretos.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1. para versões antigas da gem Heroku:  
</span></span><span class="line"><span class="cl">heroku plugins:install https://github.com/heroku/heroku-labs.git  
</span></span><span class="line"><span class="cl">heroku labs:enable user_env_compile -a mysite
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1. obrigatório  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span>…  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span>…  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">FOG_DIRECTORY</span><span class="o">=</span>assets.mysite  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">FOG_PROVIDER</span><span class="o">=</span>AWS
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1. opcional:  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">FOG_REGION</span><span class="o">=</span>us-east-1  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">ASSET_SYNC_GZIP_COMPRESSION</span><span class="o">=</span><span class="nb">true</span>  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">ASSET_SYNC_MANIFEST</span><span class="o">=</span><span class="nb">false</span>  
</span></span><span class="line"><span class="cl">heroku config:add <span class="nv">ASSET_SYNC_EXISTING_REMOTE_FILES</span><span class="o">=</span>keep  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não deve ser difícil entender essas configurações. Agora, precisamos configurar o arquivo <tt>config/production.rb</tt> com o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_controller</span><span class="o">.</span><span class="n">asset_host</span> <span class="o">=</span> <span class="s2">&#34;http://</span><span class="si">#{</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;FOG_DIRECTORY&#39;</span><span class="o">]</span><span class="si">}</span><span class="s2">.s3.amazonaws.com&#34;</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aprenda mais sobre o Asset Pipeline no mínimo lendo o <a href="http://guides.rubyonrails.org/asset_pipeline.html"target="_blank" rel="noopener">guia oficial</a>. Não me interessa quem gosta ou não gosta do Asset Pipeline (ou que não gosta porque não sabe usar). Mas vou assumir que independente da opinião, todos aqui sabem usar. Por exemplo, sabem que não pode haver tags HTML com a URL da imagem escrita manualmente, mas sim usando helpers como <tt>image_tag</tt> e mesmo no CSS, estar utilizando <a href="http://sass-lang.com/"target="_blank" rel="noopener">SASS</a> para ter acesso a helpers como <tt>image-uri</tt>. Não deve existir URLs do aplicativo, apontando para imagens, stylesheets, javascripts ou qualquer coisa, escritas manualmente sem usar esses helpers. Isso é obrigatório porque em desenvolvimento na sua máquina, ele vai apontar para URLs relativas na sua instância, mas em produção ele vai apontar para URLs externas na Amazon S3.</p>
<p>A grande vantagem é justamente porque quando os navegadores dos seus usuários pedirem imagens, stylesheets, isso vai pro S3 e não vai consumir absolutamente nada nos seus dynos, deixando-os livres para executar exclusivamente tarefas dinâmicas.</p>
<p>Queremos que os assets gerados pelo Asset Pipeline sejam sincronizados nos seus buckets na Amazon S3. Começamos adicionando as seguintes gems no <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:assets</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">obrigatório</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;sass-rails&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;coffee-rails&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;uglifier&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;asset_sync&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="n">recomendado</span> <span class="n">para</span> <span class="no">Sass</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;compass&#39;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">gem</span> <span class="s1">&#39;compass-rails&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembrando que no Rails 4 não precisa do group “assets”. Continuando, precisamos criar um arquivo de configuração em <tt>config/initializers/asset_sync.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">production?</span> <span class="o">||</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">staging?</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">defined?</span><span class="p">(</span><span class="no">AssetSync</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="no">AssetSync</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">fog_provider</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;FOG_PROVIDER&#39;</span><span class="o">]</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">aws_access_key_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_ACCESS_KEY_ID&#39;</span><span class="o">]</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">aws_secret_access_key</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_SECRET_ACCESS_KEY&#39;</span><span class="o">]</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">fog_directory</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;FOG_DIRECTORY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="no">Increase</span> <span class="n">upload</span> <span class="n">performance</span> <span class="n">by</span> <span class="n">configuring</span> <span class="n">your</span> <span class="n">region</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">fog_region</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;FOG_REGION&#39;</span><span class="o">]</span>  
</span></span><span class="line"><span class="cl"> <span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="mi">2</span><span class="o">.</span> <span class="no">Don</span><span class="s1">&#39;t delete files from the store  
</span></span></span><span class="line"><span class="cl"><span class="s1"> config.existing_remote_files = ENV[&#39;</span><span class="no">ASSET_SYNC_EXISTING_REMOTE_FILES</span><span class="s1">&#39;]  
</span></span></span><span class="line"><span class="cl"><span class="s1"> #
</span></span></span><span class="line"><span class="cl"><span class="s1">3. Automatically replace files with their equivalent gzip compressed version  
</span></span></span><span class="line"><span class="cl"><span class="s1"> config.gzip_compression = ENV[&#39;</span><span class="no">ASSET_SYNC_GZIP_COMPRESSION</span><span class="s1">&#39;]  
</span></span></span><span class="line"><span class="cl"><span class="s1"> #
</span></span></span><span class="line"><span class="cl"><span class="s1">4. Use the Rails generated &#39;</span><span class="n">manifest</span><span class="o">.</span><span class="n">yml</span><span class="s1">&#39; file to produce the list of files to
</span></span></span><span class="line"><span class="cl"><span class="s1">5. upload instead of searching the assets directory.
</span></span></span><span class="line"><span class="cl"><span class="s1">6. config.manifest == ENV[&#39;</span><span class="no">ASSET_SYNC_MANIFEST</span><span class="err">&#39;</span><span class="o">]</span>  
</span></span><span class="line"><span class="cl"> <span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="mi">7</span><span class="o">.</span> <span class="no">Fail</span> <span class="n">silently</span><span class="o">.</span> <span class="no">Useful</span> <span class="k">for</span> <span class="n">environments</span> <span class="n">such</span> <span class="n">as</span> <span class="no">Heroku</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">fail_silently</span> <span class="o">=</span> <span class="kp">true</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Entenda cada configuração e veja qual é a melhor para você, mas esta funciona o suficiente para mim. Não esqueça de configurar o arquivo principal <tt>config/application.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">initialize_on_precompile</span> <span class="o">=</span> <span class="kp">false</span> <span class="c1"># Rails 4 não precisa disso  </span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">precompile</span> <span class="o">+=</span> <span class="sx">%w(active_admin.css cross_browser.css active_admin.js)</span>  
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">enabled</span> <span class="o">=</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="k">if</span> <span class="n">you</span> <span class="n">prefer</span> <span class="sb">`.sass`</span> <span class="n">over</span> <span class="sb">`.scss`</span><span class="o">.</span>  
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">sass</span><span class="o">.</span><span class="n">preferred_syntax</span> <span class="o">=</span> <span class="ss">:sass</span>  
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">initialize_on_precompile</span> <span class="o">=</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="no">Version</span> <span class="n">of</span> <span class="n">your</span> <span class="n">assets</span><span class="p">,</span> <span class="n">change</span> <span class="n">this</span> <span class="k">if</span> <span class="n">you</span> <span class="n">want</span> <span class="n">to</span> <span class="n">expire</span> <span class="n">all</span> <span class="n">your</span> <span class="n">assets</span>  
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">version</span> <span class="o">=</span> <span class="s1">&#39;1.0&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">logger</span> <span class="o">=</span> <span class="kp">false</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você usar o Asset Pipeline direito declarando seus assets nos arquivos de manifesto <tt>app/uploads/stylesheets/application.css.sass</tt> e <tt>app/uploads/javascripts/application.js.erb</tt> tudo corre normalmente, mas arquivos fora desses manifestos que você vai precisar (minimize o uso disso) e declare em <tt>config.assets.precompile</tt>.</p>
<p>Agora, toda vez que a task <tt>assets:precompile</tt> for executada na sua máquina, ela vai se comportar como esperado gerando os arquivos estáticos na pasta local <tt>public/uploads</tt> mas quando for feito deploy com <tt>git push heroku master</tt>, a mesma task vai gerar os assets e realizar upload do que foi modificado no seu bucket na Amazon S3.</p>
<h3>Paperclip e upload de arquivos<span class="hx:absolute hx:-mt-20" id="paperclip-e-upload-de-arquivos"></span>
    <a href="#paperclip-e-upload-de-arquivos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um adendo, é que não basta os assets estáticos da aplicação estarem no S3, também queremos que quaisquer uploads de usuários do site sejam mandados automaticamente para lá. Procurem as documentações oficiais das principais gems como Paperclip ou Carrierwave. Veja a <a href="https://devcenter.heroku.com/articles/s3#uploading_files_in_rails_with_paperclip"target="_blank" rel="noopener">documentação oficial</a> do Heroku sobre Paperclip para aprender mais.</p>
<p>No caso do Carrierwave (eu particular não tenho preferência por nenhum, ambos servem adequadamente para mim), uma classe de uploader seria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UserBackgroundImageUploader</span> <span class="o">&lt;</span> <span class="no">CarrierWave</span><span class="o">::</span><span class="no">Uploader</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="no">Include</span> <span class="no">RMagick</span> <span class="ow">or</span> <span class="no">MiniMagick</span> <span class="ss">support</span><span class="p">:</span>  
</span></span><span class="line"><span class="cl"> <span class="kp">include</span> <span class="no">CarrierWave</span><span class="o">::</span><span class="no">RMagick</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="no">Choose</span> <span class="n">what</span> <span class="n">kind</span> <span class="n">of</span> <span class="n">storage</span> <span class="n">to</span> <span class="n">use</span> <span class="k">for</span> <span class="n">this</span> <span class="ss">uploader</span><span class="p">:</span>  
</span></span><span class="line"><span class="cl"> <span class="n">storage</span> <span class="ss">:fog</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="mi">1</span><span class="o">.</span> <span class="no">Override</span> <span class="n">the</span> <span class="n">directory</span> <span class="n">where</span> <span class="n">uploaded</span> <span class="n">files</span> <span class="n">will</span> <span class="n">be</span> <span class="n">stored</span><span class="o">.</span>
</span></span><span class="line"><span class="cl"><span class="mi">2</span><span class="o">.</span> <span class="no">This</span> <span class="n">is</span> <span class="n">a</span> <span class="n">sensible</span> <span class="n">default</span> <span class="k">for</span> <span class="n">uploaders</span> <span class="n">that</span> <span class="n">are</span> <span class="n">meant</span> <span class="n">to</span> <span class="n">be</span> <span class="ss">mounted</span><span class="p">:</span>  
</span></span><span class="line"><span class="cl"> <span class="k">def</span> <span class="nf">store_dir</span>  
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;uploads/</span><span class="si">#{</span><span class="n">model</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">underscore</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">mounted_as</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">id</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>  
</span></span><span class="line"><span class="cl"> <span class="k">end</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois meu model de exemplo <tt>User</tt> contém:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>  
</span></span><span class="line"><span class="cl"> <span class="err">…</span>  
</span></span><span class="line"><span class="cl"> <span class="n">mount_uploader</span> <span class="ss">:background_image</span><span class="p">,</span> <span class="no">UserBackgroundImageUploader</span>  
</span></span><span class="line"><span class="cl"> <span class="err">…</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E nas views procuro a URL da imagem assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="vi">@user</span><span class="o">.</span><span class="n">background_image_url</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Até aqui, absolutamente nada de novo. A novidade vem criando o arquivo <tt>config/initializer/carrierwave.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">CarrierWave</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">fog_credentials</span> <span class="o">=</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="ss">:provider</span> <span class="o">=&gt;</span> <span class="s1">&#39;AWS&#39;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="ss">:aws_access_key_id</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_ACCESS_KEY_ID&#39;</span><span class="o">]</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="ss">:aws_secret_access_key</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_SECRET_ACCESS_KEY&#39;</span><span class="o">]</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl"> <span class="p">}</span>  
</span></span><span class="line"><span class="cl"> <span class="n">config</span><span class="o">.</span><span class="n">fog_directory</span> <span class="o">=</span> <span class="s1">&#39;uploads.mysite&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso utilizará a configuração que você já fez acima, mas mudamos o nome do bucket para não confundir buckets de ‘assets’ de buckets de ‘uploads dos usuários’. Por isso criamos múltiplos buckets de exemplo, lembra? Inclusive, se criar dynos para ambiente de homologação/staging, lembre-se de criar buckets separados e configurá-los corretamente dependendo do ambiente neste arquivo de configuração.</p>
<p>Só isso deve ser suficiente para todos os uploads serem realizados na sua conta do S3 e não mais consumir sua aplicação Rails no Heroku, mesmo porque o Heroku não permite gravar arquivos localmente nos dynos, pois quando eles forem reiniciados, tudo que não estiver no repositório Git (arquivos temporários, uploads), serão perdidos.</p>
<h2>New Relic<span class="hx:absolute hx:-mt-20" id="new-relic"></span>
    <a href="#new-relic" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um pequeno truque se por acaso estiver usando Unicorn em vez de Thin ou Passenger, da mesma forma como precisamos reconectar no banco em caso de fork no Resque, entenda que o serviço New Relic precisa de tratamento especial quando o Unicorn realiza um novo fork de processo Ruby.</p>
<p>Não vou re-explicar o que todo mundo já sabe sobre configurar NewRelic, a dica é apenas criar o seguinte arquivo em <tt>config/initializers/new_relic_unicorn.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">NewRelic</span><span class="o">::</span><span class="no">Agent</span><span class="o">.</span><span class="n">after_fork</span><span class="p">(</span><span class="ss">:force_reconnect</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">)</span> <span class="k">if</span> <span class="n">defined?</span> <span class="no">Unicorn</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Finalmentes<span class="hx:absolute hx:-mt-20" id="finalmentes"></span>
    <a href="#finalmentes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Para ambiente de testes, a versão grátis do Heroku é boa o suficiente, para produção não esqueça de subir múltiplos dynos (suba 3 ou 4 para começar, monitore e suba mais se precisar). Também faça upgrade dos seus serviços como Redis e PostgreSQL de acordo com o tamanho que precisar, por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">heroku addons:upgrade redistogo:medium  
</span></span><span class="line"><span class="cl">heroku addons:add heroku-postgresql:fugu  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando você cria sua nova aplicação no Heroku, ele te dá um nome arbitrário que você mode mudar no dashboard web do Heroku e ele tem o formato <tt>mysite.herokuapp.com</tt>. No seu provedor de DNS (como Zerigo ou DNSimple) faça seu domínio (ex <a href="https://www.mysiteoficial.com"target="_blank" rel="noopener">www.mysiteoficial.com</a>) apontar para a URL ‘mysite.herokuapp.com’ (não há IPs fixos porque o EC2 não é feito para funcionar assim). Daí você pode configurar sua aplicação no Heroku para responder ao novo domínio assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">heroku addons:add custom_domains  
</span></span><span class="line"><span class="cl">heroku domains:add mysiteoficial.com  
</span></span><span class="line"><span class="cl">heroku domains:add mysiteoficial.com.br  
</span></span><span class="line"><span class="cl">heroku domains:add www.mysiteoficial.com  
</span></span><span class="line"><span class="cl">heroku domains:add www.mysiteoficial.com.br  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Registre a maior quantidade de domínios possíveis (dentro e fora do Brasil), nunca esqueça dos subdomínios. Se errar e precisar apagar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">heroku domains:clear <span class="c1"># to stop responding to domains  </span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Heroku é extremamente eficiente para ambientes onde você ainda não tem como ter um administrador de sistemas sênior (caro e raridade no mercado) e uma equipe 24×7. Você pode começar pequeno e depois criar sua própria infraestrutura customizada em máquinas diretamente na Amazon EC2, Rackspace Cloud ou outro provedor de Cloud Servers. E sua aplicação, diferente de se fosse num Google App Engine, não precisa ser codificada de forma proprietária. A aplicação Rails continua executando normalmente, e sem grandes mudanças além das que você viu neste post, em qualquer outro servidor devidamente configurado.</p>
<p>Uma configuração grande pra maioria dos sites brasileiros com 5 dynos web, 2 dynos workers, mais um PostgreSQL plano “Fugu”, e outros add-ons menores como Memcached, New Relic, etc vai lhe custar cerca de USD 700. Alguém pode dizer <em>“Nossa, que caro!”</em> Claro que não. Só o salário de um bom sys admin vai ser pelo menos 4 ou 5 vezes isso por mês, e não estou contando nem o custo de infraestrutura. Portanto, é <strong>barato</strong>. A menos, claro, que seu negócio seja tão ruim que não valha no mínimo isso por mês e, nesse caso, melhor não perder tempo para construí-lo.</p>
]]></content:encoded><category>rails</category><category>cloud</category><category>devops</category></item><item><title>Off-Topic: Google App Engine e Cloud Computing</title><link>https://akitaonrails.github.io/2008/04/13/off-topic-google-app-engine-e-cloud-computing/</link><guid isPermaLink="true">https://akitaonrails.github.io/2008/04/13/off-topic-google-app-engine-e-cloud-computing/</guid><pubDate>Sun, 13 Apr 2008 01:27:00 -0300</pubDate><description>&lt;p&gt;Assim como &lt;em&gt;Web 2.0&lt;/em&gt;, outro termo usado o tempo todo é &lt;strong&gt;Cloud Computing&lt;/strong&gt;. Muita gente usa para designar muitas coisas. Outro termo usado como sinônimo, sem ser exatamente a mesma coisa, é &lt;strong&gt;Web Services&lt;/strong&gt; (não o padrão XML). Na realidade isso é antigo: é o que chamávamos de ASPs (Application Service Providers).&lt;/p&gt;
&lt;p&gt;Exemplos disso são serviços como Basecamp, para gerenciar projetos sem que a empresa precise gastar em manutenção, ou mesmo seu Webmail favorito. São serviços online onde você paga para não precisar se preocupar com infraestrutura. Um tipo de outsourcing de serviços.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Assim como <em>Web 2.0</em>, outro termo usado o tempo todo é <strong>Cloud Computing</strong>. Muita gente usa para designar muitas coisas. Outro termo usado como sinônimo, sem ser exatamente a mesma coisa, é <strong>Web Services</strong> (não o padrão XML). Na realidade isso é antigo: é o que chamávamos de ASPs (Application Service Providers).</p>
<p>Exemplos disso são serviços como Basecamp, para gerenciar projetos sem que a empresa precise gastar em manutenção, ou mesmo seu Webmail favorito. São serviços online onde você paga para não precisar se preocupar com infraestrutura. Um tipo de outsourcing de serviços.</p>
<p>Esta semana o Google causou um pequeno furor ao lançar sua resposta a Cloud Computing: o <a href="http://code.google.com/appengine/"target="_blank" rel="noopener">Google App Engine</a>. O Techcrunch publicou um review <a href="http://www.techcrunch.com/2008/04/08/techcrunch-labs-our-experience-building-and-launching-app-on-google-app-engine/"target="_blank" rel="noopener">aqui</a>. Mas o que é <a href="http://en.wikipedia.org/wiki/Cloud_computing"target="_blank" rel="noopener">Cloud Computing</a>? Antes de mais nada, vamos explicar os termos mais usados no mercado.</p>
<p>Nós, desenvolvedores, em algum momento vamos querer colocar nossa aplicação em produção. Eis quando chega a parte mais cabeluda do processo: deployment!</p>
<p>Existem várias opções. Primeiro, sobre software:</p>
<ul>
<li>
<p>Comprar uma aplicação comercial. Casos de <a href="http://en.wikipedia.org/wiki/Commodity"target="_blank" rel="noopener">commodities</a> como Exchange Server: você escolhe, compra, instala e começa a usar. Aplicações maiores vendem em regime de licenças, por usuário ou por processador. Alguns softwares permitem ser estendidos e customizados, mas normalmente você precisa se adaptar ao software, e não o contrário.</p>
</li>
<li>
<p>Adquirir uma solução open source. Baixar da internet, configurar e usar, ou contratar uma empresa de serviços que faça esse trabalho por você. A vantagem óbvia é a ausência de custo de licença. A desvantagem é que administrar esse tipo de configuração tem um custo que normalmente ninguém leva em conta: o <a href="http://en.wikipedia.org/wiki/Total_cost_of_ownership"target="_blank" rel="noopener">TCO</a> não é trivial. A facilidade de modificar a aplicação varia de caso a caso.</p>
</li>
<li>
<p>Fazer o sistema in house. Programar o sistema você mesmo, customizado às suas necessidades, com uma equipe própria ou uma software house contratada. É o modelo alfaiate. Costuma ser mais caro, mas pode ser necessário se não existem softwares comerciais que façam o que você precisa. Na teoria seria o software que mais se adequa à empresa; na prática o TCO costuma ser alto e o resultado, incerto. Tudo depende da maturidade da empresa e de quem foi contratado para desenvolver. Sua milhagem vai variar bastante.</p>
</li>
<li>
<p>Usar um ASP. Você &lsquo;aluga&rsquo; uma aplicação de mercado, como o SalesForce.com, sem se preocupar com deployment, instalação e atualizações. É menos customizável, mas o custo total de propriedade costuma ser bem menor. Em muitos casos faz sentido: basta uma conexão estável à internet. O modelo de serviços é tendência porque a infra-estrutura atual da internet permite. E há um ganho claro para o desenvolvedor: uma correção no sistema central beneficia toda a base de clientes instantaneamente, sem instalar nada cliente por cliente.</p>
</li>
</ul>
<p>Sobre opções de hardware:</p>
<ul>
<li>
<p>Fazer o hosting você mesmo. Comprar máquinas, montar redes, administrar tudo dentro do seu perímetro físico. À medida que a empresa cresce, você compra mais máquinas. Funciona melhor para intranets e para empresas com um setor de TI maduro. TI normalmente é tratado como custo, como pagar impostos; cuidar de tudo internamente só faz sentido se seu core business são esses sistemas.</p>
</li>
<li>
<p>Fazer o hosting você mesmo, mas entendendo que crescimento não é linear. Às vezes você precisa de mais recursos apenas por um período curto, e comprar máquinas o tempo todo só infla o TCO. Para isso existem servidores &lsquo;on-demand&rsquo;, como algumas linhas da IBM: quando precisa de mais potência, você liga para a IBM e ela libera mais processadores na máquina que fica fisicamente no seu local. É uma solução limitada.</p>
</li>
<li>
<p>Fazer co-location. As máquinas são suas, mas ficam na infra-estrutura de terceiros, num CPD fora do perímetro da empresa. É um jogo de custo-benefício: você raramente tem a expertise de manter uma infra-estrutura tão estável quanto a de um terceiro, mas as máquinas continuam sendo sua responsabilidade. Serve para quem tem recursos para máquinas mas não tem espaço físico, ou para quem tem filiais que precisam acessar o mesmo sistema. O CPD mantém tudo ligado o tempo todo, com rede estável e suporte 24×7.</p>
</li>
<li>
<p>Usar <em>shared hosting</em>. Você aluga um pedaço de uma máquina compartilhada com outros clientes. O custo inicial é mínimo, mas você sofre quando outro cliente da mesma máquina demanda mais recursos, além de problemas como bibliotecas de sistema compartilhadas. É um balaio de gato que os provedores tentam melhorar, mas a natureza desse tipo de pacote impede algo à prova de balas. Crescer num ambiente desses é limitado. Favorece freelancers e pequenas empresas que precisam colocar um site no ar sem recursos para configurações parrudas.</p>
</li>
<li>
<p>Usar <em>VPS</em> (Virtual Private Server). Você ainda divide a máquina com outros clientes, mas tem a sensação de uma máquina inteira só sua, virtual e isolada. É um &lsquo;shared co-location&rsquo;: a máquina é alugada em slices, com soluções como Xen ou VMWare, e sai mais barato que co-location. O isolamento protege sua aplicação das atualizações dos vizinhos. É o segundo passo para quem quer custo baixo com mais flexibilidade. O problema continua quando você precisa de mais slices só por um período curto: colocar um slice no ar leva tempo, e você pode perder o time-to-market.</p>
</li>
</ul>
<h2>Cloud Computing<span class="hx:absolute hx:-mt-20" id="cloud-computing"></span>
    <a href="#cloud-computing" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Entendidos os termos usados por aí, vamos ao termo da moda. Não sei se foi o primeiro, mas com certeza o que tem feito mais barulho é o modelo da <a href="http://www.amazon.com/aws"target="_blank" rel="noopener">Amazon Web Services</a>: Elastic Compute Cloud (EC2), Simple Storage Service (S3) e SimpleDB.</p>
<p>O principal chamariz é o conceito de <strong>elastic</strong> on-demand. Você paga por quanto usa, sem precisar reconfigurar tudo manualmente a cada mudança. Usou mais, paga mais; voltou a usar menos, paga menos. É pagamento por uso de recursos, em vez de mensalidade fixa por recursos fixos.</p>
<p>Os produtos da Amazon cobrem três serviços:</p>
<ul>
<li>
<p>Armazenamento (Amazon S3). Num modelo tradicional você compraria ou alugaria um servidor de arquivos. O S3 criou um modelo diferente de file system, baseado no conceito de &lsquo;buckets&rsquo;. As vantagens são preço e estabilidade; a desvantagem é que esse modelo não é mapeável de imediato para um file system comum. Hoje já existem dezenas de bibliotecas em diferentes linguagens que facilitam a transição.</p>
</li>
<li>
<p>Banco de dados (Amazon SimpleDB). Em vez de um Oracle ou MySQL relacional, a Amazon oferece um banco de dados não-estruturado baseado em documentos. Sem schemas fixos, some a necessidade de administração custosa sobre seu domínio de dados, e a manutenção diminui. Por outro lado, ainda quase não existem frameworks ou bibliotecas que migrem trivialmente uma aplicação relacional (SQL) para o modelo do SimpleDB. De novo, a vantagem pode ser o preço.</p>
</li>
<li>
<p>VPS (Amazon EC2). São VPS dinâmicas, elásticas. Se o seu sistema requer mais recursos num pico de visitas e transações, você habilita mais slices dinamicamente e os derruba quando não precisa mais. De manhã podem ser 5 slices, à tarde 20, à noite 5 de novo. A pegadinha: não serão necessariamente os mesmos 5 slices. O modelo garante slices, mas não quais. Sua aplicação precisa armazenar os dados fora do EC2, no S3 ou no SimpleDB, para que outro slice assuma sem depender de dados locais. É Shared-Nothing levado às últimas consequências.</p>
</li>
</ul>
<p>Qual a vantagem? Você tem o que precisa, quando precisa. Em vez de se comprometer com planos caros de co-location ou VPS, você deixa a aplicação aos cuidados da Amazon e ganha dinamicidade sem se preocupar se suas máquinas &lsquo;vão aguentar&rsquo;. É como pagar academia pelas horas que você efetivamente frequentou, em vez de uma mensalidade fixa. Parou no meio do mês, não perde a mensalidade inteira. É uma tendência no mercado de serviços.</p>
<p>Qual a desvantagem? No caso da Amazon, você precisa preparar sua aplicação segundo as limitações e os requerimentos deles. Principalmente porque é normal seu slice ser derrubado: os dados locais se perdem se não forem persistidos fora antes. Levado em conta como premissa desde o início, isso deixa de ser problema. É um paradigma diferente do co-location ou da VPS clássica, onde os dados locais são permanentes e o backup é responsabilidade do hosting.</p>
<h3>Google App Engine<span class="hx:absolute hx:-mt-20" id="google-app-engine"></span>
    <a href="#google-app-engine" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Chegamos à novidade da semana. Na prática, o App Engine mapeia um a um os serviços da Amazon:</p>
<ul>
<li>
<p>Amazon SimpleDB vs Google BigTable: nenhum dos dois é banco de dados relacional; esqueça o RDBMS clássico.</p>
</li>
<li>
<p>Amazon S3 vs Google GFS: de novo, nada de file system clássico como o da sua máquina.</p>
</li>
<li>
<p>Amazon EC2 vs App Engine: parecido, mas diferente, como veremos a seguir.</p>
</li>
</ul>
<p>O marketing do Google vendeu o produto como <em>&ldquo;sua aplicação rodando dentro da nossa infra-estrutura&rdquo;</em>. Todos sabemos que Google Search, Reader, Gmail e Orkut rodam numa infra-estrutura proprietária altamente escalável, que tornou o Google famoso e agora está &lsquo;aberta&rsquo; (<a href="http://en.wikipedia.org/wiki/Gratis_versus_libre"target="_blank" rel="noopener">grátis, mas não libre</a>) para que outros usufruam. Vejamos vantagens e desvantagens:</p>
<ul>
<li>
<p>A desvantagem dos dois lados é a quebra de paradigmas: as aplicações precisam nascer exclusivas para um desses ambientes. Escolher um significa ficar fechado nele, porque suportar os dois custa mais desenvolvimento.</p>
</li>
<li>
<p>A vantagem da Amazon é a independência dos serviços: quer apenas storage? Use o S3 sozinho. A desvantagem é que eles exigem mais conhecimento de integração que qualquer outro.</p>
</li>
<li>
<p>A desvantagem do Google é a proposição tudo-ou-nada: sua aplicação fica toda lá dentro, não apenas pedaços. A vantagem é o ambiente online de administração, que coloca o Google um passo à frente da Amazon em usabilidade.</p>
</li>
</ul>
<p>Neste momento o Google só suporta aplicações escritas em Python. Já vem com Django pré-disponível e com APIs que facilitam o manuseio de dados e storage. Com certeza uma grande notícia para a comunidade Python.</p>
<p>Isso gerou certa <a href="http://web.archive.org/web/20090108161617/http://profy.com/2008/04/08/google-jumps-shark-with-app-engine/"target="_blank" rel="noopener">comoção</a> entre desenvolvedores de outras linguagens, mas isso é normal. Se não fosse o Google, passaria despercebido como &ldquo;mais um provedor de serviços&rdquo;. Sendo o Google, eles sabem que sempre receberão elogios e críticas massivas. Falar bem é democrático, falar mal também. Se eles não previram isso, alguém do departamento de Public Relations precisa ser demitido.</p>
<p>De qualquer forma, não deem ouvidos a mais essas guerrinhas de linguagens:</p>
<ul>
<li>
<p>elas são fogo de palha: só divertem agora</p>
</li>
<li>
<p>elas não representam muita coisa: se o Google previu isso e tem um bom PR, sai ileso</p>
</li>
<li>
<p>o Google gosta de Python: isso é público e notório, e não há problema nenhum nisso. Empresas decidem o que querem, quando querem e como querem. O dia que vocês tiverem suas empresas, vão entender isso.</p>
</li>
<li>
<p>e o Google nunca disse que o App Engine seria exclusivo para Python</p>
</li>
</ul>
<p>No <a href="http://www.google.com/intl/en/press/annc/20080407_app_engine.html"target="_blank" rel="noopener">anúncio oficial</a> o Google apresenta o serviço como &ldquo;preview release&rdquo;, o famoso Beta de toda aplicação Web 2.0 que se preze. Grande novidade :-) A promessa é suportar outras linguagens no futuro. Não é uma solução perfeita, mas, como tudo que vem de uma corporação do tamanho do Google, tem grandes potenciais. E, como toda aplicação Web 2.0, tem uma conta gratuita para você fazer o test-drive do ambiente antes de decidir por um upgrade de planos. Neste momento de Beta, porém, ela ainda não é liberada para todo mundo.</p>
<h2>O que isso representa?<span class="hx:absolute hx:-mt-20" id="o-que-isso-representa"></span>
    <a href="#o-que-isso-representa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando o App Engine evoluir e passar a suportar mais ambientes de desenvolvimento além de Python, ele pode se tornar a principal ameaça aos Web Services da Amazon: é uma cópia descarada dos serviços deles, e o concorrente comercial óbvio é a Amazon.</p>
<p>Também pode se tornar uma ameaça a serviços menores, como o <a href="http://web.archive.org/web/20081228161600/http://www.joyent.com:80/accelerator"target="_blank" rel="noopener">Joyent Accelerator</a>, um hosting que implementa o mesmo conceito de recursos elásticos, ou o recém-chegado <a href="http://heroku.com/"target="_blank" rel="noopener">Heroku</a>, que no fundo é um App Engine para Rails: uma ponte sobre os serviços da Amazon, configurada de uma maneira mais simples para desenvolvedores de Ruby on Rails. Claro, ele ainda não está pronto e não tem nada perto da escala do Google.</p>
<p>Nem os serviços do Google e nem os da Amazon são perfeitos. Aliás, falei algo óbvio: nenhum serviço é perfeito. Tudo depende dos seus requerimentos e de como você quer se comprometer. A regra continua a mesma: quanto mais compromisso, mais barato no curto prazo e mais caro no longo; quanto menos compromisso, mais caro agora e, talvez, melhor no futuro.</p>
<p>Resumindo: quer algo barato e flexível? Amazon e Google podem ajudar. Mas, com sorte, se um dia você crescer e quiser ficar independente, desvincular pode ser difícil, porque sua aplicação ficou intimamente amarrada aos serviços deles. Com um serviço mais tradicional, como co-location ou VPS, você paga mais agora, mas continua isolado de terceiros e a migração futura para outros hostings sai mais barata.</p>
<p>Se o App Engine for bem sucedido, o efeito provável é pressionar o mercado de hostings a oferecer serviços mais criativos, mais baratos, com mais qualidade. É a lei de mercado: concorrência gera benefícios para nós, consumidores. Eles que se matem enquanto nós assistimos. A curto prazo, para quem quer colocar uma aplicação no ar, é apenas mais uma variável a considerar.</p>
<p>Qual o objetivo do Google nisso? Não sei se é o mesmo dos hostings: ganhar dinheiro terceirizando infra-estrutura num mercado de commodities. O Google se especializou em transformar bons produtos em commodities, o que é bom e ruim ao mesmo tempo, dependendo do seu ponto de vista. Se for um serviço grátis, um dos objetivos é facilitar que mais e mais websites apareçam: clientes potenciais para o verdadeiro core business do Google, o AdSense.</p>
<p>Claro que eu não faria hosting do meu equivalente-Twitter num serviço gratuito. Aliás, se seus dados são importantes para você, <strong>nunca</strong> use serviços gratuitos. Eles são bons enquanto funcionam; quando não funcionam e você toma um prejuízo, o problema é única e exclusivamente seu. É como andar de carro sem fazer seguro.</p>
<p>(off topic do off topic: eu uso Gmail, mas todo e-mail que cai no inbox tem um forward automático para meu e-mail pago no Apple .Mac, todo e-mail do .Mac cai na minha máquina, e minha máquina tem dois backups redundantes. Apenas para exemplificar. Eu gosto do Gmail, mas não apostaria sequer meus e-mails nisso.)</p>
<h2>Entendendo o Google<span class="hx:absolute hx:-mt-20" id="entendendo-o-google"></span>
    <a href="#entendendo-o-google" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ao analisar um movimento do Google, nunca pense em &lsquo;altruísmo&rsquo; ou bobagens do tipo. Eles são uma empresa de capital aberto cujo objetivo principal é deixar os acionistas felizes. Novamente: o negócio deles é lucro, e caridade não paga dividendo.</p>
<p>Por acaso o core business deles envolve atingir pessoas que ficam felizes ao ouvir frases-feitas como <em>&ldquo;Don&rsquo;t be evil&rdquo;</em>. Ótimo para nós, claro, mas isso também gera conflito de interesses.</p>
<p>(off topic do off topic, sem ingenuidade: <a href="http://mashable.com/2008/04/10/orkut-pedophilia/"target="_blank" rel="noopener">não fazer o mal</a> é vago demais, porque &lsquo;bem&rsquo; e &lsquo;mal&rsquo; são conceitos relativos. O que é bom pra mim pode ser ruim para alguém, e vice-versa; bem e mal mudam conforme o local e o tempo. Ninguém nunca vai dizer &lsquo;faça o mal&rsquo;, óbvio, não seria muito esperto. &lsquo;Fazer o bem&rsquo;, ou pelo menos o que a maioria percebe hoje como &lsquo;bem&rsquo;, é uma maneira muito inteligente de fazer publicidade positiva junto à opinião pública. Além de barata, essa publicidade pode render isenção de impostos, o que para empresas grandes é uma economia considerável. A Lei Rouanet é um exemplo disso: ambos ganham, mas sem leis desse tipo não haveria muita motivação para uma empresa &lsquo;investir&rsquo; fora de seu core business. Bah, what do I know. E, claro, eu gosto do Google também; até agora faz parte dos meus interesses.)</p>
<p>Só para entender melhor, leia <a href="http://web.archive.org/web/20081221135058/http://www.wired.com:80/wired/archive/11.01/google_pr.html"target="_blank" rel="noopener">isto</a>. Parte interessante:</p>
<blockquote>
  <p>&ldquo;Evil,&rdquo; says Google CEO Eric Schmidt, &ldquo;is what Sergey says is evil.&rdquo;</p>

</blockquote>
<p>Wired: <em>&ldquo;Comprometer princípios morais é simplesmente o custo de se fazer grandes negócios.&rdquo;</em></p>
<p>O App Engine é mais um canal para o AdSense, assim como um Android, um Gmail. Uma das razões de existir uma empresa como a Mozilla é a linha de receita que vem do Google para que ela traga no seu toolbar uma procura default para o Google, claro. A Apple também ganha para ter o campo de search do Google no Safari. Não chega a ser &lsquo;venda-casada&rsquo;, porque ambos os browsers são de graça, mas o custo é indireto: interessa aos anunciantes do AdSense aparecer na maioria dos browsers. O Yahoo! também anunciou um <a href="http://web.archive.org/web/20080516124707/http://afp.google.com:80/article/ALeqM5jEgHRf-Zi_z785TetNNwtg2V5a9w"target="_blank" rel="noopener">teste para exibir anúncios do AdSense</a> nos próprios resultados de busca, o que pode aumentar ainda mais o market share do Google.</p>
<p>Isso significa visitas indiretas que geram os bilhões de receita do Google e agradam seus acionistas. E os usuários geeks, que contribuem com a propaganda gratuita do boca-a-boca, obviamente gostam do pensamento de que apoiar um Google ou um Firefox significa <em>&lsquo;a Microsoft perder&rsquo;</em>. Eu posso estar sendo pessimista, mas aconteceu com Rockefeller, um grande filantropo, e sua Standard Oil. Aconteceu com Bill Gates, outro grande filantropo, e sua Microsoft. Pode acontecer com um Google. Para se ter uma idéia, leiam <a href="http://mashable.com/2008/04/10/orkut-pedophilia/"target="_blank" rel="noopener">este artigo</a> e um trecho interessante:</p>
<blockquote>
  <p>Blogger and Mashable reader Constantine von Hoffman at Collateral Damage probably put it best when he said: &ldquo;At this point, it would take a mashup of Wittgenstein, Quantum mechanics and LSD to make sense of Google&rsquo;s various explanations for what it will and won&rsquo;t censor and why.&rdquo;</p>

</blockquote>
<p>Enfim, o Google App Engine, por si só, ainda não representa revolução nenhuma: é apenas mais um player no mercado de hosting. Cloud Computing é mais um termo de marketing para uma solução derivada de outras que já existiam: a granularidade de preços nos serviços. Assim como fez com o Android, o Google está commoditizando mais um segmento de mercado.</p>
<p>A regra de sempre continua valendo: <a href="http://en.wikipedia.org/wiki/Cost-benefit_analysis"target="_blank" rel="noopener">análise de custo x benefício</a>. E esse tipo de análise não é receita-de-bolo e nem vem com o clique de um botão.</p>
]]></content:encoded><category>cloud</category><category>arquitetura-de-software</category><category>off-topic</category></item></channel></rss>