
Я давно хотел хостить блог самостоятельно и наконец-то сделал это. Главное, что меня останавливало до этого момента — это отсутствие опыта в DevOps и перфекционизм в том, как сделать все проще для себя.
Все, что я хотел делать — это писать посты и пушить их в какой-нибудь репозиторий. Другая сторона этого pipeline выглядела слишком сложной для реализации.
С другой стороны, все сложное — это весело. Когда есть веселье, сложные вещи становятся проще, вы получаете и знания, и удовольствие одновременно.
Это история успеха о том, как я настроил continuous deployment для блога.
TODO
- Создать сервер на удаленном VPS
- Настроить HTTPS для домена с помощью Let's Encrypt
- Настроить сервер один раз
- Сделать так, чтобы было легко настроить на любой другой машине
- Писать посты локально в markdown
- Коммитить и пушить изменения в Github Repository
- Публиковать изменения на сервер автоматически
Hugo
Я не буду объяснять здесь ничего специфичного для Hugo. Hugo — это генератор статических сайтов, который создает директорию public со всем содержимым сайта. Этот контент нужно раздавать. Я буду использовать Nginx для этого.
Dockerfile для сборки docker-образа из директории public с использованием Nginx.
FROM nginx:alpine
COPY ./public /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/conf.d/default.conf # Эту строку можно опустить, если дефолтный nginx конфиг вам подходит
EXPOSE 80
У меня есть простой nginx.conf в корне проекта рядом с Dockerfile. Основная причина, по которой я переопределил этот конфиг — великолепная страница 404.
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Github actions
Я использую Github Actions как CI/CD инструмент для сборки и деплоя Docker-образа блога. Это простое и понятное решение, встроенное в Github.
Github Container Registry
Нам нужен Docker Registry для деплоя наших образов. Я использую Github Container Registry, который был представлен недавно. Вы можете использовать Docker Hub для той же цели.
Чтобы использовать GHCR, нам нужен Container Registry Personal Access Token. Чтобы создать этот токен, перейдите в Settings вашего пользователя Github, затем в Developer settings → Personal access tokens, и нажмите Generate new token. После ввода пароля откроется эта страница. Отметьте эти чекбоксы и нажмите Generate token. Сохраните полученный токен для дальнейшего использования.

Secrets
Когда у вас есть что-то секретное для использования в Actions, вы должны добавить это как Secret, чтобы оно было зашифровано Github. Таким образом, вы можете создать свой Action без хардкода приватных данных.
Чтобы добавить строку Secret, перейдите на вкладку Settings, затем на страницу Secrets и нажмите кнопку New Secret.
Давайте добавим Secret с именем CR_PAT и вставим токен, полученный на предыдущем шаге.

Теперь у нас есть все для написания workflow Github Actions.
Чтобы создать Action, нужно создать директорию .github/workflows с файлом action *.yaml.

Я создал workflow с именем pipeline.yml, используя все из предыдущих шагов.
name: Site Deploy # Имя Action. Может быть любым.
on:
push:
branches: [ develop ] # Выполнять эти jobs при любом push в ветку develop
jobs:
pipeline: # Имя Job. Может быть любым.
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2 # Checkout проекта
- name: hugo
uses: klakegg/actions-hugo@1.0.0 # Запуск hugo action для создания статической директории `public`
with:
env: production # Установка production env для разделения локальных проверок от production
- name: Set up QEMU
uses: docker/setup-qemu-action@v1 # Настройка QEMU для использования BuildX
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1 # Настройка BuildX для использования Docker Push action
with:
version: latest
- name: Login to Registry
uses: docker/login-action@v1 # Настройка credentials для Docker registry
with:
registry: ghcr.io
username: ${{ github.repository_owner }} # Использование имени репозитория как GHCR username
password: ${{ secrets.CR_PAT }} # Container Registry Personal Access Token из secrets
- uses: docker/build-push-action@v2
with:
context: . # Контекст текущей директории для создания образа
file: ./Dockerfile # Использование Dockerfile из репозитория
push: true
tags: ghcr.io/${{ github.repository_owner }}/pavelkorolevxyz-web # Docker image tag для push
Чтобы увидеть ваши actions, перейдите на вкладку Actions внутри репозитория.

Теперь, когда вы пушите в ветку develop, вы можете видеть лог action прямо на Github.

Docker
Мы будем использовать Docker Compose для запуска на VPS. Используя Docker Compose вместо обычного Docker, мы можем писать конфиги в удобных yaml-файлах, и таким образом можем связывать несколько контейнеров.
Настройка репозитория и установка Docker
Сначала нужно установить Docker на наш VPS. Все мои примеры для Ubuntu 20.04 VPS.
Настройка репозитория для загрузки docker.
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common \
-y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
Установка Docker.
sudo apt-get install docker-ce docker-ce-cli containerd.io
Больше информации в Официальном руководстве.
Установка Docker Compose
Для использования compose нужно установить его тоже.
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
Больше информации в Официальном руководстве.
Логин с помощью Github Access Token
export CR_PAT= # Установите ваш токен здесь
echo $CR_PAT | docker login ghcr.io -u username --password-stdin
Где $CR_PAT — это Github Access Token из Github Settings. GHCR username должен быть указан вместо username.
Watchtower
Хорошо. Теперь у нас все изменения пушатся в репозиторий и загружаются в registry без какой-либо помощи. Нам нужно что-то, чтобы получать эти измененные образы на нашем сервере.
Watchtower — безумно простой инструмент. Он просто периодически подтягивает последние образы из удаленного репозитория и может быть запущен как отдельный Docker-контейнер. Если последний образ и запущенный отличаются, то Watchtower перезапускает контейнер с новым образом. Давайте посмотрим, как это настраивается с помощью Docker Compose.
watchtower:
image: containrrr/watchtower # Образ Watchtower
restart: unless-stopped # Установить контейнер для перезапуска при выходе или перезапуске Docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Слушать изменения docker-контейнеров
- /root/.docker/config.json:/config.json # Использовать config для получения credentials для pull из приватного репозитория
command: --interval 60 # Запускать каждую минуту
Traefik
Я упоминал, что у меня домен pavelkorolev.xyz подключен к моему VPS. Сегодня это понятное желание — заставить его работать через протокол HTTPS, а также через HTTP.
Traefik — мощный reverse proxy и load balancer инструмент, но все, что нам нужно от Traefik здесь — это его поддержка Let's Encrypt.
Давайте суммируем наши намерения. Нам нужно запустить Traefik как Docker-контейнер на нашем сервере, настроить его на перенаправление на Nginx внутри контейнера блога и автоматически получать SSL-сертификаты.
Создать Docker proxy network. Она будет использоваться другими нашими контейнерами для нахождения в той же сети, что и Traefik, чтобы Traefik мог проксировать к ним.
docker network create proxy
Создать директорию traefik где угодно на сервере и директорию data внутри нее. Не обязательно создавать эту структуру директорий именно так, но мои сниппеты ниже подразумевают эту структуру и именование.
mkdir traefik
mkdir traefik/data
Создать пустой файл acme.json внутри директории data. Он будет заполнен позже самим Traefik, когда получит данные сертификатов.
touch traefik/data/acme.json
Создать конфигурацию traefik.yml внутри директории data.
nano traefik/data/traefik.yml
Заполнить traefik.yml.
api:
dashboard: true
entryPoints:
http:
address: ":80"
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
certificatesResolvers:
http:
acme:
email: mail@example.com # ваш email
storage: acme.json # ссылка на файл acme.json для записи
httpChallenge:
entryPoint: http
Создать credentials для dashboard Traefik. Заполните user и password, сохраните результирующую строку для дальнейшего использования.
echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g
Создать docker-compose.yml для образа Traefik в директории traefik.
nano traefik/docker-compose.yml
Заполнить docker-compose.yml.
version: '3'
services:
traefik:
image: traefik:v2.0 # Образ Traefik для использования
container_name: traefik # Имя контейнера для запуска образа
restart: unless-stopped # Установить контейнер traefik для перезапуска при выходе или перезапуске Docker
security_opt:
- no-new-privileges:true
networks:
- proxy # Установить traefik для использования docker network, которую мы настроили ранее
ports:
- 80:80 # HTTP port mapping
- 443:443 # HTTPS port mapping
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/traefik.yml:ro # Файл конфигурации Traefik
- ./data/acme.json:/acme.json # Файл сертификатов Traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy" # Установить traefik для использования docker network, которую мы настроили ранее
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)" # Установите ваш домен здесь
- "traefik.http.middlewares.traefik-auth.basicauth.users=username:password" # Установите credentials dashboard traefik, полученные ранее
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.example.com`)" # Установите ваш домен здесь
- "traefik.http.routers.traefik-secure.middlewares=traefik-auth" # Установить dashboard traefik для защиты с помощью auth
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=http"
- "traefik.http.routers.traefik-secure.service=api@internal"
networks:
proxy: # Определить внешнюю сеть для запуска traefik
external: true
Добавить права на запись в файл traefik/data/acme.json. Он используется для записи информации о сертификатах Let's Encrypt.
chmod 600 acme.json
Запустить Docker Compose из директории traefik.
cd traefik
docker-compose up -d
Конфигурация блога
Теперь у нас все настроено. Образ нашего блога — это единственное, для чего мы не создали конфиг.
Итоговый docker-compose.yml для проекта выглядит так.
version: "3"
services:
web: # Конфигурация образа моего блога
image: ghcr.io/pavelkorolevxyz/pavelkorolevxyz-web # расположение docker-образа в GHCR
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.entrypoints=http"
- "traefik.http.routers.web.rule=Host(`example.com`)" # Ваш домен
- "traefik.http.middlewares.web-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.web.middlewares=web-https-redirect"
- "traefik.http.routers.web-secure.entrypoints=https"
- "traefik.http.routers.web-secure.rule=Host(`example.com`)" # Ваш домен
- "traefik.http.routers.web-secure.tls=true"
- "traefik.http.routers.web-secure.service=web"
- "traefik.http.services.web.loadbalancer.server.port=80"
networks:
- proxy
watchtower: # Конфигурация Watchtower, которую я описал ранее
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /root/.docker/config.json:/config.json
command: --interval 60
networks:
proxy: # Установить внешнюю Docker network для использования
external: true
Теперь можно запустить.
docker-compose up -d
Результат
В результате у меня есть:
Dockerfileдля сборки образа блога.- Traefik
docker-compose.ymlс его конфигурациейtraefik.ymlиacme.json. - Основной
docker-compose.ymlдля запуска блога с Watchtower, отслеживающим его изменения.
Эти файлы можно хранить в том же репозитории для дальнейшего использования (просто скопировать из репо на удаленный VPS при настройке). Основной бонус, который мы получили, используя Docker — это то, как легко мы можем развернуть ту же конфигурацию на любой другой машине.
TLDR настройка блога с нуля
- Зарегистрировать домен
- Создать VPS
- Связать VPS с доменом
- Создать репозиторий с блогом на Github
- Если репозиторий приватный, нужна настройка Secret в Settings репо.
- Создать Github Action для публикации Docker-образа блога в Github Container Registry
- ssh на VPS
- Установить Docker и Compose
- Создать proxy network с помощью Docker
- Если репозиторий приватный, нужна авторизация в GHCR
- Скопировать конфиги для Traefik на VPS (из примеров выше)
- Запустить compose Traefik
- Скопировать конфиг compose для блога на VPS (из примеров выше)
- Запустить compose блога
Вот и все. Все, что мне нужно делать теперь для добавления нового поста — написать его и запушить в репозиторий. Весь процесс деплоя автоматический.
Достаточно ли это чисто? Я думаю, да.