Blog do Fernando Jorge Mota https://fjorgemota.com/ Dicas e ferramentas para facilitar o seu dia-a-dia como desenvolvedor Sun, 07 Jul 2024 23:23:24 +0000 pt-BR hourly 1 https://wordpress.org/?v=6.9.1 71563333 Docker Networks - Ou como configurar PHP-FPM+MySQL no Docker com o Nginx externo https://fjorgemota.com/2024/07/07/docker-networks-ou-como-configurar-php-fpmmysql-no-docker-com-o-nginx-externo/ https://fjorgemota.com/2024/07/07/docker-networks-ou-como-configurar-php-fpmmysql-no-docker-com-o-nginx-externo/#comments Sun, 07 Jul 2024 23:03:27 +0000 https://fjorgemota.com/?p=5436

Quando eu postei o tutorial completo sobre Docker, muitos anos atrás, algumas pessoas comentaram pedindo mais detalhes a respeito desse trecho aqui que comentei no post:

Esse blog que vos fala, por exemplo, está rodando sob dois containers do Docker, um para o php-fpm, outro para o MySQL. Onde está o Nginx? Está fora, instalado na máquina principal, e aí o Nginx se conecta com o php-fpm para poder renderizar essa página da forma que você vê.

Bom, depois de.. *check notes*, mais de OITO anos, chegou a hora de eu detalhar como funciona isso, afinal, antes tarde do que mais tarde, certo? Então vamos lá.

O Docker, como vocês bem sabem se vocês leram o post referido acima, consegue isolar os processos em containers que compartilham o mesmo kernel, mas cada processo deve ser um container próprio, isolado. Apesar disso, é possível estabelecer um setup onde os containers comunicam entre si, e também com o mundo exterior.

No caso desse blog que vos fala, conforme referido no trecho citado acima, temos três peças (2 rodando em containers) se comunicando entre si:

  1. Nginx - Que é instalado na máquina principal, e fica escutando na porta 80 e 443, e por onde a maior parte dos usuários se conecta
  2. MySQL - Que escuta na clássica porta 3306, mas que NÃO é exposta para fora de container algum, em nenhuma porta. Ficando mais "isolado", por assim dizer.
  3. PHP-FPM - Que escuta na porta 9000 dentro do container, mas é exposta apenas localmente (e isso é importante!) dentro da máquina como porta 2004. A ideia é que o Nginx consiga usar a porta 2004 rodando no host 127.0.0.1 (ou seja, o melhor host, o localhost!) para se comunicar com o PHP-FPM.

Note que em nenhum momento eu mencionei que há um link (que usando o legacy linking system seria algo tipo -l mysql:mysql) entre o PHP-FPM e o MySQL. Mas, como você pode imaginar, por causa do WordPress tal conexão se faz necessária, caso contrário esse blog não funcionaria pois o PHP nunca conseguiria se comunicar com o MySQL (afinal, os containers são isolados, certo?).

Para fazer isso, usamos o que o Docker chama de "Docker User-Defined Network", ou Rede Docker definida pelo usuário, em tradução livre. O que é isso? Imagine que cada container Docker - apesar de rodar um único processo cada - é como se fosse uma rede de computadores. Com base no que sabemos até então, temos a seguinte situação:

Diagrama mostrando você se conectando ao Nginx, que por sua vez se comunica com o PHP-FPM, mas sem se comunicar com o MySQL

Um rede definida pelo usuário, no Docker, nada mais é do que uma rede DENTRO do próprio Docker. No caso desse blog, temos uma rede no Docker chamada fjorgemota-com, e a estrutura fica mais ou menos assim:

Diagrama mostrando você se conectando ao Nginx, que por sua vez se comunica com o PHP-FPM e este se comunica com o MySQL

O que que isso implica, necessariamente? Implica que, assim como no caso da rede LOCAL na sua casa, onde seu celular consegue se comunicar com seu computador e vice-versa, cada container DENTRO da rede fjorgemota-com também consegue se comunicar um com o outro. Porém, da mesma forma que alguém externo à rede local da sua casa NÃO consegue se conectar diretamente ao seu computador ou ao seu celular, um container que esteja FORA da rede fjorgemota-com também não consegue se comunicar nem com o MySQL, e nem com o PHP.

Entretanto, assim como você PODE expor o seu computador conectado na rede interna da sua casa para o mundo exterior, o fato de um container estar dentro de uma rede definida por usuário também não o impossibilita de expor portas da mesma forma, como no caso do container do PHP-FPM cuja porta 9000 é exposta como 2004 dentro do servidor.

Tutorial

Como a configuração do Nginx pode variar DEMAIS de máquina para máquina, vamos fazer uma configuração mais simples aqui, usando o Caddy, que é bem mais simples de configurar (bastando um simples arquivo de configuração!). Vamos ao tutorial:

1) Baixe e instale o Docker (importante ter uma máquina de 64 bits, pois é o único ambiente no qual o Docker efetivamente roda..)

2) Baixe e instale o Caddy para sua máquina (basta clicar no botão de Download na página, você não precisa de nenhum plugin extra)

3) Crie uma rede definida por usuário, usando docker network create tutorial-fjorgemota-com. A saída desse comando deve ser um simples hash, conforme mostrado abaixo:

https://asciinema.org/a/yOfuP5h3XHzoRq6matGusmWqA

4) Agora, vamos criar os containers! Primeiro, inicialize o MySQL usando docker run --name bd-tutorial-fjorgemota-com --network tutorial-fjorgemota-com --network-alias mysql -e MYSQL_DATABASE=tutorial_blog -e MYSQL_ROOT_PASSWORD=12345678 -d mysql. A saida do Docker deve ser algo assim:

https://asciinema.org/a/yQcI4KaMU00CMl4lLFqQQ0btk

5) Baixe o WordPress e descompacte-o para uma pasta chamada wordpress. No Linux, você pode usar wget -O wordpress.zip https://br.wordpress.org/latest-pt_BR.zip && unzip wordpress.zip, conforme mostra a saída abaixo:

https://asciinema.org/a/t8wxbPWqCqHxM8pw9KZesSncC

7) Crie um arquivo chamado Dockerfile para preparar a imagem do PHP-FPM para ter as extensões necessárias para rodar o WordPress. O arquivo deve ter o seguinte conteúdo:

FROM php:8.3-fpm 
RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libxpm-dev \
    libwebp-dev \
    libxslt1-dev \
    libzip-dev \
    libonig-dev \
    libtidy-dev \
    libicu-dev \
    imagemagick \
    libmagickwand-dev \
    less \
    && pecl install imagick \
    && pecl install apcu \
    && docker-php-ext-configure gd --with-freetype --with-jpeg --with-xpm --with-webp \
    && docker-php-ext-install mbstring mysqli zip bcmath exif xsl intl pdo_mysql gd tidy \
    && docker-php-ext-enable imagick apcu \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

Para contextualização, aqui estamos apenas algumas dependêcnias necessárias para a execução do WordPress, uma vez que a imagem php:8.3-fpm vem sem as extensões necessárias instaladas/ativadas.

8) Construa a imagem acima usando docker build -t tutorial-php-fjorgemota-com .

O comando deve mostrar algo assim:

https://asciinema.org/a/hBFE89RMUvwbOcEGicBKUKMI6

9) Rode o container do PHP-FPM usando essa pasta do wordpress como volume, e mapeando para /wordpress. Sem entrar na pasta wordpress, criada acima, o comando fica assim docker run -v $(pwd)/wordpress:$(pwd)/wordpress --network tutorial-fjorgemota-com -p 127.0.0.1:2004:9000 -d tutorial-php-fjorgemota-com

Esse comando deve ter um simples hash como saída, conforme mostrado abaixo:

https://asciinema.org/a/LqEjOkCw191JSKqctixl4Kbpj

Com isso, você deve ter o container rodando PHP-FPM rodando na mesma rede do container do MySQL. E o PHP-FPM deve estar exposto na porta 2004 no seu computador.

7) Agora, vamos configurar o Caddy para se conectar no PHP-FPM na sua máquina. Na mesma pasta onde está a pasta wordpress, crie um arquivo chamado Caddyfile com o seguinte conteúdo:

:8080 {
    root * wordpress
    encode gzip
    php_fastcgi 127.0.0.1:2004
    file_server
}

8) Então, execute caddy run Caddyfile

Se tudo deu certo, você deve ver algo assim:

[fernando@fernando-ryzen ~]$ caddy run
2024/07/07 22:39:08.186	INFO	using adjacent Caddyfile
2024/07/07 22:39:08.187	WARN	Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies	{"adapter": "caddyfile", "file": "Caddyfile", "line": 2}
2024/07/07 22:39:08.188	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//127.0.0.1:2019", "//localhost:2019", "//[::1]:2019"]}
2024/07/07 22:39:08.188	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc000429880"}
2024/07/07 22:39:08.188	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2024/07/07 22:39:08.188	INFO	autosaved config (load with --resume flag)	{"file": "/home/fernando/.local/share/caddy/autosave.json"}
2024/07/07 22:39:08.188	INFO	serving initial configuration
2024/07/07 22:39:08.189	WARN	tls	storage cleaning happened too recently; skipping for now	{"storage": "FileStorage:/home/fernando/.local/share/caddy", "instance": "cf543b53-950d-435f-8cfc-59ea67aff4c5", "try_again": "2024/07/08 22:39:08.189", "try_again_in": 86399.99999981}
2024/07/07 22:39:08.189	INFO	tls	finished cleaning storage units

9) Acesse http://127.0.0.1:8080 no seu navegador. Você verá a tela de instalação do WordPress:

Clique em "Vamos lá!"

10) Nessa tela de configuração do banco de dados:

Informe os seguintes dados:

  • Nome do banco de dados: tutorial_blog
  • Nome de usuário: root
  • Senha: 12345678
  • Servidor do banco de dados: mysql
  • Prefixo da tabela: wp_

Clique em "Enviar"

11) O WordPress te pedirá para criar um arquivo wp-config.php com o código que ele passar:

Entre na pasta wordpress e crie o arquivo wp-config.php com aquele conteúdo informado.

Clique em "Instalar"

12) O WordPress te pedirá mais algumas informações, agora a respeito do site. Preencha com os dados que bem entender:

Clique em "Instalar WordPress"

13) O WordPress estará instalado:

Agora você tem o Caddy conectando ao PHP-FPM rodando dentro de um container Docker, e se comunicando com o MySQL em outro container Docker dentro da mesma rede.

Conclusão

Como dá pra ver, mesmo com o uso de containers, não há a necessidade de se preocupar em colocar TUDO dentro do Docker. É possível ter uma certa flexibilidade, ainda mais em termos de serviços de rede (como o MySQL ou o PHP-FPM).

Naturalmente, entretanto, ter mais containers rodando no Docker tende a facilitar mais a vida em alguns cenários. Em ambientes de desenvolvimento, por exemplo, quando você usa algo como o Docker Compose, todo o processo de instalação e configuração de um projeto se resume a alguns poucos comandos. Não há a necessidade de, por exemplo, pedir pela instalação de softwares adicionais (como o Caddy, no caso desse post), nem de configurações extras.

Portanto, como tudo na vida, decidir como estruturar sua aplicação é uma questão de custo benefício. No meu caso, o blog roda dessa forma (só que usando Nginx em vez do Caddy) pois isso me facilita a instalação e configuração de outros projetos, além do blog. Por exemplo, posso usar o Gitea (que é uma alternativa mais moderna ao Gogs, sobre o qual já falei aqui no blog), o Dokku e outros serviços tendo apenas um servidor web como "porta principal" do servidor. Logo, acaba valendo mais a pena ter tudo centralizado em uma única configuração do Nginx.

E você? Como gosta de estruturar os seus projetos? Compartilhe nos comentários!

]]>
Quando eu postei o tutorial completo sobre Docker, muitos anos atrás, algumas pessoas comentaram pedindo mais detalhes a respeito desse trecho aqui que comentei no post:

Esse blog que vos fala, por exemplo, está rodando sob dois containers do Docker, um para o php-fpm, outro para o MySQL. Onde está o Nginx? Está fora, instalado na máquina principal, e aí o Nginx se conecta com o php-fpm para poder renderizar essa página da forma que você vê.

Bom, depois de.. *check notes*, mais de OITO anos, chegou a hora de eu detalhar como funciona isso, afinal, antes tarde do que mais tarde, certo? Então vamos lá.

O Docker, como vocês bem sabem se vocês leram o post referido acima, consegue isolar os processos em containers que compartilham o mesmo kernel, mas cada processo deve ser um container próprio, isolado. Apesar disso, é possível estabelecer um setup onde os containers comunicam entre si, e também com o mundo exterior.

No caso desse blog que vos fala, conforme referido no trecho citado acima, temos três peças (2 rodando em containers) se comunicando entre si:

  1. Nginx - Que é instalado na máquina principal, e fica escutando na porta 80 e 443, e por onde a maior parte dos usuários se conecta
  2. MySQL - Que escuta na clássica porta 3306, mas que NÃO é exposta para fora de container algum, em nenhuma porta. Ficando mais "isolado", por assim dizer.
  3. PHP-FPM - Que escuta na porta 9000 dentro do container, mas é exposta apenas localmente (e isso é importante!) dentro da máquina como porta 2004. A ideia é que o Nginx consiga usar a porta 2004 rodando no host 127.0.0.1 (ou seja, o melhor host, o localhost!) para se comunicar com o PHP-FPM.

Note que em nenhum momento eu mencionei que há um link (que usando o legacy linking system seria algo tipo -l mysql:mysql) entre o PHP-FPM e o MySQL. Mas, como você pode imaginar, por causa do WordPress tal conexão se faz necessária, caso contrário esse blog não funcionaria pois o PHP nunca conseguiria se comunicar com o MySQL (afinal, os containers são isolados, certo?).

Para fazer isso, usamos o que o Docker chama de "Docker User-Defined Network", ou Rede Docker definida pelo usuário, em tradução livre. O que é isso? Imagine que cada container Docker - apesar de rodar um único processo cada - é como se fosse uma rede de computadores. Com base no que sabemos até então, temos a seguinte situação:

Diagrama mostrando você se conectando ao Nginx, que por sua vez se comunica com o PHP-FPM, mas sem se comunicar com o MySQL

Um rede definida pelo usuário, no Docker, nada mais é do que uma rede DENTRO do próprio Docker. No caso desse blog, temos uma rede no Docker chamada fjorgemota-com, e a estrutura fica mais ou menos assim:

Diagrama mostrando você se conectando ao Nginx, que por sua vez se comunica com o PHP-FPM e este se comunica com o MySQL

O que que isso implica, necessariamente? Implica que, assim como no caso da rede LOCAL na sua casa, onde seu celular consegue se comunicar com seu computador e vice-versa, cada container DENTRO da rede fjorgemota-com também consegue se comunicar um com o outro. Porém, da mesma forma que alguém externo à rede local da sua casa NÃO consegue se conectar diretamente ao seu computador ou ao seu celular, um container que esteja FORA da rede fjorgemota-com também não consegue se comunicar nem com o MySQL, e nem com o PHP.

Entretanto, assim como você PODE expor o seu computador conectado na rede interna da sua casa para o mundo exterior, o fato de um container estar dentro de uma rede definida por usuário também não o impossibilita de expor portas da mesma forma, como no caso do container do PHP-FPM cuja porta 9000 é exposta como 2004 dentro do servidor.

Tutorial

Como a configuração do Nginx pode variar DEMAIS de máquina para máquina, vamos fazer uma configuração mais simples aqui, usando o Caddy, que é bem mais simples de configurar (bastando um simples arquivo de configuração!). Vamos ao tutorial:

1) Baixe e instale o Docker (importante ter uma máquina de 64 bits, pois é o único ambiente no qual o Docker efetivamente roda..)

2) Baixe e instale o Caddy para sua máquina (basta clicar no botão de Download na página, você não precisa de nenhum plugin extra)

3) Crie uma rede definida por usuário, usando docker network create tutorial-fjorgemota-com. A saída desse comando deve ser um simples hash, conforme mostrado abaixo:

4) Agora, vamos criar os containers! Primeiro, inicialize o MySQL usando docker run --name bd-tutorial-fjorgemota-com --network tutorial-fjorgemota-com --network-alias mysql -e MYSQL_DATABASE=tutorial_blog -e MYSQL_ROOT_PASSWORD=12345678 -d mysql. A saida do Docker deve ser algo assim:

5) Baixe o WordPress e descompacte-o para uma pasta chamada wordpress. No Linux, você pode usar wget -O wordpress.zip https://br.wordpress.org/latest-pt_BR.zip && unzip wordpress.zip, conforme mostra a saída abaixo:

7) Crie um arquivo chamado Dockerfile para preparar a imagem do PHP-FPM para ter as extensões necessárias para rodar o WordPress. O arquivo deve ter o seguinte conteúdo:

FROM php:8.3-fpm 
RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libxpm-dev \
    libwebp-dev \
    libxslt1-dev \
    libzip-dev \
    libonig-dev \
    libtidy-dev \
    libicu-dev \
    imagemagick \
    libmagickwand-dev \
    less \
    && pecl install imagick \
    && pecl install apcu \
    && docker-php-ext-configure gd --with-freetype --with-jpeg --with-xpm --with-webp \
    && docker-php-ext-install mbstring mysqli zip bcmath exif xsl intl pdo_mysql gd tidy \
    && docker-php-ext-enable imagick apcu \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

Para contextualização, aqui estamos apenas algumas dependêcnias necessárias para a execução do WordPress, uma vez que a imagem php:8.3-fpm vem sem as extensões necessárias instaladas/ativadas.

8) Construa a imagem acima usando docker build -t tutorial-php-fjorgemota-com .

O comando deve mostrar algo assim:

9) Rode o container do PHP-FPM usando essa pasta do wordpress como volume, e mapeando para /wordpress. Sem entrar na pasta wordpress, criada acima, o comando fica assim docker run -v $(pwd)/wordpress:$(pwd)/wordpress --network tutorial-fjorgemota-com -p 127.0.0.1:2004:9000 -d tutorial-php-fjorgemota-com

Esse comando deve ter um simples hash como saída, conforme mostrado abaixo:

Com isso, você deve ter o container rodando PHP-FPM rodando na mesma rede do container do MySQL. E o PHP-FPM deve estar exposto na porta 2004 no seu computador.

7) Agora, vamos configurar o Caddy para se conectar no PHP-FPM na sua máquina. Na mesma pasta onde está a pasta wordpress, crie um arquivo chamado Caddyfile com o seguinte conteúdo:

:8080 {
    root * wordpress
    encode gzip
    php_fastcgi 127.0.0.1:2004
    file_server
}

8) Então, execute caddy run Caddyfile

Se tudo deu certo, você deve ver algo assim:

[fernando@fernando-ryzen ~]$ caddy run
2024/07/07 22:39:08.186	INFO	using adjacent Caddyfile
2024/07/07 22:39:08.187	WARN	Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies	{"adapter": "caddyfile", "file": "Caddyfile", "line": 2}
2024/07/07 22:39:08.188	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//127.0.0.1:2019", "//localhost:2019", "//[::1]:2019"]}
2024/07/07 22:39:08.188	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc000429880"}
2024/07/07 22:39:08.188	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2024/07/07 22:39:08.188	INFO	autosaved config (load with --resume flag)	{"file": "/home/fernando/.local/share/caddy/autosave.json"}
2024/07/07 22:39:08.188	INFO	serving initial configuration
2024/07/07 22:39:08.189	WARN	tls	storage cleaning happened too recently; skipping for now	{"storage": "FileStorage:/home/fernando/.local/share/caddy", "instance": "cf543b53-950d-435f-8cfc-59ea67aff4c5", "try_again": "2024/07/08 22:39:08.189", "try_again_in": 86399.99999981}
2024/07/07 22:39:08.189	INFO	tls	finished cleaning storage units

9) Acesse http://127.0.0.1:8080 no seu navegador. Você verá a tela de instalação do WordPress:

Clique em "Vamos lá!"

10) Nessa tela de configuração do banco de dados:

Informe os seguintes dados:

  • Nome do banco de dados: tutorial_blog
  • Nome de usuário: root
  • Senha: 12345678
  • Servidor do banco de dados: mysql
  • Prefixo da tabela: wp_

Clique em "Enviar"

11) O WordPress te pedirá para criar um arquivo wp-config.php com o código que ele passar:

Entre na pasta wordpress e crie o arquivo wp-config.php com aquele conteúdo informado.

Clique em "Instalar"

12) O WordPress te pedirá mais algumas informações, agora a respeito do site. Preencha com os dados que bem entender:

Clique em "Instalar WordPress"

13) O WordPress estará instalado:

Agora você tem o Caddy conectando ao PHP-FPM rodando dentro de um container Docker, e se comunicando com o MySQL em outro container Docker dentro da mesma rede.

Conclusão

Como dá pra ver, mesmo com o uso de containers, não há a necessidade de se preocupar em colocar TUDO dentro do Docker. É possível ter uma certa flexibilidade, ainda mais em termos de serviços de rede (como o MySQL ou o PHP-FPM).

Naturalmente, entretanto, ter mais containers rodando no Docker tende a facilitar mais a vida em alguns cenários. Em ambientes de desenvolvimento, por exemplo, quando você usa algo como o Docker Compose, todo o processo de instalação e configuração de um projeto se resume a alguns poucos comandos. Não há a necessidade de, por exemplo, pedir pela instalação de softwares adicionais (como o Caddy, no caso desse post), nem de configurações extras.

Portanto, como tudo na vida, decidir como estruturar sua aplicação é uma questão de custo benefício. No meu caso, o blog roda dessa forma (só que usando Nginx em vez do Caddy) pois isso me facilita a instalação e configuração de outros projetos, além do blog. Por exemplo, posso usar o Gitea (que é uma alternativa mais moderna ao Gogs, sobre o qual já falei aqui no blog), o Dokku e outros serviços tendo apenas um servidor web como "porta principal" do servidor. Logo, acaba valendo mais a pena ter tudo centralizado em uma única configuração do Nginx.

E você? Como gosta de estruturar os seus projetos? Compartilhe nos comentários!

]]>
https://fjorgemota.com/2024/07/07/docker-networks-ou-como-configurar-php-fpmmysql-no-docker-com-o-nginx-externo/feed/ 2 5436
Como melhorar a velocidade do phpcs e do phpcbf https://fjorgemota.com/2024/06/30/como-melhorar-a-velocidade-do-phpcs-e-do-phpcbf/ https://fjorgemota.com/2024/06/30/como-melhorar-a-velocidade-do-phpcs-e-do-phpcbf/#respond Sun, 30 Jun 2024 22:33:13 +0000 https://fjorgemota.com/?p=6871

Uma tarefa comum quando você está desenvolvendo um projeto é rodar um linter. Linters são ferramentas que analisam o código fonte do seu projeto e buscam detectar diferentes problemas no código, incluindo possíveis falhas na lógica e também questões relacionadas ao estilo do código. Porém, se tem uma coisa que incomoda MUITO é quando você está desenvolvendo, QUASE finalizando o software, e precisa ESPERAR para que ferramentas de verificação assim (que incluem tanto linters quanto testes, que são importantes para qualquer projeto) assim rodem.

Esse é o clássico problema que o XKCD retratou extremamente bem no post "Compiling":

Tirinha do XKCD que traduz livremente para: A desculpa número 1 dos programadores para procrastinar legitimamente: 'meu código está compilando'

E isso incomoda AINDA MAIS quando a ferramenta em questão é single-thread ou mal otimizada, pois o seu computador (que assumo ter um processador com múltiplos núcleos hoje em dia, certo?), fica mais ou menos assim:

Imagem de vários trabalhadores ao redor de uma obra, com só um dos trabalhadores de fato trabalhando em um buraco. Fazendo analogia ao uso de 100% de apenas um núcleo só na maior parte das atividades cotidianas em um computador.

Bom. Agora vamos ao tópico do post: Já que é bem difícil simplesmente paralelizar o trabalho de uma ferramenta de forma eficiente, vamos tentar tornar a sua atuação mais inteligente: Uma forma de deixar o trabalho de qualquer script - ou qualquer coisa, na verdade - mais eficiente, é fazer com que tal script (ou coisa) faça MENOS trabalho.

No caso de um linter, uma forma bem fácil de fazer MENOS trabalho é simplesmente... não ficar rechecando arquivos que não são necessários. Isso é MUITO mais eficiente do que simplesmente paralelizar o algoritmo, uma vez que mesmo o número de núcleos em um computador moderno é limitado.

Para exemplificar isso, considere um projeto que tenha, digamos, 5000 arquivos para serem analisados, cada um levando aí algo em torno de 0.04 segundos. Um linter rodando sequencialmente levaria, portanto, 200 segundos (ou 3 minutos e 20 segundos) para analisar tudo.

Se você tem um processador com 8 núcleos e 16 threads no seu computador, normalmente um algoritmo paralelo tentaria disparar 16 threads para processar cada arquivo. Digamos que essas 16 threads consigam acelerar em 16 vezes a velocidade de processamento (algo que nunca acontece pois há diversas limitações que impedem que algo assim aconteça). Em vez de demorar 200 segundos, ou 3 minutos e 20 segundos, tal algoritmo paralelo levaria cerca de 12 segundos para processar todos os 5000 arquivos.

Levar 12 segundos em vez de 3 minutos e 20 segundos é uma vitória tremenda, certo? Porém, considere que você NÃO vai modificar todos os 5000 arquivos a todo momento. Pelo contrário, você vai modificar ali 1, 3, 5 arquivos por vez, enquanto trabalha no seu projeto. E é aqui que mesmo a paralelização deixa de fazer sentido: Para que checar todos os arquivos, se apenas alguns poucos foram modificados? Se apenas 5 arquivos foram modificados, e o linter leva 0.04 segundos para processar um arquivo, apenas 0.04 * 5 = 0.2 segundos (ou MENOS de um segundo!) deveriam ser necessários para processar tudo, o que já é 60 vezes mais rápido que o algoritmo paralelo ou 1000 vezes mais rápido que o algoritmo sequencial.

E é aqui que entra o script que é o assunto desse post.

Esse é o script, em bash:

#!/bin/bash

set -e

SCRIPT_NAME=$(basename "$0");

find_script() {
    script_name="$1"
    current_dir=$(pwd)
    parent_dir=$(dirname "$current_dir")
    
    # Check if the path exists in the directory exists in the current directory
    script_path="$current_dir/vendor/bin/$script_name"
    if [ -x "$script_path" ]; then
        echo "$script_path"
        return
    fi
    
    # Traverse up the directory tree and check if the script exists in any parent directory
    while [ "$parent_dir" != "$current_dir" ]; do
        script_path="$parent_dir/vendor/bin/$script_name"
        if [ -x "$script_path" ]; then
            echo "$script_path"
            return
        fi
        
        current_dir="$parent_dir"
        parent_dir=$(dirname "$current_dir")
    done
    
    # Script not found
    echo "Script $SCRIPT_NAME not found." > /dev/stderr
    exit 1
}


SCRIPT_PATH=$(find_script "$SCRIPT_NAME");

MODIFIED_FILES=$(git status --porcelain=v2 . | awk '{print $NF}');

if [ -z "$MODIFIED_FILES" ]; then
  echo "No modified files found in the current directory."
  exit 0
fi

echo "Running $SCRIPT_PATH for the following list of files:";
echo "$MODIFIED_FILES" | xargs -i{} echo "- {}";

echo "$MODIFIED_FILES" | xargs "$SCRIPT_PATH";

O que o script faz? É bem simples, vou explicar:

  1. Primeiramente, rodamos set -e para fazer com que o bash imediatamente interrompa a execução caso qualquer comando retorne erro (com código de status de saída diferenet de 0)
  2. Definimos uma variável chamada SCRIPT_NAME contendo o nome do arquivo atual, já explico mais abaixo o motivo disso
  3. Definimos uma função find_script para procurar o script. Essa função busca encontrar o script de nome SCRIPT_NAME dentro da pasta vendor/bin, e busca tanto no diretório atual quanto em qualquer superior. Para exemplificar, se você rodar o arquivo dentro de uma pasta /home/usuario/projetos/MeuProjeto, ele procurará um arquivo executável com o nome do script salvo em SCRIPT_NAME (digamos que tal nome seja phpcs) nas seguintes pastas:
    • /home/usuario/projetos/MeuProjeto/vendor/bin/phpcs
    • /home/usuario/projetos/vendor/bin/phpcs
    • /home/usuario/vendor/bin/phpcs
    • /home/vendor/bin/phpcs
    • /vendor/bin/phpcs
  4. Definimos uma variável SCRIPT_PATH contendo o caminho para o script encontrado pela função find_script
  5. Computamos uma lista de arquivos modificados a partir do git. Apenas arquivos modificados cujas mudanças não foram comittados ainda aparecerão aqui
  6. Verificamos se a lista de arquivos modificados possui, de fato, arquivos modificados. Se não houver arquivos modificados mostramos uma mensagem e encerramos o script
  7. Mostramos a lista de arquivos modificados, e passamos essa lista de arquivos modificados para o script em questão, como parâmetros.

Para usar esse script em uma computador Linux, basta você

  1. Criar uma pasta bin na sua $HOME se você ainda não tiver, assim: mkdir -p $HOME/bin
  2. Adicionar essa pasta bin no seu environment, adicionando no arquivo de configuração do seu shell (digamos, .bashrc) algo como export PATH=$PATH:$HOME/bin
  3. Adicionar esse script nessa pasta bin, com o nome da ferramenta que você quer otimizar. Você pode nomeá-lo como phpcs ou phpcbf, por exemplo
  4. Em um projeto PHP com o phpcbf ou phpcs instalado via Composer, executar o comando correspondente SEM usar vendor/bin como prefixo
  5. Pronto!

O comando só rodará de fato a ferramenta em questão SE houver arquivos modificados e não comittados ainda no repositório Git.

Também não executará se a ferramenta em questão não estiver instalada usando Composer, que já apresentei me outro post.

Finalmente, se você não estiver usando Git, o script não executará, pois ele não tem como rastrear quais arquivos modificados. Se você ainda não usa Git, verifque o meu post sobre, também. ;)

E você, tem alguma outra dica para agilizar tarefas demoradas - mas necessárias - do seu dia-a-dia? Compartilhe nos comentários! E se ficou alguma dúvida, pode madnar nos comentários que vou tentar fazer o meu melhor para ajudar, também. :D

Valeu pela leitura!

]]>
Uma tarefa comum quando você está desenvolvendo um projeto é rodar um linter. Linters são ferramentas que analisam o código fonte do seu projeto e buscam detectar diferentes problemas no código, incluindo possíveis falhas na lógica e também questões relacionadas ao estilo do código. Porém, se tem uma coisa que incomoda MUITO é quando você está desenvolvendo, QUASE finalizando o software, e precisa ESPERAR para que ferramentas de verificação assim (que incluem tanto linters quanto testes, que são importantes para qualquer projeto) assim rodem.

Esse é o clássico problema que o XKCD retratou extremamente bem no post "Compiling":

Tirinha do XKCD que traduz livremente para: A desculpa número 1 dos programadores para procrastinar legitimamente: 'meu código está compilando'

E isso incomoda AINDA MAIS quando a ferramenta em questão é single-thread ou mal otimizada, pois o seu computador (que assumo ter um processador com múltiplos núcleos hoje em dia, certo?), fica mais ou menos assim:

Imagem de vários trabalhadores ao redor de uma obra, com só um dos trabalhadores de fato trabalhando em um buraco. Fazendo analogia ao uso de 100% de apenas um núcleo só na maior parte das atividades cotidianas em um computador.

Bom. Agora vamos ao tópico do post: Já que é bem difícil simplesmente paralelizar o trabalho de uma ferramenta de forma eficiente, vamos tentar tornar a sua atuação mais inteligente: Uma forma de deixar o trabalho de qualquer script - ou qualquer coisa, na verdade - mais eficiente, é fazer com que tal script (ou coisa) faça MENOS trabalho.

No caso de um linter, uma forma bem fácil de fazer MENOS trabalho é simplesmente... não ficar rechecando arquivos que não são necessários. Isso é MUITO mais eficiente do que simplesmente paralelizar o algoritmo, uma vez que mesmo o número de núcleos em um computador moderno é limitado.

Para exemplificar isso, considere um projeto que tenha, digamos, 5000 arquivos para serem analisados, cada um levando aí algo em torno de 0.04 segundos. Um linter rodando sequencialmente levaria, portanto, 200 segundos (ou 3 minutos e 20 segundos) para analisar tudo.

Se você tem um processador com 8 núcleos e 16 threads no seu computador, normalmente um algoritmo paralelo tentaria disparar 16 threads para processar cada arquivo. Digamos que essas 16 threads consigam acelerar em 16 vezes a velocidade de processamento (algo que nunca acontece pois há diversas limitações que impedem que algo assim aconteça). Em vez de demorar 200 segundos, ou 3 minutos e 20 segundos, tal algoritmo paralelo levaria cerca de 12 segundos para processar todos os 5000 arquivos.

Levar 12 segundos em vez de 3 minutos e 20 segundos é uma vitória tremenda, certo? Porém, considere que você NÃO vai modificar todos os 5000 arquivos a todo momento. Pelo contrário, você vai modificar ali 1, 3, 5 arquivos por vez, enquanto trabalha no seu projeto. E é aqui que mesmo a paralelização deixa de fazer sentido: Para que checar todos os arquivos, se apenas alguns poucos foram modificados? Se apenas 5 arquivos foram modificados, e o linter leva 0.04 segundos para processar um arquivo, apenas 0.04 * 5 = 0.2 segundos (ou MENOS de um segundo!) deveriam ser necessários para processar tudo, o que já é 60 vezes mais rápido que o algoritmo paralelo ou 1000 vezes mais rápido que o algoritmo sequencial.

E é aqui que entra o script que é o assunto desse post.

Esse é o script, em bash:

#!/bin/bash

set -e

SCRIPT_NAME=$(basename "$0");

find_script() {
    script_name="$1"
    current_dir=$(pwd)
    parent_dir=$(dirname "$current_dir")
    
    # Check if the path exists in the directory exists in the current directory
    script_path="$current_dir/vendor/bin/$script_name"
    if [ -x "$script_path" ]; then
        echo "$script_path"
        return
    fi
    
    # Traverse up the directory tree and check if the script exists in any parent directory
    while [ "$parent_dir" != "$current_dir" ]; do
        script_path="$parent_dir/vendor/bin/$script_name"
        if [ -x "$script_path" ]; then
            echo "$script_path"
            return
        fi
        
        current_dir="$parent_dir"
        parent_dir=$(dirname "$current_dir")
    done
    
    # Script not found
    echo "Script $SCRIPT_NAME not found." > /dev/stderr
    exit 1
}


SCRIPT_PATH=$(find_script "$SCRIPT_NAME");

MODIFIED_FILES=$(git status --porcelain=v2 . | awk '{print $NF}');

if [ -z "$MODIFIED_FILES" ]; then
  echo "No modified files found in the current directory."
  exit 0
fi

echo "Running $SCRIPT_PATH for the following list of files:";
echo "$MODIFIED_FILES" | xargs -i{} echo "- {}";

echo "$MODIFIED_FILES" | xargs "$SCRIPT_PATH";

O que o script faz? É bem simples, vou explicar:

  1. Primeiramente, rodamos set -e para fazer com que o bash imediatamente interrompa a execução caso qualquer comando retorne erro (com código de status de saída diferenet de 0)
  2. Definimos uma variável chamada SCRIPT_NAME contendo o nome do arquivo atual, já explico mais abaixo o motivo disso
  3. Definimos uma função find_script para procurar o script. Essa função busca encontrar o script de nome SCRIPT_NAME dentro da pasta vendor/bin, e busca tanto no diretório atual quanto em qualquer superior. Para exemplificar, se você rodar o arquivo dentro de uma pasta /home/usuario/projetos/MeuProjeto, ele procurará um arquivo executável com o nome do script salvo em SCRIPT_NAME (digamos que tal nome seja phpcs) nas seguintes pastas:
    • /home/usuario/projetos/MeuProjeto/vendor/bin/phpcs
    • /home/usuario/projetos/vendor/bin/phpcs
    • /home/usuario/vendor/bin/phpcs
    • /home/vendor/bin/phpcs
    • /vendor/bin/phpcs
  4. Definimos uma variável SCRIPT_PATH contendo o caminho para o script encontrado pela função find_script
  5. Computamos uma lista de arquivos modificados a partir do git. Apenas arquivos modificados cujas mudanças não foram comittados ainda aparecerão aqui
  6. Verificamos se a lista de arquivos modificados possui, de fato, arquivos modificados. Se não houver arquivos modificados mostramos uma mensagem e encerramos o script
  7. Mostramos a lista de arquivos modificados, e passamos essa lista de arquivos modificados para o script em questão, como parâmetros.

Para usar esse script em uma computador Linux, basta você

  1. Criar uma pasta bin na sua $HOME se você ainda não tiver, assim: mkdir -p $HOME/bin
  2. Adicionar essa pasta bin no seu environment, adicionando no arquivo de configuração do seu shell (digamos, .bashrc) algo como export PATH=$PATH:$HOME/bin
  3. Adicionar esse script nessa pasta bin, com o nome da ferramenta que você quer otimizar. Você pode nomeá-lo como phpcs ou phpcbf, por exemplo
  4. Em um projeto PHP com o phpcbf ou phpcs instalado via Composer, executar o comando correspondente SEM usar vendor/bin como prefixo
  5. Pronto!

O comando só rodará de fato a ferramenta em questão SE houver arquivos modificados e não comittados ainda no repositório Git.

Também não executará se a ferramenta em questão não estiver instalada usando Composer, que já apresentei me outro post.

Finalmente, se você não estiver usando Git, o script não executará, pois ele não tem como rastrear quais arquivos modificados. Se você ainda não usa Git, verifque o meu post sobre, também. 😉

E você, tem alguma outra dica para agilizar tarefas demoradas - mas necessárias - do seu dia-a-dia? Compartilhe nos comentários! E se ficou alguma dúvida, pode madnar nos comentários que vou tentar fazer o meu melhor para ajudar, também. 😀

Valeu pela leitura!

]]>
https://fjorgemota.com/2024/06/30/como-melhorar-a-velocidade-do-phpcs-e-do-phpcbf/feed/ 0 6871
Uma simples dica para economizar tempo ao atualizar pacotes do AUR no Arch Linux https://fjorgemota.com/2023/12/28/uma-simples-dica-para-economizar-tempo-ao-atualizar-pacotes-do-aur-no-arch-linux/ https://fjorgemota.com/2023/12/28/uma-simples-dica-para-economizar-tempo-ao-atualizar-pacotes-do-aur-no-arch-linux/#respond Thu, 28 Dec 2023 17:34:57 +0000 https://fjorgemota.com/?p=7656
Imagem gerada pelo Dall-E representando um usuário mexendo em um computador com um relógio na parede

Como usuário da excelente distribuição Arch Linux (BTW, I use Arch), eu só gostaria de fazer aqui um breve lembrete para todos vocês: Antes de você gastar bastante tempo compilando um pacote gigante do AUR (ou Arch User Repository), como por exemplo o qt5-webkit, vale checar se você realmente PRECISA desse pacote OU se ele é dependência para algum outro pacote instalado na sua máquina.

No Arch Linux, você pode checar se outro pacote depende do pacote em questão através do pactree, que é um script disponível no pacote pacman-contrib. Primeiro, instale o pacman-contrib:

sudo pacman -S pacman-contrib

Depois, execute o seguinte comando, sendo pacote o pacote que você quer verificar se é usado de alguma forma:

sudo pactree -r pacote

No caso do qt5-webkit, por exemplo, você pode usar:

sudo pactree -r qt5-webkit

Nesse comando, o -r significa reverse, ou seja, ele monta uma árvore ASCII de pacotes que DEPENDEM do pacote que você especificou no parâmetro. Se nenhum outro pacote no seu computador tiver o pacote que você informou como dependência, apenas o nome do pacote que você informou aparecerá na tela (como "raiz" da árvore).

Claro que, se o pacote informado for de algum programa que você use, como o MySQL ou o Google Chrome, por exemplo, é provável que nenhum outro pacote aparecerá para esse comando, afinal, nenhum outro pacote instalado no seu computador depende dele.

Quando encontrar um pacote do AUR que de fato não faz sentido gastar tempo para atualizar, você pode simplesmente...removê-lo. Esse é o comando para fazer isso, e, novamente, pacote é o pacote que você quer remover:

sudo pacman -Rns pacote

No caso do qt5-webkit, o comando fica assim:

sudo pacman -Rns qt5-webkit

Note que esse o parâmetro -Rns aqui vai remover não somente o pacote em questão mas também todas as suas dependências não utilizadas (o que pode muito bem incluir outros pacotes grandes do AUR..).

"Mas, Fernando, por qual motivo tantas menções para o qt5-webkit?!". Bom, nada em especial, é só o fato de que o gênio que vos escreve aqui gastou mais de uma hora essa semana esperando a compilação desse pacote só para finalmente descobrir que.. esse pacote não tava sendo utilizado por nenhuma outra dependência do PC. Ou seja, literalmente desperdicei tempo a toa. 🙃

Um último detalhe...

Agora, para você que leu até aqui (obrigado!), uma dúvida: Qual a sua opinião sobre textos curtos assim? Eu quero muito voltar a escrever mais para esse blog, e inclusive até comecei a preparar uma versão em inglês (!!!), mas para a surpresa de um total de zero pessoas a vida adulta é bem...caótica, e artigos de sucesso como o que escrevi anos atrás sobre o Docker (que até hoje recebe um razoável número de visitas) levam MUITO tempo para serem propriamente escrito, testados e revisados. Eu gosto da ideia de trazer dicas e comentários simples assim em posts concisos, entretanto, mas queria saber: qual a sua opinião sobre isso? Deixe nos comentários! :)

Ah, e feliz ano novo, pessoal! :D

]]>
Imagem gerada pelo Dall-E representando um usuário mexendo em um computador com um relógio na parede

Como usuário da excelente distribuição Arch Linux (BTW, I use Arch), eu só gostaria de fazer aqui um breve lembrete para todos vocês: Antes de você gastar bastante tempo compilando um pacote gigante do AUR (ou Arch User Repository), como por exemplo o qt5-webkit, vale checar se você realmente PRECISA desse pacote OU se ele é dependência para algum outro pacote instalado na sua máquina.

No Arch Linux, você pode checar se outro pacote depende do pacote em questão através do pactree, que é um script disponível no pacote pacman-contrib. Primeiro, instale o pacman-contrib:

sudo pacman -S pacman-contrib

Depois, execute o seguinte comando, sendo pacote o pacote que você quer verificar se é usado de alguma forma:

sudo pactree -r pacote

No caso do qt5-webkit, por exemplo, você pode usar:

sudo pactree -r qt5-webkit

Nesse comando, o -r significa reverse, ou seja, ele monta uma árvore ASCII de pacotes que DEPENDEM do pacote que você especificou no parâmetro. Se nenhum outro pacote no seu computador tiver o pacote que você informou como dependência, apenas o nome do pacote que você informou aparecerá na tela (como "raiz" da árvore).

Claro que, se o pacote informado for de algum programa que você use, como o MySQL ou o Google Chrome, por exemplo, é provável que nenhum outro pacote aparecerá para esse comando, afinal, nenhum outro pacote instalado no seu computador depende dele.

Quando encontrar um pacote do AUR que de fato não faz sentido gastar tempo para atualizar, você pode simplesmente...removê-lo. Esse é o comando para fazer isso, e, novamente, pacote é o pacote que você quer remover:

sudo pacman -Rns pacote

No caso do qt5-webkit, o comando fica assim:

sudo pacman -Rns qt5-webkit

Note que esse o parâmetro -Rns aqui vai remover não somente o pacote em questão mas também todas as suas dependências não utilizadas (o que pode muito bem incluir outros pacotes grandes do AUR..).

"Mas, Fernando, por qual motivo tantas menções para o qt5-webkit?!". Bom, nada em especial, é só o fato de que o gênio que vos escreve aqui gastou mais de uma hora essa semana esperando a compilação desse pacote só para finalmente descobrir que.. esse pacote não tava sendo utilizado por nenhuma outra dependência do PC. Ou seja, literalmente desperdicei tempo a toa. 🙃

Um último detalhe...

Agora, para você que leu até aqui (obrigado!), uma dúvida: Qual a sua opinião sobre textos curtos assim? Eu quero muito voltar a escrever mais para esse blog, e inclusive até comecei a preparar uma versão em inglês (!!!), mas para a surpresa de um total de zero pessoas a vida adulta é bem...caótica, e artigos de sucesso como o que escrevi anos atrás sobre o Docker (que até hoje recebe um razoável número de visitas) levam MUITO tempo para serem propriamente escrito, testados e revisados. Eu gosto da ideia de trazer dicas e comentários simples assim em posts concisos, entretanto, mas queria saber: qual a sua opinião sobre isso? Deixe nos comentários! 🙂

Ah, e feliz ano novo, pessoal! 😀

]]>
https://fjorgemota.com/2023/12/28/uma-simples-dica-para-economizar-tempo-ao-atualizar-pacotes-do-aur-no-arch-linux/feed/ 0 7656
Como reduzir o tamanho de vídeos no Linux com ffmpeg https://fjorgemota.com/2023/04/09/como-reduzir-o-tamanho-de-videos-no-linux-com-ffmpeg/ https://fjorgemota.com/2023/04/09/como-reduzir-o-tamanho-de-videos-no-linux-com-ffmpeg/#respond Mon, 10 Apr 2023 01:03:53 +0000 https://fjorgemota.com/?p=6486

Sabe quando você tem um vídeo e você quer compartilhar na internet? Pois então, hoje, na maior parte dos casos, isso não é um problema em termos de plataforma, visto que muitas plataformas aceitam vídeos sem problema nenhum. O problema, entretanto, é a velocidade de upload da maior parte das conexões de internet aqui no Brasil: Aqui, apesar de assinar uma conexão com centenas de Mbps, por exemplo, eu possuo apenas 25 Mbps de upload. Não que hoje seja MUITO demorado quando você compara com, por exemplo, a conexão discada (famosos 56 Kbps que na verdade se traduziam em apenas 8 Kbps em dias em que a conexão estava boa), mas, no dia a dia moderno, o upload de um vídeo qualquer pode levar...bastante tempo.

Logo, a possibilidade de otimizar o vídeo de forma a reduzir o seu tamanho enquanto mantendo uma boa qualidade acaba sendo bem atraente. É dificil dizer em números exatos, mas, na minha experiência, recodificar um vídeo localmente e depois enviar acaba tomando bem menos tempo do que só..enviar, daí a razão por trás desse post.

Antes de finalmente apresentar o script, um pequeno aviso:

Os vídeo ao qual me refiro nesse post são, majoritariamente, gravações de tela, que são muito úteis para mostrar um recurso em desenvolvimento, ou então documentar um bug. Nesses vídeos, pode haver um pequeno bloco no qual a imagem da sua webcam aparece, mas os comandos aqui apresentados não são necessariamente otimizados para um vídeo contendo SOMENTE isso. Em virtude disso, o framerate do vídeo é reduzido para apenas 30 FPS, o que reforça a intenção do script de ser mais otimizado para uso em vídeos em que muitos quadros acabam por serem estáticos.

Segue o script, que você pode salvar como prepare-video no seu computador:

#!/bin/bash
ffmpeg -i "$1" -crf 27 -preset veryfast -movflags +faststart -r 30 -vcodec libx264 -acodec aac "$2"

Antes de executar, lembre-se de dar permissões de execução (chmod +x prepare-video) para o comando em questão. A partir daí, você pode usar o seguinte comando para converter vídeos:

prepare-video video-de-entrada.avi video-de-saida.mp4

Substituindo video-de-entrada.avi pelo vídeo que você quer converter e video-de-saida.mp4 pelo arquivo para o qual o vídeo convertido deve ser salvo.

Esse é o script que eu tenho usado há anos, só que com o nome convert-to-whatsapp, pois foi o motivador inicial para criar tal script: O WhatsApp é CHATO no suporte a vídeos, e esse script é quem me permitia codificar um vídeo que fosse suportado pelo WhatsApp nativamente.

Note que um grande problema desse script é que ele roda todo o processo de decodificação e codificação no processador. Em um processador moderna, com 8 núcleos, isso normalmente não é um grande problema, mas em processadores mais antigos isso pode ser um problema.

Movendo a carga para a placa de vídeo

Como documentado acima, esse é o script que eu uso já há alguns bons anos. Entretanto, como tudo na vida, sempre há espaço pra melhorias. Recentemente, eu fiz uma pequena melhoria no meu computador: troquei a minha placa de vídeo, que antes era uma AMD RX 580 8GB, por uma NVIDIA RTX 4090. Em virtude disso, a possibilidade de usar a placa de vídeo para fazer todo o processo de decodificação e codificação se tornou mais...real, uma vez que infelizmente a AMD é bem fraca nesse aspecto (mesmo nas últimas gerações de placa de vídeo).

Se você tem uma placa de vídeo da NVIDIA com suporte a NVENC, uma possibilidade é usar esse comando aqui para conseguir resultados similares à versão com processador, conforme gerado pelo ChatGPT (a ideia é que esse comando seja equivalente ao anterior):

#!/bin/bash
ffmpeg -hwaccel cuvid -i "$1" -c:v h264_nvenc -preset:v fast -rc:v vbr -cq:v 19 -profile:v high -level:v 4.2 -b:v 1200k -maxrate:v 2400k -bufsize:v 2400k -c:a aac -b:a 128k -movflags +faststart -r 30 "$2"

A parte curiosa? Apesar de ser um pouco mais rápida do que a versão que roda somente em processador, esta versão gera arquivos MUITO maiores. Segue uma tabela comparando os resultados:

Configuração do CodificadorTamanho final do arquivoTempo para realizar a conversão
Arquivo Original1.1 GB-
Usando somente o processador106 MB1 minuto e 50 segundos
Usando somente a placa de vídeo293 MB1 minuto e 34 segundos

Para fins de documentação, o meu processador é um AMD Ryzen 7 5800X3D, um dos processadores mais poderosos da atualidade...para jogos.

Aí você me pergunta: Tá, mas pra quê eu vou utilizar a versão baseada em GPU, então? Bom, eu não faço ideia, ¯\_(ツ)_/¯. Ao meu ver, a maior vantagem de usar o codificador baseado em GPU é que você acaba movendo para a GPU toda a carga de processamento, o que é bem útil em algumas situações.

Outras alternativa usando somente o processador

De acordo com o ChatGPT, uma outra boa alternativa - ainda usando somente o processador - para codificar os vídeos com o propósito desse post, é o seguinte script:

#!/bin/bash
ffmpeg -y -i "$1" -c:v libx264 -preset medium -crf 28 -r 30 -c:a aac -b:a 192k -movflags +faststart "$2"

Fica a seu critério qual utilizar. Pelos meus testes, esse comando provê muda algumas configurações para ter um pouco mais de qualidade de imagem, MAS naturalmente isso aumenta um pouco o tamanho do arquivo. Além disso, o comando demora mais para executar, veja a tabela abaixo:

Configuração do CodificadorTamanho final do arquivoTempo para realizar a conversão
Arquivo Original1.1 GB-
Meu script106 MB1 minuto e 50 segundos
Script do ChatGPT128 MB2 minutos e 26 segundos

Se você valoriza mais qualidade e não está tão preocupado em ter a execução mais demorada nem o tamanho ligeiramente maior, essa pode ser definitivamente uma opção.

Conclusão

O ffmpeg é uma ferramenta extremamente poderosa para esse tipo de manipulação de vídeo, e esse tipo de script, em torno de ferramentas assim, ajuda MUITO a facilitar o uso de tarefas cotidianas no computador. Hoje em dia, eu não gravo tantos vídeos quanto gostaria, mas ainda assim esse script se torna muito útil para compartilhar vídeos na internet, em especial considerando os limites da velocidade de upload do plano de internet aqui de casa.

E você, grava vídeos para a internet? Tem algum script maneiro para compartilhar? Deixe nos comentários! :D

]]>
Sabe quando você tem um vídeo e você quer compartilhar na internet? Pois então, hoje, na maior parte dos casos, isso não é um problema em termos de plataforma, visto que muitas plataformas aceitam vídeos sem problema nenhum. O problema, entretanto, é a velocidade de upload da maior parte das conexões de internet aqui no Brasil: Aqui, apesar de assinar uma conexão com centenas de Mbps, por exemplo, eu possuo apenas 25 Mbps de upload. Não que hoje seja MUITO demorado quando você compara com, por exemplo, a conexão discada (famosos 56 Kbps que na verdade se traduziam em apenas 8 Kbps em dias em que a conexão estava boa), mas, no dia a dia moderno, o upload de um vídeo qualquer pode levar...bastante tempo.

Logo, a possibilidade de otimizar o vídeo de forma a reduzir o seu tamanho enquanto mantendo uma boa qualidade acaba sendo bem atraente. É dificil dizer em números exatos, mas, na minha experiência, recodificar um vídeo localmente e depois enviar acaba tomando bem menos tempo do que só..enviar, daí a razão por trás desse post.

Antes de finalmente apresentar o script, um pequeno aviso:

Os vídeo ao qual me refiro nesse post são, majoritariamente, gravações de tela, que são muito úteis para mostrar um recurso em desenvolvimento, ou então documentar um bug. Nesses vídeos, pode haver um pequeno bloco no qual a imagem da sua webcam aparece, mas os comandos aqui apresentados não são necessariamente otimizados para um vídeo contendo SOMENTE isso. Em virtude disso, o framerate do vídeo é reduzido para apenas 30 FPS, o que reforça a intenção do script de ser mais otimizado para uso em vídeos em que muitos quadros acabam por serem estáticos.

Segue o script, que você pode salvar como prepare-video no seu computador:

#!/bin/bash
ffmpeg -i "$1" -crf 27 -preset veryfast -movflags +faststart -r 30 -vcodec libx264 -acodec aac "$2"

Antes de executar, lembre-se de dar permissões de execução (chmod +x prepare-video) para o comando em questão. A partir daí, você pode usar o seguinte comando para converter vídeos:

prepare-video video-de-entrada.avi video-de-saida.mp4

Substituindo video-de-entrada.avi pelo vídeo que você quer converter e video-de-saida.mp4 pelo arquivo para o qual o vídeo convertido deve ser salvo.

Esse é o script que eu tenho usado há anos, só que com o nome convert-to-whatsapp, pois foi o motivador inicial para criar tal script: O WhatsApp é CHATO no suporte a vídeos, e esse script é quem me permitia codificar um vídeo que fosse suportado pelo WhatsApp nativamente.

Note que um grande problema desse script é que ele roda todo o processo de decodificação e codificação no processador. Em um processador moderna, com 8 núcleos, isso normalmente não é um grande problema, mas em processadores mais antigos isso pode ser um problema.

Movendo a carga para a placa de vídeo

Como documentado acima, esse é o script que eu uso já há alguns bons anos. Entretanto, como tudo na vida, sempre há espaço pra melhorias. Recentemente, eu fiz uma pequena melhoria no meu computador: troquei a minha placa de vídeo, que antes era uma AMD RX 580 8GB, por uma NVIDIA RTX 4090. Em virtude disso, a possibilidade de usar a placa de vídeo para fazer todo o processo de decodificação e codificação se tornou mais...real, uma vez que infelizmente a AMD é bem fraca nesse aspecto (mesmo nas últimas gerações de placa de vídeo).

Se você tem uma placa de vídeo da NVIDIA com suporte a NVENC, uma possibilidade é usar esse comando aqui para conseguir resultados similares à versão com processador, conforme gerado pelo ChatGPT (a ideia é que esse comando seja equivalente ao anterior):

#!/bin/bash
ffmpeg -hwaccel cuvid -i "$1" -c:v h264_nvenc -preset:v fast -rc:v vbr -cq:v 19 -profile:v high -level:v 4.2 -b:v 1200k -maxrate:v 2400k -bufsize:v 2400k -c:a aac -b:a 128k -movflags +faststart -r 30 "$2"

A parte curiosa? Apesar de ser um pouco mais rápida do que a versão que roda somente em processador, esta versão gera arquivos MUITO maiores. Segue uma tabela comparando os resultados:

Configuração do CodificadorTamanho final do arquivoTempo para realizar a conversão
Arquivo Original1.1 GB-
Usando somente o processador106 MB1 minuto e 50 segundos
Usando somente a placa de vídeo293 MB1 minuto e 34 segundos

Para fins de documentação, o meu processador é um AMD Ryzen 7 5800X3D, um dos processadores mais poderosos da atualidade...para jogos.

Aí você me pergunta: Tá, mas pra quê eu vou utilizar a versão baseada em GPU, então? Bom, eu não faço ideia, ¯\_(ツ)_/¯. Ao meu ver, a maior vantagem de usar o codificador baseado em GPU é que você acaba movendo para a GPU toda a carga de processamento, o que é bem útil em algumas situações.

Outras alternativa usando somente o processador

De acordo com o ChatGPT, uma outra boa alternativa - ainda usando somente o processador - para codificar os vídeos com o propósito desse post, é o seguinte script:

#!/bin/bash
ffmpeg -y -i "$1" -c:v libx264 -preset medium -crf 28 -r 30 -c:a aac -b:a 192k -movflags +faststart "$2"

Fica a seu critério qual utilizar. Pelos meus testes, esse comando provê muda algumas configurações para ter um pouco mais de qualidade de imagem, MAS naturalmente isso aumenta um pouco o tamanho do arquivo. Além disso, o comando demora mais para executar, veja a tabela abaixo:

Configuração do CodificadorTamanho final do arquivoTempo para realizar a conversão
Arquivo Original1.1 GB-
Meu script106 MB1 minuto e 50 segundos
Script do ChatGPT128 MB2 minutos e 26 segundos

Se você valoriza mais qualidade e não está tão preocupado em ter a execução mais demorada nem o tamanho ligeiramente maior, essa pode ser definitivamente uma opção.

Conclusão

O ffmpeg é uma ferramenta extremamente poderosa para esse tipo de manipulação de vídeo, e esse tipo de script, em torno de ferramentas assim, ajuda MUITO a facilitar o uso de tarefas cotidianas no computador. Hoje em dia, eu não gravo tantos vídeos quanto gostaria, mas ainda assim esse script se torna muito útil para compartilhar vídeos na internet, em especial considerando os limites da velocidade de upload do plano de internet aqui de casa.

E você, grava vídeos para a internet? Tem algum script maneiro para compartilhar? Deixe nos comentários! 😀

]]>
https://fjorgemota.com/2023/04/09/como-reduzir-o-tamanho-de-videos-no-linux-com-ffmpeg/feed/ 0 6486
Dokku - Deploy simplificado para os seus projetos https://fjorgemota.com/2023/03/30/dokku-deploy-simplificado-para-os-seus-projetos/ https://fjorgemota.com/2023/03/30/dokku-deploy-simplificado-para-os-seus-projetos/#comments Fri, 31 Mar 2023 01:00:00 +0000 https://fjorgemota.com/?p=6337
Logotipo do Dokku

Como programador, uma coisa que eu aprecio demais na nossa profissão é a possibilidade de criar side projects. Claro, relaxar é super importante, eu não nego, ainda mais para nós que trabalhamos basicamente pensando muito o dia inteiro.

Mas poder criar projetos que resolvem AQUELE problema ou necessidade chato que você tem - e ainda de quebra poder ajudar outras pessoas (ou até faturar algum $$$ extra, em alguns casos) - também é algo muito bem vindo.

O problema, como sempre, é que isso é basicamente...trabalho, e por mais interessante que seja poder customizar cada parte do seu próprio projeto, o fato é que quanto mais você demora para lançá-lo - por qualquer motivo que seja - maiores são as chances de você acabar desistindo e/ou partindo para outro projeto.

E é nesse ponto que entra o Dokku: um projeto que, após ser instalado em um servidor (VPS ou dedicado, conforme o seu bolso deixar), lhe permite facilmente criar novas aplicações e fazer deploy delas, usando um workflow muito parecido com o do Heroku, mas sem os custos extras (além do custo do servidor e do seu tempo, é claro).

O que é

O Dokku é um projeto que implementa um PaaS - Platform as a Service, ou Plataforma como Serviço, em inglês - e usa o Docker - que já apresentamos em outro post - para fazer suas "magias". Sua administração decorre de um simples comando chamado "dokku", que permite a criação de novos aplicativos (que é como o projeto chama os seus projetos) e também configuração dos mesmos.

Além disso, possui um ecossistema muito rico, com plugins que facilitam, entre outras coisas, a instalação e configuração de projetos como MySQL, PostgreSQL e RabbitMQ, e até mesmo facilitam a configuração de certificados SSL usando Let's Encrypt.

Depois de configurar o Dokku completamente, se torna possível publicar seu projeto com apenas alguns poucos comandos (a depender do que você usa no seu projeto), portanto tornando possível publicar o seu projeto muito rapidamente. Para que isso seja possível, entretanto, é necessário seguir algumas regras durante o desenvolvimento do seu projeto, chamado Twelve-Factor App, em especial no que diz respeito à configuração do projeto, mas isso é algo que qualquer framework moderno já suporta facilmente, então não chega a ser um grande problema.

Como instalar

Para instalar o Dokku, você precisa primeiramente de um servidor VPS ou dedicado, ou seja, não dá para usar uma hospedagem compartilhada com ele. Também é necessário que você possua um domínio, e que o servidor em questão rode uma distribuição Linux. Para simplificar o processo de instalação, vou mostrar aqui apenas os comandos para quem usa Ubuntu ou Debian no servidor. É plenamente possível usar o Dokku também com outras distribuições, mas, o processo será potencialmente mais complicado.

Se você quiser apenas usar o Dokku em seu próprio computador, digamos, para ver como funciona, também é possível usar o Vagrant - que apresentei em outro post - para rodá-lo. Apenas esteja ciente de que dessa forma os seus projetos NÃO serão publicados na internet. Segue aqui as instruções.

Com tudo isso dito, segue os comandos para instalar a última versão do Dokku - 0.30.2 - conforme disponível no momento de escrita desse post. Eu sugiro fortemente para que você verifique a última versão disponível do Dokku e ajuste o comando de forma correspondente abaixo, portanto, os comandos abaixo são apenas uma referência:

wget https://dokku.com/bootstrap.sh
sudo DOKKU_TAG=v0.30.2 bash bootstrap.sh

ATENÇÃO: Antes de rodar esses comandos de forma imediata, eu sugiro para que os rode individualmente, e, se possível, analise o conteúdo do arquivo baixado após a execução do primeiro comando. O Dokku é um projeto bastante confiável, mas, sempre vale ter essa prática por segurança, com todo e qualquer comando do tipo que você vê por aí.

Depois de rodar esses comandos e de instalar o Dokku, chegou a hora de configurar o domínio global que o Dokku usará. Uma questão importante sobre esse domínio é que, em um cenário ideal, o domínio deve ser configurado de forma que todo sub-domínio ainda não definido aponte para o servidor no qual o Dokku está instalado. Em termos técnico, isso significa que o DNS precisa ter uma entrada "wildcard" apontando para o seeu servidor. Isso NÃO é um requisito para o uso do Dokku, mas, facilita e muito a configuração de novos aplicativos, visto que você acaba não precisando configurar o DNS manualmente a cada vez que você vai criar um novo aplicativo (bom, pelo menos não se você quiser que sua aplicação rode em um..sub-domínio). Para referência, nesse artigo é mostrado como configurar uma entrada wildcard no Cloudflare. Consulte a documentação do servidor DNS que você usa para mais informações.

Com isso dito, segue o comando para configurar o domínio global no Dokku, onde SEU_DOMINIO é o dominio que o Dokku deve usar como global:

dokku domains:set-global SEU_DOMINIO

Depois de configurar o Dokku no seu servidor, uma última etapa que eu considero bastante importante é configurar um cliente do Dokku no seu próprio computador. Naturalmente, o processo varia bastante de acordo com a sua preferência, mas aqui está a lista de clientes que podem ser usados para se comunicar com o Dokku. Pessoalmente, eu gosto bastante do cliente oficial do Dokku, que é bastante simples e funciona por SSH, tornando toda a comunicação bastante segura e simples.

Como publicar sua primeira aplicação

Depois de configurado, publicar sua primeira aplicação é bem simples. Digamos que você tenha um simples repositório Git - que você pode ler mais sobre nesse post - com o seguinte arquivo - chamado index.php - comittado:

<?php
// Pega o nome da URL, ou assume o padrão "Mundo", e converte caracteres HTML de forma que não seja possível injetar JS/CSS na página:
$nome = htmlspecialchars( $_GET["name"] ?? "Mundo" );
// Imprime "Olá, " seguido do valor da variável acima
echo "Olá, {$nome}";

E também tenha no repositório os arquivos "composer.json" e "composer.lock" criados pelo Composer, sobre o qual já falei aqui (você pode rodar composer init e responder todas as perguntas para configurar o Composer corretamente).

Apesar da criação dos arquivos composer.json e composer.lock não ser SUPER necessária - visto que o Dokku consegue detectar que se trata de um projeto que usa PHP à partir da existência do arquivo index.php - é algo que é recomendado, visto que o modo de detecção a partir da existência de arquivos PHP é considerado depreciado.

Antes de fazer o deploy dessa aplicação, que vamos chamar aqui de hello, você primeiramente precisa criar a aplicação no Dokku. Para isso, você pode usar o seguinte comando, que usa o cliente oficial do Dokku mencionado anteriormente:

dokku apps:create hello

Depois de criado, você pode simplesmente rodar o seguinte comando para fazer o deploy do projeto, assumindo que a branch principal do repositório em questão é a branch main, e que você fez o commit nessa branch:

git push dokku main

Depois de rodar o comando, o Dokku analisará o conteúdo do repositório e rapidamente constatará que se trata de uma aplicação em PHP, usando um projeto chamado Herokuish. A partir daí, será usado o buildpack do Heroku para PHP para fazer o deploy da sua aplicação, que deverá estar disponível em hello.SEU_DOMINIO.

Como eu possuo Dokku instalado nesse mesmo servidor, você pode ver a aplicação rodando aqui: http://hello.fjorgemota.com/?nome=Teste - sinta-se a vontade para mudar o parâmetro nome para qualquer outra coisa apenas para experimento. :)

Conectando um banco de dados

Naturalmente, fazer uma simples aplicação assim não mostra o potencial completo da ferramenta. Em virtude disso, vamos complicar um pouquinho o exemplo: Em vez de um simples código PHP que printa um parâmetro recebido na URL, vamos fazer um contador de visitas...bem simples, mas enfim.

Para isso, no repositório Git criado anteriormente, adicione o seguinte código no final do arquivo index.php:

// Captura a URL da variável de ambiente e divide em partes
$parametros = parse_url( getenv( "DATABASE_URL" ) );
// Gera a URL para passar para o PDO, contendo os dados de configuração do banco de dados
$url = sprintf( "pgsql:host=%s;port=%d;dbname=%s;user=%s;password=%s", $parametros['host'], $parametros['port'],  substr( $parametros['path'], 1 ), $parametros['user'], $parametros['pass'] );
// Conecta oa banco de dados
$conexao = new PDO( $url );
// Faz a consulta para contar o número de visitas e retornar o número atualizado
$consulta = $conexao->query( "INSERT INTO visits ( id, num_visits ) VALUES( 1, 1 ) ON CONFLICT (id) DO UPDATE SET num_visits = visits.num_visits + EXCLUDED.num_visits RETURNING num_visits");
// Captura o número de visitas retornado pelo banco de dados
$visitas = $consulta->fetchColumn( 0 );
// Imprime o resultado na página
echo "<br /> Número de visitas: {$visits}";

Depois de adicionar esse script ao arquivo, lembre-se de comittar e enviar o resultado para o Dokku, usando git push dokku main como definido acima.

Como você pode imaginar, isso não vai ser suficiente: Estamos aqui usando o PDO com o PDO_PGSQL para conectar a um banco de dados PostgreSQL, que é um SGBD bastante completo. Só que, no momento, o Dokku não faz a mínima ideia dessa dependência do PostgreSQL pra começo de conversa. Portanto, vamos por partes: Vamos configurar o PostgreSQL no Dokku. Felizmente, graças ao fato de que o PHP que o Dokku usa vem com o PDO e o PDO_PGSQL (conforme listado aqui) ativado por padrão, não vamos precisar nos preocupar com a configuração do mesmo.

Para configurar o PostgreSQL no Dokku, primeiramente você precisa instalar o plugin dokku-postgres, que permite criar e gerenciar "serviços" PostgreSQL, e também integrá-los com sua aplicação. Para fazer isso, basta rodar o comando abaixo:

sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres

Agora, vamos criar o "serviço", que nada mais é do que o container Docker rodando o PostgreSQL. Esse serviço se chamará pgdb:

dokku postgres:create pgdb

Finalmente, vamos conectar o serviço criado com a nossa aplicação hello:

dokku postgres:link pgdb hello

Com isso feito, deveremos ter uma variável de ambiente (ou environment variable, em inglês) chamada DATABASE_URL com as informações necessárias para se conectar ao serviço pgdb, que é basicamente o nosso servidor PostgreSQL. Entretanto, falta uma coisa importante: criar a tabela visits, que o nosso script em PHP usa para contar as visitas. Para fazer isso, primeiro, conecte ao PostgreSQL usando o comando abaixo:

dokku postgres:connect pgdb

E então execute o seguinte SQL para criar a tabela visits:

CREATE TABLE visits(id INT PRIMARY KEY NOT NULL, num_visits INT NOT NULL);

Agora, acesse sua aplicação e verifique que, a cada vez que você atualiza a página, o contador é devidamente incrementado, como se espera de um contador de visitas.

Configurando sua aplicação

O Dokku fornece uma forma fácil de configurar sua aplicação através do uso de variáveis de ambiente, que são úteis para definir parâmetros diversos da sua aplicação. Alguns exemplos que me vem à mente são configurações do servidor de e-mail, configurações de log (para usos mais robustos além do tradicional dokku logs) e outras configurações mais específicas da sua aplicação, como...fuso horário padrão.

Para configurar uma variável de ambiente, você pode usar o comando dokku config. No script acima, se você mudar a linha:

$nome = htmlspecialchars( $_GET["nome"] ?? "Mundo" );

Por:

$nome = htmlspecialchars( $_GET["nome"] ?? getenv("DEFAULT_NAME") ?? "Mundo" );

Você se torna apto a usar a variável de ambiente DEFAULT_NAME para mudar o nome que deve aparecer por padrão caso o parâmetro não seja definido na URL. Com o Dokku, você pode configurar tal variável de ambiente usando o seguinte comando, que configurará o valor para "User":

dokku config:set DEFAULT_NAME=Usuário

Escalonando a aplicação

Vamos dizer que sua aplicação é um sucesso absoluto! E que, graças a isso, não é mais suficiente rodar sua aplicação com apenas um processo. O Dokku fornece um comando simples para ajudar nisso, inclusive configurando balanceamento de carga para você. Por exemplo, para ter 4 instâncias da sua aplicação rodando, você pode rodar o seguinte comando:

dokku ps:scale hello web=4

Note, entretanto, que as 4 instâncias da aplicação ainda estarão rodando dentro do mesmo servidor. Na maior parte dos casos, isso pode ser suficiente, em especial se o seu servidor possuir vários núcleos.

Entretanto, em um dado momento, se torna interessante ter vários servidores operando. Para fazer isso, o processo é BEM mais complicado, e envolve o uso de sistemas como o Kubernetes, com o qual é possível integrar usando um plugin do Dokku. Em virtude disso, eu não vou cobrir esse assunto aqui, mas, é algo que é interesante saber de qualquer forma. :)

Conclusão

Como é possível ver, o Dokku facilita e muito todo o processo de deploy e configuração da sua aplicação. No geral, todo o uso da ferramenta é muito inspirado pelo Heroku, que foi pioneiro nessa abordagem de deploy e configuração.

Apesar disso, o fato é que, honestamente, a grande maior parte dos projetos NÃO precisam de algo tão robusto e nem tão caro quanto o Heroku, e aí o Dokku começa a se tornar muito mais interessante, visto que com um simples VPS (que hoje você consegue contratar por menos de 5 dólares ao mês), você consegue hospedar diversas aplicações E ainda ter todas essas facilidades de forma bem tranquila, o que é útil em especial se as suas aplicações não tem como meta retorno financeiro (que é o meu caso).

"Mas, Fernando, eu trabalho para uma empresa pequena, vale a pena usar Dokku?" - Bom, eu diria que depende muito. Se vocês tem condições de ter um VPS (e há, por exemplo, há uma pessoa trabalhando com a infraestrutura da empresa), a resposta é... Talvez.

Na prática, entretanto, se você tem um projeto que possui retorno financeiro, quase sempre vale considerar fortemente o uso de um serviço como o Heroku (ou um de seus concorrentes), simplesmente pela paz de espírito que se tem ao poder dormir à noite ou passar o tempo livre, e também por todo o suporte que você tem ao usar algo especializado do tipo.

Bom, espero que tenham gostado da leitura, e deixem nos comentários: como é o processo de deploy do seu último projeto?

]]>
Logotipo do Dokku

Como programador, uma coisa que eu aprecio demais na nossa profissão é a possibilidade de criar side projects. Claro, relaxar é super importante, eu não nego, ainda mais para nós que trabalhamos basicamente pensando muito o dia inteiro.

Mas poder criar projetos que resolvem AQUELE problema ou necessidade chato que você tem - e ainda de quebra poder ajudar outras pessoas (ou até faturar algum $$$ extra, em alguns casos) - também é algo muito bem vindo.

O problema, como sempre, é que isso é basicamente...trabalho, e por mais interessante que seja poder customizar cada parte do seu próprio projeto, o fato é que quanto mais você demora para lançá-lo - por qualquer motivo que seja - maiores são as chances de você acabar desistindo e/ou partindo para outro projeto.

E é nesse ponto que entra o Dokku: um projeto que, após ser instalado em um servidor (VPS ou dedicado, conforme o seu bolso deixar), lhe permite facilmente criar novas aplicações e fazer deploy delas, usando um workflow muito parecido com o do Heroku, mas sem os custos extras (além do custo do servidor e do seu tempo, é claro).

O que é

O Dokku é um projeto que implementa um PaaS - Platform as a Service, ou Plataforma como Serviço, em inglês - e usa o Docker - que já apresentamos em outro post - para fazer suas "magias". Sua administração decorre de um simples comando chamado "dokku", que permite a criação de novos aplicativos (que é como o projeto chama os seus projetos) e também configuração dos mesmos.

Além disso, possui um ecossistema muito rico, com plugins que facilitam, entre outras coisas, a instalação e configuração de projetos como MySQL, PostgreSQL e RabbitMQ, e até mesmo facilitam a configuração de certificados SSL usando Let's Encrypt.

Depois de configurar o Dokku completamente, se torna possível publicar seu projeto com apenas alguns poucos comandos (a depender do que você usa no seu projeto), portanto tornando possível publicar o seu projeto muito rapidamente. Para que isso seja possível, entretanto, é necessário seguir algumas regras durante o desenvolvimento do seu projeto, chamado Twelve-Factor App, em especial no que diz respeito à configuração do projeto, mas isso é algo que qualquer framework moderno já suporta facilmente, então não chega a ser um grande problema.

Como instalar

Para instalar o Dokku, você precisa primeiramente de um servidor VPS ou dedicado, ou seja, não dá para usar uma hospedagem compartilhada com ele. Também é necessário que você possua um domínio, e que o servidor em questão rode uma distribuição Linux. Para simplificar o processo de instalação, vou mostrar aqui apenas os comandos para quem usa Ubuntu ou Debian no servidor. É plenamente possível usar o Dokku também com outras distribuições, mas, o processo será potencialmente mais complicado.

Se você quiser apenas usar o Dokku em seu próprio computador, digamos, para ver como funciona, também é possível usar o Vagrant - que apresentei em outro post - para rodá-lo. Apenas esteja ciente de que dessa forma os seus projetos NÃO serão publicados na internet. Segue aqui as instruções.

Com tudo isso dito, segue os comandos para instalar a última versão do Dokku - 0.30.2 - conforme disponível no momento de escrita desse post. Eu sugiro fortemente para que você verifique a última versão disponível do Dokku e ajuste o comando de forma correspondente abaixo, portanto, os comandos abaixo são apenas uma referência:

wget https://dokku.com/bootstrap.sh
sudo DOKKU_TAG=v0.30.2 bash bootstrap.sh

ATENÇÃO: Antes de rodar esses comandos de forma imediata, eu sugiro para que os rode individualmente, e, se possível, analise o conteúdo do arquivo baixado após a execução do primeiro comando. O Dokku é um projeto bastante confiável, mas, sempre vale ter essa prática por segurança, com todo e qualquer comando do tipo que você vê por aí.

Depois de rodar esses comandos e de instalar o Dokku, chegou a hora de configurar o domínio global que o Dokku usará. Uma questão importante sobre esse domínio é que, em um cenário ideal, o domínio deve ser configurado de forma que todo sub-domínio ainda não definido aponte para o servidor no qual o Dokku está instalado. Em termos técnico, isso significa que o DNS precisa ter uma entrada "wildcard" apontando para o seeu servidor. Isso NÃO é um requisito para o uso do Dokku, mas, facilita e muito a configuração de novos aplicativos, visto que você acaba não precisando configurar o DNS manualmente a cada vez que você vai criar um novo aplicativo (bom, pelo menos não se você quiser que sua aplicação rode em um..sub-domínio). Para referência, nesse artigo é mostrado como configurar uma entrada wildcard no Cloudflare. Consulte a documentação do servidor DNS que você usa para mais informações.

Com isso dito, segue o comando para configurar o domínio global no Dokku, onde SEU_DOMINIO é o dominio que o Dokku deve usar como global:

dokku domains:set-global SEU_DOMINIO

Depois de configurar o Dokku no seu servidor, uma última etapa que eu considero bastante importante é configurar um cliente do Dokku no seu próprio computador. Naturalmente, o processo varia bastante de acordo com a sua preferência, mas aqui está a lista de clientes que podem ser usados para se comunicar com o Dokku. Pessoalmente, eu gosto bastante do cliente oficial do Dokku, que é bastante simples e funciona por SSH, tornando toda a comunicação bastante segura e simples.

Como publicar sua primeira aplicação

Depois de configurado, publicar sua primeira aplicação é bem simples. Digamos que você tenha um simples repositório Git - que você pode ler mais sobre nesse post - com o seguinte arquivo - chamado index.php - comittado:

<?php
// Pega o nome da URL, ou assume o padrão "Mundo", e converte caracteres HTML de forma que não seja possível injetar JS/CSS na página:
$nome = htmlspecialchars( $_GET["name"] ?? "Mundo" );
// Imprime "Olá, " seguido do valor da variável acima
echo "Olá, {$nome}";

E também tenha no repositório os arquivos "composer.json" e "composer.lock" criados pelo Composer, sobre o qual já falei aqui (você pode rodar composer init e responder todas as perguntas para configurar o Composer corretamente).

Apesar da criação dos arquivos composer.json e composer.lock não ser SUPER necessária - visto que o Dokku consegue detectar que se trata de um projeto que usa PHP à partir da existência do arquivo index.php - é algo que é recomendado, visto que o modo de detecção a partir da existência de arquivos PHP é considerado depreciado.

Antes de fazer o deploy dessa aplicação, que vamos chamar aqui de hello, você primeiramente precisa criar a aplicação no Dokku. Para isso, você pode usar o seguinte comando, que usa o cliente oficial do Dokku mencionado anteriormente:

dokku apps:create hello

Depois de criado, você pode simplesmente rodar o seguinte comando para fazer o deploy do projeto, assumindo que a branch principal do repositório em questão é a branch main, e que você fez o commit nessa branch:

git push dokku main

Depois de rodar o comando, o Dokku analisará o conteúdo do repositório e rapidamente constatará que se trata de uma aplicação em PHP, usando um projeto chamado Herokuish. A partir daí, será usado o buildpack do Heroku para PHP para fazer o deploy da sua aplicação, que deverá estar disponível em hello.SEU_DOMINIO.

Como eu possuo Dokku instalado nesse mesmo servidor, você pode ver a aplicação rodando aqui: http://hello.fjorgemota.com/?nome=Teste - sinta-se a vontade para mudar o parâmetro nome para qualquer outra coisa apenas para experimento. 🙂

Conectando um banco de dados

Naturalmente, fazer uma simples aplicação assim não mostra o potencial completo da ferramenta. Em virtude disso, vamos complicar um pouquinho o exemplo: Em vez de um simples código PHP que printa um parâmetro recebido na URL, vamos fazer um contador de visitas...bem simples, mas enfim.

Para isso, no repositório Git criado anteriormente, adicione o seguinte código no final do arquivo index.php:

// Captura a URL da variável de ambiente e divide em partes
$parametros = parse_url( getenv( "DATABASE_URL" ) );
// Gera a URL para passar para o PDO, contendo os dados de configuração do banco de dados
$url = sprintf( "pgsql:host=%s;port=%d;dbname=%s;user=%s;password=%s", $parametros['host'], $parametros['port'],  substr( $parametros['path'], 1 ), $parametros['user'], $parametros['pass'] );
// Conecta oa banco de dados
$conexao = new PDO( $url );
// Faz a consulta para contar o número de visitas e retornar o número atualizado
$consulta = $conexao->query( "INSERT INTO visits ( id, num_visits ) VALUES( 1, 1 ) ON CONFLICT (id) DO UPDATE SET num_visits = visits.num_visits + EXCLUDED.num_visits RETURNING num_visits");
// Captura o número de visitas retornado pelo banco de dados
$visitas = $consulta->fetchColumn( 0 );
// Imprime o resultado na página
echo "<br /> Número de visitas: {$visits}";

Depois de adicionar esse script ao arquivo, lembre-se de comittar e enviar o resultado para o Dokku, usando git push dokku main como definido acima.

Como você pode imaginar, isso não vai ser suficiente: Estamos aqui usando o PDO com o PDO_PGSQL para conectar a um banco de dados PostgreSQL, que é um SGBD bastante completo. Só que, no momento, o Dokku não faz a mínima ideia dessa dependência do PostgreSQL pra começo de conversa. Portanto, vamos por partes: Vamos configurar o PostgreSQL no Dokku. Felizmente, graças ao fato de que o PHP que o Dokku usa vem com o PDO e o PDO_PGSQL (conforme listado aqui) ativado por padrão, não vamos precisar nos preocupar com a configuração do mesmo.

Para configurar o PostgreSQL no Dokku, primeiramente você precisa instalar o plugin dokku-postgres, que permite criar e gerenciar "serviços" PostgreSQL, e também integrá-los com sua aplicação. Para fazer isso, basta rodar o comando abaixo:

sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres

Agora, vamos criar o "serviço", que nada mais é do que o container Docker rodando o PostgreSQL. Esse serviço se chamará pgdb:

dokku postgres:create pgdb

Finalmente, vamos conectar o serviço criado com a nossa aplicação hello:

dokku postgres:link pgdb hello

Com isso feito, deveremos ter uma variável de ambiente (ou environment variable, em inglês) chamada DATABASE_URL com as informações necessárias para se conectar ao serviço pgdb, que é basicamente o nosso servidor PostgreSQL. Entretanto, falta uma coisa importante: criar a tabela visits, que o nosso script em PHP usa para contar as visitas. Para fazer isso, primeiro, conecte ao PostgreSQL usando o comando abaixo:

dokku postgres:connect pgdb

E então execute o seguinte SQL para criar a tabela visits:

CREATE TABLE visits(id INT PRIMARY KEY NOT NULL, num_visits INT NOT NULL);

Agora, acesse sua aplicação e verifique que, a cada vez que você atualiza a página, o contador é devidamente incrementado, como se espera de um contador de visitas.

Configurando sua aplicação

O Dokku fornece uma forma fácil de configurar sua aplicação através do uso de variáveis de ambiente, que são úteis para definir parâmetros diversos da sua aplicação. Alguns exemplos que me vem à mente são configurações do servidor de e-mail, configurações de log (para usos mais robustos além do tradicional dokku logs) e outras configurações mais específicas da sua aplicação, como...fuso horário padrão.

Para configurar uma variável de ambiente, você pode usar o comando dokku config. No script acima, se você mudar a linha:

$nome = htmlspecialchars( $_GET["nome"] ?? "Mundo" );

Por:

$nome = htmlspecialchars( $_GET["nome"] ?? getenv("DEFAULT_NAME") ?? "Mundo" );

Você se torna apto a usar a variável de ambiente DEFAULT_NAME para mudar o nome que deve aparecer por padrão caso o parâmetro não seja definido na URL. Com o Dokku, você pode configurar tal variável de ambiente usando o seguinte comando, que configurará o valor para "User":

dokku config:set DEFAULT_NAME=Usuário

Escalonando a aplicação

Vamos dizer que sua aplicação é um sucesso absoluto! E que, graças a isso, não é mais suficiente rodar sua aplicação com apenas um processo. O Dokku fornece um comando simples para ajudar nisso, inclusive configurando balanceamento de carga para você. Por exemplo, para ter 4 instâncias da sua aplicação rodando, você pode rodar o seguinte comando:

dokku ps:scale hello web=4

Note, entretanto, que as 4 instâncias da aplicação ainda estarão rodando dentro do mesmo servidor. Na maior parte dos casos, isso pode ser suficiente, em especial se o seu servidor possuir vários núcleos.

Entretanto, em um dado momento, se torna interessante ter vários servidores operando. Para fazer isso, o processo é BEM mais complicado, e envolve o uso de sistemas como o Kubernetes, com o qual é possível integrar usando um plugin do Dokku. Em virtude disso, eu não vou cobrir esse assunto aqui, mas, é algo que é interesante saber de qualquer forma. 🙂

Conclusão

Como é possível ver, o Dokku facilita e muito todo o processo de deploy e configuração da sua aplicação. No geral, todo o uso da ferramenta é muito inspirado pelo Heroku, que foi pioneiro nessa abordagem de deploy e configuração.

Apesar disso, o fato é que, honestamente, a grande maior parte dos projetos NÃO precisam de algo tão robusto e nem tão caro quanto o Heroku, e aí o Dokku começa a se tornar muito mais interessante, visto que com um simples VPS (que hoje você consegue contratar por menos de 5 dólares ao mês), você consegue hospedar diversas aplicações E ainda ter todas essas facilidades de forma bem tranquila, o que é útil em especial se as suas aplicações não tem como meta retorno financeiro (que é o meu caso).

"Mas, Fernando, eu trabalho para uma empresa pequena, vale a pena usar Dokku?" - Bom, eu diria que depende muito. Se vocês tem condições de ter um VPS (e há, por exemplo, há uma pessoa trabalhando com a infraestrutura da empresa), a resposta é... Talvez.

Na prática, entretanto, se você tem um projeto que possui retorno financeiro, quase sempre vale considerar fortemente o uso de um serviço como o Heroku (ou um de seus concorrentes), simplesmente pela paz de espírito que se tem ao poder dormir à noite ou passar o tempo livre, e também por todo o suporte que você tem ao usar algo especializado do tipo.

Bom, espero que tenham gostado da leitura, e deixem nos comentários: como é o processo de deploy do seu último projeto?

]]>
https://fjorgemota.com/2023/03/30/dokku-deploy-simplificado-para-os-seus-projetos/feed/ 1 6337