--- tags: web,day3 robots: noindex, nofollow lang: pt-br --- # 30 Container Vamos colocar nossa aplicação para rodar dentro de containers mas antes vamos alinhas alguns conceitos. ## O que é um container? Container é um ambiente isolado do restante do sistema operacional, utiliza alguns recursos compartilhados como processamento e memória porém tem a sua árvore de execução de processos e escalonamento de permissões separado do restante do sistema. Além disso os containers são iniciados a partir de uma imagem base que pode ser reutilizada para iniciar várias instâncias de container. ## Containerizando a app Django ### 1. Dockerfile O Dockerfile é o arquivo a partir de onde o Docker (ou outro sistema de builds de container como buildah) irá ler as diretivas e criar uma imagem de container especifica para nossa aplicação. Dentro do Dockerfile vamos colocar algumas diretivas como: - `FROM` especifica a imagem que servirá de base - `WORKDIR` especifica o diretório corrente dentro do container - `RUN` executa um comando no shell do container - `USER` altera o usuário atual logado no container - `COPY` copia arquivos locais para dentro do container - `ARG` especifica argumentos que podemos customizar ao rodar o container - `ENV` seta variáveis de ambiente - `ENTRYPOINT` aponta para um executavél que roda sempre que usarmos `run` no container - `CMD` especifica o comando padrão a ser passado ao entrypoint Com essas diretivas podemos fazer exatamente como fizemos com o **Ansible** na aula anterior e automatizar os passos que usamos para construir a VM, porém ao invés de uma VM estaremos contruindo uma imagem de container que tem caracteristicas diferentes. A recomendação é que o Dockerfile fique na raiz do nosso projeto, portanto: `exemplos/day2/django/Dockerfile` ```docker FROM python:3.10.6-slim-bullseye AS app WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential curl libpq-dev \ && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ && apt-get clean \ && useradd --create-home python \ && chown python:python -R /app \ && mkdir /static \ && chown python:python -R /static USER python # Copy local django/ contents to container /app (WORKDIR) COPY --chown=python:python . . ARG DEBUG="false" ENV DEBUG="${DEBUG}" \ PYTHONUNBUFFERED="true" \ PYTHONPATH="/app" \ PATH="${PATH}:/home/python/.local/bin" \ USER="python" \ BLOG_STATIC_ROOT="/static/" \ BLOG_STATIC_URL="/static/" \ BLOG_DEBUG="${DEBUG}" \ BLOG_ALLOWED_HOSTS='["*"]' \ BLOG_DATABASES__default__ENGINE="django.db.backends.postgresql" \ BLOG_DATABASES__default__NAME="blog" \ BLOG_DATABASES__default__USER="blog" \ BLOG_DATABASES__default__HOST="blog_postgres" \ DJANGO_SETTINGS_MODULE="djblog.settings" RUN python -m pip install install /app gunicorn psycopg2 RUN if [ "${DEBUG}" = "false" ]; then \ SECRET_KEY=dummyvalue python manage.py collectstatic --no-input; \ fi ENTRYPOINT [ "/app/docker/entrypoint.sh" ] EXPOSE 8000 CMD ["python -m gunicorn", "-c", "python:djblog.gunicorn", "djblog.wsgi"] ``` Para construir a imagem principal usamos alguns artefatos extras que teremos que criar: `exemplos/day2/django/djblog/gunicorn.py` ```python import os import multiprocessing bind = f"0.0.0.0:{os.getenv('PORT', '8000')}" accesslog = "-" access_log_format = "%(h)s %(l)s %(u)s %(t)s '%(r)s' %(s)s %(b)s '%(f)s' '%(a)s' in %(D)sµs" # noqa: E501 workers = int(os.getenv("WEB_CONCURRENCY", multiprocessing.cpu_count() * 2)) threads = int(os.getenv("PYTHON_MAX_THREADS", 1)) reload = os.getenv("WEB_RELOAD","").lower() in ("1", "true") ``` Este script utiliza Python para configurar o `gunicorn` e a utilidade dele é calcular a quantidade de workers baseando-se na quantidade de CPUs disponíveis. `exemplos/day2/django/docker/entrypoint.sh` ```bash #!/usr/bin/env bash set -e # python manage.py migrate --noinput $@ ``` O script de ponto de entrada que irá executar sempre que o container iniciar e é nele que colocamos coisas como comandos iniciais de deploye outras utilidades casos seja necessário. ### Build Para construir a imagem a partir do Dockerfile usaremos o seguinte comando: ```bash cd exemplos/day2/django docker build -t django-blog:latest . ``` ``` Successfully built 4a8e606af015 Successfully tagged django-blog:latest ``` ### Executando Agora se tentarmos executar obteremos um erro: ```bash docker run --rm -it -p 8000:8000 django-blog Traceback (most recent call last): File "/home/python/.local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 282, in ensure_connection self.connect() File "/home/python/.local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner return func(*args, **kwargs) File "/home/python/.local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 263, in connect self.connection = self.get_new_connection(conn_params) File "/home/python/.local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner return func(*args, **kwargs) File "/home/python/.local/lib/python3.10/site-packages/django/db/backends/postgresql/base.py", line 215, in get_new_connection connection = Database.connect(**conn_params) File "/home/python/.local/lib/python3.10/site-packages/psycopg2/__init__.py", line 122, in connect conn = _connect(dsn, connection_factory=connection_factory, **kwasync) psycopg2.OperationalError: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"? ``` E isso acontece pois em nosso Dockerfile temos a seguinte configuração: ```bash BLOG_DATABASES__default__ENGINE="django.db.backends.postgresql" BLOG_DATABASES__default__NAME="blog" BLOG_DATABASES__default__USER="blog" ``` E a aplicação tentará se conectar a um banco de dados `PostgreSQL` que não temos sendo executado. Em containers rodamos cada serviço em seu container, portanto precisaremos de mais containers na nossa arquitetura. ### Compose Existem alguns orquestradores de containers e entre eles temos Kubernetes para ambiente de produção e o docker-compose sendo o principal para ambientes de desenvolvimento. #### Quais são os padrões para containers? Cada container deve executar apenas um único processo, ou seja, servidor de aplicação, banco de dados, serviço de filas, web server, cada um em seu container separado e orquestrado via um orquestrador de containers. #### Como a arquitetura se parece? ![](https://github.com/rochacbruno/python-web-api/raw/day3/docs/day3/imgs/containers.png) `exemplos/day2/django/docker/docker-compose.yaml` ```yaml volumes: blog_pg_data: name: blog_pg_data blog_static: name: blog_static services: django: image: compose-django-blog:latest build: context: .. dockerfile: Dockerfile expose: - 8000 volumes: - "blog_static:/static" depends_on: - blog_postgres environment: - BLOG_STATIC_ROOT=/static/ - BLOG_STATIC_URL=/static/ - BLOG_DEBUG=false - BLOG_ALLOWED_HOSTS=["*"] - BLOG_DATABASES__default__ENGINE=django.db.backends.postgresql - BLOG_DATABASES__default__NAME=blog - BLOG_DATABASES__default__USER=blog - BLOG_DATABASES__default__PASSWORD=blog - BLOG_DATABASES__default__HOST=blog_postgres - DJANGO_SETTINGS_MODULE=djblog.settings - PYTHONPATH=/app - PATH="${PATH}:/home/python/.local/bin" blog_postgres: image: "postgres:12" expose: - 5432 environment: - POSTGRES_USER=blog - POSTGRES_PASSWORD=blog - POSTGRES_DB=blog volumes: - "blog_pg_data:/var/lib/postgresql/data" blog_nginx: image: nginx:1.21-alpine ports: - "80:80" volumes: - "blog_static:/usr/share/nginx/html" - "./nginx.conf:/etc/nginx/conf.d/default.conf:z" depends_on: - django ``` #### Iniciando o compose ```bash docker-compose down -v docker-compose up -d --build docker-compose exec django python manage.py migrate --noinput docker-compose exec django python manage.py createsuperuser --username=admin --email=admin@example.com ```