Автор: Денис Матаков

  • Как передать переменные окружения GitHub Actions Secrets на удаленный сервер для docker-compose?

    Как передать переменные окружения GitHub Actions Secrets на удаленный сервер для docker-compose?

    Иногда самые сложные вопросы имеют самые легкие ответы. Когда мы работаем на локальной машине, нам проще всего взять наши переменные окружения (environment variables) из файла .env при работе с docker-compose. Но ведь в таком виде секреты передавать в репозиторий нельзя! Значит, мы в лучшем случае запишем туда .env.template, где перечислим переменные и, возможно, дадим им некоторые значения по умолчанию для примера.

    Очевидно, что сами секреты мы заведем в настройках репозитория, в разделе GitHub Secrets:

    Но как их дальше передать на сервер? Первое, что хочется сделать — сформировать на сервере .env файл неким скриптом, но есть путь гораздо проще и чище, давайте используем стандартную команду export, которая в командной среде может присваивать переменную окружения и хранить ее прямо в системе, без каких-либо файлов! Добавим GitHub Action следующего вида:

        runs-on: ubuntu-latest
        needs: set_up_env
        steps:
          - name: executing remote ssh commands to set env
            uses: appleboy/ssh-action@master
            with:
              host: ${{ secrets.HOST }}
              username: ${{ secrets.USER }}
              key: ${{ secrets.SSH_KEY }}
              script: |
                export DB_ENGINE=${{ secrets.DB_ENGINE }}
                export DB_NAME=${{ secrets.DB_NAME }}
                export POSTGRES_USER=${{ secrets.POSTGRES_USER }}
                export POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
                export DB_HOST=${{ secrets.DB_HOST }}
                export DB_PORT=${{ secrets.DB_PORT }}
                export SECRET_KEY=${{ secrets.SECRET_KEY }}
                export DJANGO_SUPERUSER_PASSWORD=${{ secrets.DJANGO_SUPERUSER_PASSWORD }}
                export DJANGO_SUPERUSER_EMAIL=${{ secrets.DJANGO_SUPERUSER_EMAIL }}
                export DJANGO_SUPERUSER_USERNAME=${{ secrets.DJANGO_SUPERUSER_USERNAME }}

    Также можно на лету генерировать .env файл, если это то, к чему вы больше привыкли:

                echo DB_ENGINE=${{ secrets.DB_ENGINE }} > .env
                echo DB_NAME=${{ secrets.DB_NAME }} >> .env
                echo POSTGRES_USER=${{ secrets.POSTGRES_USER }} >> .env
                echo POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} >> .env
                echo DB_HOST=${{ secrets.DB_HOST }} >> .env
                echo DB_PORT=${{ secrets.DB_PORT }} >> .env
                echo SECRET_KEY=${{ secrets.SECRET_KEY }} >> .env
                echo DJANGO_SUPERUSER_PASSWORD=${{ secrets.DJANGO_SUPERUSER_PASSWORD }} >> .env
                echo DJANGO_SUPERUSER_EMAIL=${{ secrets.DJANGO_SUPERUSER_EMAIL }} >> .env
                echo DJANGO_SUPERUSER_USERNAME=${{ secrets.DJANGO_SUPERUSER_USERNAME }} >> .env

    Возможно, здесь много повторяющего кода, и хотелось бы записать это одной строкой с одним экспортом, но так, на мой взгляд, проще и понятнее. А также небольшая формула в экселе позволяет это довольно быстро сгенерировать:

    Теперь все переменные будут автоматически подставляться в docker-compose.

    env_file:
          - ./.env

    Которая, как известно, сканирует файл .env в поиске переменных. А все можно прочитать в документации:

    When you set the same environment variable in multiple files, here’s the priority used by Compose to choose which value to use:

    1) Compose file
    2) Shell environment variables
    3) Environment file
    4) Dockerfile
    5) Variable is not defined

    Сначала env файл, а затем уже переменные Shell. Это позволяет нам на компьютере для тестирования использовать один набор переменных, а для деплоя — другой.

  • Как извлечь docker-compose.yaml из образа на сервере?

    Как извлечь docker-compose.yaml из образа на сервере?

    В рамках Continuous Integration нам нужно уметь разворачивать не только контейнеры командой docker, но и группу контейнеров, например django + postgres + nginx.

    На локальной машине это просто, команда docker-compose up -d выполнит все инструкции из файла docker-compose.yaml, но на сервере его нет.

    Существует два способа доставить файл docker-compose.yaml на удаленный сервер. Мы не будем рассматривать вариант «доставить руками», ведь у нас непрерывная интеграция, а значит на каждый коммит и пуш в репозиторий на GitHub наш проект после прохождения всех тестов должен развернуться самостоятельно на сервере.

    Вариант 1

    Используя CI на базе GitHub Actions, мы можем воспользоваться сторонним действием, которое позволяет нам подключиться к удаленному серверу и скопировать произвольный файл из репозитория, который предварительно развернут на сервере GitHub:

          - name: copy docker-compose.yaml file to remote
            uses: appleboy/scp-action@master
            with:
              host: ${{ secrets.HOST }}
              username: ${{ secrets.USER }}
              key: ${{ secrets.SSH_KEY }}
              passphrase: ${{ secrets.PASSPHRASE }}
              source: "./docker-compose.yaml"
              target: "./code/"

    Обратите внимание, в разделе GitHub Secrets вам нужно будет указать необходимые переменные. Если вы не используете пароль на ваш SSH KEY — просто не указываете эту строчку.

    Повторимся, копирует он с виртуальной машины GitHub, и для того, чтобы на ней были ваши файлы, необходимо перед этим действием выполнить команду сбора файлов:

          - name: Check out the repo
            uses: actions/checkout@v2

    Вариант 2

    Подключиться через SSH в GitHub Actions к серверу и достать нужный файл из образа, который мы скачаем с Docker Hub:

          - name: executing remote ssh commands to deploy
            uses: appleboy/ssh-action@master
            with:
              host: ${{ secrets.HOST }}
              username: ${{ secrets.USER }}
              key: ${{ secrets.SSH_KEY }}
              script: |
                sudo docker pull ${{ secrets.DOCKER_USERNAME }}/yamdb:latest
                id=$(docker create ${{ secrets.DOCKER_USERNAME }}/yamdb:latest)
                sudo docker cp $id:code/docker-compose.yaml docker-compose.yaml
                sudo mkdir nginx
                sudo docker cp $id:code/nginx/Dockerfile nginx/Dockerfile
                sudo docker cp $id:code/nginx/nginx.conf nginx/nginx.conf
                sudo docker rm -v $id

    Давайте разберем, что происходит. Вы подключаетесь по SSH, скачиваете ваш образ, дальше вы создаете временный контейнер из этого образа и кладете его имя в переменную id.

    Следом идут обычные команды копирования из контейнера на хостовую машину. Мы берем файлы для docker-compose, в том числе настройки nginx, а затем удаляем ненужный нам контейнер. Все, заключительным действием мы на всякий случай выключим и удалим все контейнеры (если это не первый наш деплой, то действие поможет избежать конфликтов) и запустим наши контейнеры:

                docker kill $(docker ps -q)
                docker rm $(docker ps -a -q)
                sudo docker-compose up -d --force-recreate

    Обратите внимание на флаг -d в последней команде — если его забыть, то логи докера никогда не закончатся и ваш CI никогда не завершится успешно.

  • SSL и открытые порты в Docker — как защитить соединение?

    SSL и открытые порты в Docker — как защитить соединение?

    В случае, когда за SSL сертификаты для докеризированных приложений отвечает внешний веб-сервер (например, Caddy), нужно очень внимательно отнестись к тому, как вы открываете порты в своем docker-compose.

    Обычная практика такая:

      nginx:
        build: ./nginx
        ports:
          - 8888:80
        volumes:
          - static_sp2:/code/static
          - media_sp2:/code/media
        depends_on:
          - web
        restart: "on-failure"

    Но здесь есть серьезная проблема. Ваш nginx теперь доступен не только по адресу localhost или 127.0.0.1, но и всему миру, если у сервера есть публичный адрес.

    Мы же выпускаем сертификат с помощью Caddy, значит доступ к сайту будет защищен только если пользователь придет по доменному имени. А что если он забьет ip адрес и добавить в конце порт 8888? Так он получит доступ к сайту без SSL, и может воспользоваться данной возможностью множеством неприятных способов.

    Поэтому мы будем создавать контейнеры правильно, мы будем их показывать только приложениям, которые крутятся на нашем сервере:

      nginx:
        build: ./nginx
        ports:
          - "127.0.0.1:8888:80"
        volumes:
          - static_sp2:/code/static
          - media_sp2:/code/media
        depends_on:
          - web
        restart: "on-failure"

    Обратите внимание, что с появлением локального айпи адреса нам пришлось взять все выражение в кавычки, иначе докер не сможет достойно распарсить данную строку.

    Смотрим, что показывает нам nmap:

    user@server:~$ nmap 127.0.0.1
    Starting Nmap 7.91 ( https://nmap.org ) at 2021-02-24 06:42 UTC
    Nmap scan report for localhost (127.0.0.1)
    Host is up (0.00011s latency).
    Not shown: 993 closed ports
    PORT     STATE SERVICE
    22/tcp   open  ssh
    80/tcp   open  http
    443/tcp  open  https
    5000/tcp open  upnp
    5432/tcp open  postgresql
    8000/tcp open  http-alt
    8888/tcp open  sun-answerbook
    

    Видим, что внутри порт 888 открыт. Сканируем порты по внешнему доменному имени:

    matakov@matacoder:~$ nmap site.ru
    Starting Nmap 7.91 ( https://nmap.org ) at 2021-02-24 06:44 UTC
    Nmap scan report for site.ru (178.154.254.176)
    Host is up (0.0011s latency).
    Not shown: 997 filtered ports
    PORT    STATE SERVICE
    22/tcp  open  ssh
    80/tcp  open  http
    443/tcp open  https
    

    Все, уязвимость ушла, наружу смотрит только SSH, HTTP и HTTPS.

  • Простой SSL сертификат на базе веб-сервера Caddy, Django и Gunicorn

    Простой SSL сертификат на базе веб-сервера Caddy, Django и Gunicorn

    В рамках учебного проекта мне потребовалось развернуть на одном сервере два приложения. Первое из них — классический Django Framework, который крутится с помощью Guncorn, файлы лежат прямо на сервере. Второе же хитрое — полностью докеризированная связка Nginx + Gunicorn + PostgreSQL.

    Поднять незащищенное соединение совсем не сложно, у нас наружу светятся два порта, один от Gunicorn, один от Nginx из докера. Но как защитить все это дело? Если для серверного приложения с помощью Certbot это делается легко, то для докера настройки связки Certbot в контейнере и Nginx в контейнере — занятие не для слабонервных. А для того, чтобы сертификат заработал, нам потребовалось бы еще и пробрасывать 443 порт на докер, а сделать это можно двумя способами — либо не расшифровывая трафик через stream директиву Nginx, либо расшифровывая, но придется указать пути до ключей и, что самое сложное, понадобиться дать certbot положить файл в корневую директорию сайта для проверки соединения, а она у нас благодаря гуникорну не так-то просто достижима.

    В качестве решения можно использовать сервер Caddy. Идея будет заключаться в том, что за все сертификаты будет отвечать только она, за ней уже пойдет незашифрованный трафик до нужных нам портов.

    Установить Caddy просто, несколько команд в баше:

    sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https 
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo apt-key add - 
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list 
    sudo apt update sudo apt install caddy

    Дальше необходимо создать конфиругацию. У кадди есть особенность, файл конфига может лежать где угодно. Я оставил его в домашней директории, он должен быть назван Caddyfile. Вот содержимое:

    site-1-on-server.ru {
    root * /home/user/site/
    @notStatic {
    not path /static/*
    not path /media/*
    }
    reverse_proxy @notStatic 127.0.0.1:8000
    file_server
    }
    site-2-in-docker.ru {
    reverse_proxy localhost:8888
    }

    Этот конфиг позволяет запустить сайт1, который у нас лежит на сервере в указанной папке. Директива notStatic создает исключения для Кадди, эти файлы она не будет отправлять в Gunicorn и будет сама обслуживать. Обратный прокси — стандартная как для Ngnix, так и для Кадди операция, мы передаем все запросы дальше, на 8000 порт Гуникорна.

    Вторая часть конфига не делает каких-либо исключений для статики, так как у нас в контейнерах крутится Nginx и он ответственный за статику.

    А теперь немного магии от Кадди. Когда мы в директории, где у нас каддифайл лежит, запустим команду sudo caddy start, она автоматически выпустит SSL сертификаты для обоих доменов и будет их обновлять, проверяя оставшееся время каждые 10 дней. То есть самая сложная часть в случае с приложением, которое лежит на сервере — указать пути до статики, а если поднимается докеризированное приложение — то всего две строки и готово.

  • Как быть стоиком?

    Как быть стоиком?

    Оказывается, я стоик. И тут нет никаких аналогий со словом «стоять» или мыслью «стоять на своем», нет. Стоя — это место, где первые стоики собирались и обсуждали свою философию.

    По сути это очень хорошо проработанная религия, причем я не про божественные проявления, а про философию, своды, заветы. А в качестве бога в стоицизме оставлен широкий спектр для домысливания, так, что любая религия или даже атеисты смогут использовать в своей жизни принципы и не поступиться с догматами как своего верования, так и учения Зенона, основателя.

    Тут даже немного про политику есть, про любовь к отчизне, чтобы она не делала.

    Книга отлично подойдет в качестве ориентира для тех, кто до многого додумался сам, но еще не сложил целой картины мира, а также для широкого круга читателей, готовых стать лучше.

  • Карты. Деньги. Фитнес-клуб.

    Карты. Деньги. Фитнес-клуб.

    Так как я уже два с половиной года консультирую фитнес-клуб по всем экономическим и техническим вопросам, не обходит меня стороной и литература, посвященная этому. Решил уточниться по продажам, так как приходится внедрять различные решения, которые им помогают. И приятно удивлен тем, насколько, согласно Александру Шумилину и Татьяне Ивановой хорошо отстроена у нас как философия, так и техническая часть продаж.

    Читая книгу, вспомнил свой первый опыт фитнеса. Я был откровенно напуган возможностями клуба, и этот страх несколько лет удерживал меня от покупки клубной карты. А мне всего лишь нужен был либо ментор, либо развернутая карта клуба с подробным руководством.

    Основная мысль, соответственно, здесь даже не про продажи, а про продления. Не забывать клиента, вести его, интересоваться, предлагать попробовать то, что он забыл или не нашел. Или, как в моем случае, испугался.

    Вроде как очевидные вещи, но мало получить знание. Нужно его применить и получить умение. А потом применить еще сотню раз и получить опыт.

  • Ген директора. 17 правил позитивного менеджмента по-русски (Владимир Моженков)

    Ген директора. 17 правил позитивного менеджмента по-русски (Владимир Моженков)

    Давно не слушал книги в дороге, и зря. В долгой поездке в Москву можно получить концентрированную мудрость без каких-либо отвлечений, особенно на новой платной дороге М11.

    Ген директора — отличная книга. Я вынес оттуда несколько вроде бы как всегда очевидных мыслей, но почему-то пока не услышишь, что кто-то это использует — это остается всего лишь мыслью. А теперь буду внедрять в свою жизнь. Начну, пожалуй, с планирования, как бизнеса, так и собственной жизни. Нужно понимать к чему движешься.

    Отдельное спасибо Алексею Румянцеву, который порекомендовал эту книгу.

  • Первая фотография черной дыры

    Первая фотография черной дыры

    Решил нарушить долгое молчание в блоге ради одного из величайших событий в истории человечества. Мы построили телескоп размером с Землю, увидели то, о чем только догадывались, подтвердили теории многих великий ученых.

    10 апреля — великий день в истории человечества.

    А на промо картинке к записи вы можете увидеть сравнительные размеры, если наложить нашу солнечную систему на черную дыру.

    Источник: NSF

  • История за час: Гитлер (автор: Руперт Колли)

    История за час: Гитлер (автор: Руперт Колли)

    После подробнейшей биографии Сталина, написанной Эдвардом Радзинским, я захотел узнать и про Гитлера больше, чем дают в школьной истории. И снова помог Патефон — прослушал книгу как на одном дыхании.

    Я многое что уже знал, но теперь все факты выстроились в моей голове в правильной временной линии. Удивительно, как все получилось. Сколько факторов сложилось, чтобы началась Вторая Мировая Война.

    История — вот наш главный учитель.

    Слушать на Патефоне »

  • Странная история доктора Джекила и мистера Хайда (Роберт Льюис Стивенсон)

    Странная история доктора Джекила и мистера Хайда (Роберт Льюис Стивенсон)

    Моя первая аудиокнига, прослушанная в приложении Патефон, который посоветовал Леонид Парфенов в Парфеноне. Давно хотел попробовать, но как-то сомневался. А теперь точно знаю, что это удобно.

    Книга, кстати, очень интересная.

    https://patephone.com/product/6581