Como rodar um php-fpm server atrás de um nginx
Problema atual
Eu precisava colocar nossos servers em PHP para rodar dentro de containers no Docker, para isso temos duas soluções uma usando a imagem do docker oficial que já vem com o apache instalado e configurado:
1
FROM php:7.2-apache
ou utilizar uma imagem customizada com php+apache ou nginx.
Mas eu teimoso queria uma coisa mais simples que rodasse o PHP com o minimo de espaço ocupado pelas imagens e pudesse utilizar o nginx para gerenciar os sites/proxy reverso.
Entra em cena php-fpm.
1
2
3
4
$ docker images php
REPOSITORY TAG IMAGE ID CREATED SIZE
php 7.2-fpm 03d449391aab 10 days ago 398MB
php 7.2-apache ea7f5666bfc1 10 days ago 410MB
O que é PHP-FPM
FPM é um gerenciador de processos para gerenciar o FastCGI SAPI (Server API) em PHP.
O PHP-FPM é um serviço e não um módulo. Este serviço é executado completamente independente do servidor web em um processo à parte e é suportado por qualquer servidor web compatível com FastCGI (Fast Common Gateway Interface).
PHP-FPM é consideravelmente mais rápido que os outros métodos de se processar scripts php, e também é escalável, ou seja é possível construir clusters e expandir a capacidade do PHP de receber requisições.
O que é NGINX
NGINX, pronunciado “engine-ex,” é um famoso software de código aberto para servidores web lançado originalmente para navegação HTTP. Hoje, porém, ele também funciona como proxy reverso, balanceador de carga HTTP, e proxy de email para os protocolos IMAP, POP3, e SMTP.
Resolução do meu problema?
Foi demorado conseguir chegar num exemplo que esteja funcionando 100%, muitas contradições em configurações, principalmente do nginx.
Mas após algum tempo procurando e juntando pedaços de respostas do stackoverflow
consegui chegar num exemplo funcional, com 2 sites rodando php + 1 rodando node, atrás de um nginx.
Como testar você mesmo
Se não quiser fazer manualmente pegue o projeto de exemplo aqui https://github.com/brilvio/php-fpm-mssql-nginx/
Crie uma pasta onde irá rodar os testes e entre na mesma
1
2
$ mkdir teste
$ cd teste
Crie uma pasta chamada site-a
1
2
$ mkdir site-a
$ cd site-a
Dentro da pasta site-a
crie uma pasta php
e dentro dela crie um arquivo index.php
com o seguinte conteúdo
1
2
<?
echo "Bem vindo ao Site A";
Volte a pasta site-a
e crie um arquivo chamado Dockerfile
com o seguinte conteúdo
1
2
3
FROM brilvio/php-fpm-mssql
COPY ./php /var/www/html/site-a
Se você é familiarizado com o Docker verá que é bem simples ele irá pegar como base a minha imagem já configurado com o conector para mssql
e copiar os arquivos da pasta ./php
para a pasta dentro do container /var/www/html/site-a
é importante que essa pasta dentro do container concida depois com o nome da pasta dentro do container do nginx.
Rode o comando na pasta site-a
para gerar a imagem do docker com a tag correta
1
$ docker build -t site-a .
Crie agora mais uma pasta chamada site-b
modificando o Dockerfile
e o index.php
de acordo.
Rode o comando na pasta site-b
para gerar a imagem do docker com a tag correta
1
$ docker build -t site-b .
Para o site-c
vai ser um pouco diferente pois ele vai rodar um servidor node+express dentro dele, crie a pasta normal, porém invés de uma pasta php
crie uma pasta chamada src
e dentro dela o arquivo index.js
com o seguinte conteúdo.
1
2
3
4
5
6
7
8
9
10
11
const express = require('express');
const app = express();
const port = 80;
app.get('/', (req, res) => {
res.send('Hello from express!');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
Na pasta site-c
crie um arquivo package.json
com o seguinte conteúdo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "site-c",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
}
}
E um arquivo Dockerfile
com o seguinte conteúdo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FROM node:12-alpine as compile-image
RUN apk update && apk add yarn curl bash python g++ make && rm -rf /var/cache/apk/*
RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | bash -s -- -b /usr/local/bin
WORKDIR /usr/src/
COPY package.json ./
RUN yarn install
COPY src/ ./
RUN npm prune --production
RUN /usr/local/bin/node-prune
FROM node:12-alpine
WORKDIR /usr/src/
COPY --from=compile-image /usr/src/node_modules ./node_modules
COPY --from=compile-image /usr/src/ ./
EXPOSE 80
CMD [ "node", "index.js" ]
Rode o comando na pasta site-c
para gerar a imagem do docker com a tag correta
1
$ docker build -t site-c .
Estamos quase lá, vamos criar agora as configurações que o nginx vai usar para saber para onde enviar as requisições, crie uma pasta sites e dentro dela crie as seguintes configurações.
site-a.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server {
root /var/www/html/site-a; # deve ser a mesma pasta dentro do Dockerfile
index index.php index.html index.htm;
server_name site-a.docker;
index index.php index.html;
client_max_body_size 108M;
gzip_static on;
location / {
if (!-e $request_filename) {
rewrite ^.*$ /index.php last;
}
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name)
{
return 404;
}
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
fastcgi_read_timeout 150;
fastcgi_buffers 4 256k;
fastcgi_buffer_size 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_pass site-a:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
site-b.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server {
root /var/www/html/site-b; # deve ser a mesma pasta dentro do Dockerfile
index index.php index.html index.htm;
server_name site-b.docker;
index index.php index.html;
client_max_body_size 108M;
gzip_static on;
location / {
if (!-e $request_filename) {
rewrite ^.*$ /index.php last;
}
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name)
{
return 404;
}
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
fastcgi_read_timeout 150;
fastcgi_buffers 4 256k;
fastcgi_buffer_size 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_pass site-b:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
site-c.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
upstream site-c {
server site-c:80;
}
server {
server_name site-c.docker;
client_max_body_size 200M;
location / {
proxy_pass http://site-c/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forward-Proto http;
proxy_set_header X-Nginx-Proxy true;
proxy_redirect off;
}
listen 80;
}
Veja que o site-c.conf
é mais simples, é por que ele é somente um proxy reverso.
Vamos agora a parte mais fácil, criar o docker-compose.yml
que vai subir os nossos servidores.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
version: '3.3'
services:
nginx:
image: nginx:latest
container_name: nginx
restart: always
volumes:
- type: volume
source: site-a
target: /var/www/html/site-a # deve ser a mesma pasta dentro do Dockerfile
- type: volume
source: site-a
target: /var/www/html/site-b # deve ser a mesma pasta dentro do Dockerfile
- ./sites:/etc/nginx/conf.d/
ports:
- '8080:80'
links:
- site-a
- site-b
- site-c
site-a:
image: site-a:latest
container_name: site-a
expose:
- '9000'
volumes:
- type: volume
source: site-a
target: /var/www/html/site-a # deve ser a mesma pasta dentro do Dockerfile
site-b:
image: site-b:latest
container_name: site-b
expose:
- '9000'
volumes:
- type: volume
source: site-b
target: /var/www/html/site-b # deve ser a mesma pasta dentro do Dockerfile
site-c:
image: site-c:latest
container_name: site-c
expose:
- '80'
volumes:
site-a:
site-b:
Somente os site-a
e site-b
tem volumes configurados, é porque o nginx precisa ter os arquivos .php
dentro dele também e nas mesmas pastas para poder passar para o servidor php-fpm interpretar.
Único problema é que se você quiser atualizar os arquivos php dentro dos containers tu vai ter que remover os volumes e recria-los novamente.
Configure no teu arquivo hosts /etc/hosts
1
2
3
127.0.0.1 site-a
127.0.0.1 site-b
127.0.0.1 site-c
Suba os servidores com comando:
1
$ docker-compose up -d
Ao acessar no browser http://site-a:8080 http://site-b:8080 ou http://site-c:8080 verá que o nginx está redicionando para os servidores corretamente.