Учимся бесплатно деплоить приложение (Java, Docker, CircleCI, Google Cloud)

Всех приветствую. В последнее время все чаще задумывался о том какую бы еще полезную статью написать. Параллельно этому постоянно видел в интернетах рекламу о «крутых» курсах в IT, обещают сделать из вас Java, Python и какого угодно разработчика за полгода/год, и ладно с ним, допустим за год они чему-то вас обучат и, возможно, где-то вы попадете на бесплатную стажировку (что еще тоже под воросом, учитывая нынешний рынок). Но когда рекламируют «крутые курсы DevOps’ов», я уже начинаю задаваться вопросом, как можно стать ДевОпсом, не имея опыта программирования, не опробовав самому весь цикл разработки на хоть каком-то языке, не опробовав различные настройки сборки приложений, не опробовав Линукс, со всеми его утилитами, докер, кубер, git и т.д. в работе, а просто «обучиться» этому в обособленности от всего и ожидать, что тебя куда-то возьмут, а если и возьмут, то к чему-то серьезному подпустят? Ответа на этот вопрос я так и не нашел у себя в голове.

Однако к чему это я, к тому, что я то все это перепробовал еще в студенческие годы, и все равно до недавнего момента особо не лез в девопсятину просто потому, что были люди, отдельно занимающиеся этими вещами. Но недавно выпал случай помочь одной компании с настройкой деплоя некоторых из их продуктов, и переборов свою неуверенность, я таки попробовал, что-то получилось и я решил поделиться своим опытом. Конечно, это далеко не «продвинутый» уровень и, скорее всего, в серьезных больших приложениях вы будете использовать что-то посерьезнее, по типу Gitlab Ci, Кубера и т.д., но обычно к этим вещам прямой доступ имеют только девопсы, оставляя наружу пару файликов для разрабов, которые они настраивают, по типу ci файла или helm чартов, но это и не суть статьи, суть в том, что это простой гайд для таких разрабов, как я, кто особо настройкой деплоя раньше не занимался, но не против попробовать, для расширения своего опыта, можно считать это продолжением серии моих статей о пет проектах (сначала мы узнали как его лучше оформить, теперь пробуем его задеплоить).

Итак, приступим. Во-первых, скажу, что для деплоя мы будем использовать бесплатный сервис CircleCi. Да, возможно, это не самый очевидный вариант, ведь есть GitHub Actions, есть Gitlab Ci, есть еще куча всего, но мне CircleCi показался достаточно простым, а значит вполне подойдет для начинающих и тех, кто просто желает попробовать. Для начала вам нужно просто зарегистрироваться. Раньше это можно было сделать через Гитхаб, но с конца сентября эту функциональность почему-то отключили, поэтому регистрируйтесь по почте и подключайте гитхаб, думаю найдете как это сделать. Далее, если у вас есть доступ к нескольким проектам от разных пользователей, выбирайте свой профиль, и слева в меню открывайте список проектов. В списке проектов вы увидите кнопку «Set up project», после нажатия которой он предложит вам несколько вариантов на выбор. Самый простой — это использовать настройки из проекта. Создаете папку .circleci с файлом config.yml в папке своего проекта, и после настройки этого файла circleci автоматически использует этот файл для деплоя.

4d8019ebf53ba0167fd8905a58306575.png

Итак, с сервисом CD (continuous delivery и continuous deployment) мы определились. Но куда деплоить, спросите вы меня, ведь для этого нужна машина. Если у вас есть локальная машина, умеющая в доступ извне — для тестирования вполне можете использовать и ее, я пробовал и такой вариант в этой связке, все также работает. Однако, т.к. у большинства нет таких серверов, нам понадобится также какой-либо сервис, предоставляющий linux сервера. Самый очевидный выбор — AWS от Amazon. Большой сервис, предоставляющий бесплатный год (!) использования сервера. Однако, у них могут возникать проблемы с валидацией вашей карты, ведь даже мою, рабочую визу, они отклонили и затем заставили прислать кучу документов в подтверждение, и все равно это не помогло. Поэтому наш выбор пока что падает на Google Cloud. Они предоставляют, к сожалению, всего 3 бесплатных месяца, однако, чтобы протестировать и попробовать вполне подойдет, да и проблем с привязкой карты там не возникло. Думаю как найти сервис, войти в свой гугл аккаунт, зарегистрироваться и ввести свои кредитные данные можно не объяснять, поэтому сразу перейдем к созданию инстанса и его настройке. Зайдя в консоль, пролистайте чуть ниже и выберите «Create a VM», в открывшейся странице нажмите «Create instance». Далее настраиваем наш инстанс.

Регион и имя можете выбрать по желанию, также как и конфигурацию машины, в зависимости от ваших нужд, для нас вполне подойдет стандартный E2. Да и вообще в целом большинство настроек можно оставить дефолтными, кроме тех, что я покажу. Обязательно разрешите HTTP/HTTPS трафик и выберите по желанию дистрибутив линукса, в моем случае я выбирал Ubuntu. Честно говоря, все остальное мы будем добавлять после, поэтому нажимаем далее и создаем свой инстанс.

5773f9a67f34bcd28daadeb2a530ca38.png

После создания зайдите в инстанс → metadata и добавьте свой публичный ssh ключ для подключения к серверу. Если у вас его нет по каким-то причинам до сих пор, и гитом, например, вы пользуетесь по логину и паролю, то чего вы ждете, бегите и генерируйте себе пару прямо сейчас (ssh-keygen -t ed25519 -C "your_email@example.com")

2832d029c2593d48ab1df4c501d9e21b.png

Пока что на данный момент с настройкой инстанса мы закончили. Далее нам еще понадобится открыть нужные нам порты, но об этом позже. Перейдем непосредственно к настройке сборки приложения и Ci файла. И так, у каждого языка и типа приложения свой способ его собрать, однако я буду рассказывать на примере своего Spring приложения на Java. Сразу скажу, что оно вышло не совсем удачным, т.к. оно микросервисное и изначально я рассчитывал лишь на локальный его запуск, а соответственно всю связь между сервисами настроил прямо в docker compose файле, без использования nginx или чего-то подобного, rookie mistake, но для демонстрации деплоя вполне подойдет. Итак, сначала создайте Dockerfile своего приложения. В моем случае все получилось немного сложно, сначала я собираю докерфайлом мавена весь проект и копирую сгенерированные jar в папку проекта, т.к. я использовал мультимодульную структуру Maven проекта. Если у вас всего один сервис в проекте, то ваш докерфайл будет выглядеть достаточно просто:

FROM openjdk:17.0.2-jdk-slim
COPY target/shows-migrations-service-0.0.1-SNAPSHOT.jar .
CMD ["java", "-jar", "shows-migrations-service-0.0.1-SNAPSHOT.jar"]

Далее напишите compose файл, в который вы соберете все необходимые вам сервисы, а именно ваш сервис, базу данных, еще что-то по необходимости. В моем случае это было 3 сервиса, postgres и keycloak:

version: "3.8"

services:
  postgres:
    image: 'postgres:14-alpine'
    container_name: postgres
    restart: always
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    volumes:
      - ./imports/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - auth

  keycloak:
    image: quay.io/keycloak/keycloak:20.0.0
    container_name: keycloak
    restart: always
    volumes:
      - ./imports:/opt/keycloak/data/import
    command: ['start-dev --import-realm --http-relative-path=/auth']
    environment:
      KC_DB: postgres
      KC_DB_URL_HOST: postgres
      KC_DB_URL_PORT: 5432
      KC_DB_URL_DATABASE: postgres
      KC_DB_USERNAME: postgres
      KC_DB_PASSWORD: postgres
      KEYCLOAK_ADMIN: admin
      KC_DB_SCHEMA: public
      KEYCLOAK_ADMIN_PASSWORD: admin
      KEYCLOAK_FRONTEND_URL: http://localhost:8484/auth
    ports:
      - "8484:8080"
    depends_on:
      - postgres
    links:
      - "postgres:postgres"
    networks:
      - auth
...

  shows-data:
    image: shows-data-service
    container_name: shows-data
    restart: always
    depends_on:
      - postgres
      - keycloak
      - migrations
    network_mode: host
    environment:
      POSTGRES_JDBC_URL: jdbc:postgresql://localhost:5432/postgres
      KEYCLOAK_URL: http://localhost:8484/auth
      SERVER_PORT: 8080
    extra_hosts:
      - "host.docker.internal:host-gateway"

...

networks:
  auth:
    driver: bridge

Как вы видите, достаточно настроек, и это я еще опустил 2 сервиса. Если коротко, я импорчу данные в postgres, импорчу realm в keycloak и настраиваю сервисы. Сервисы работают в сети «host», что значит, что они работают в сети инстанса, на самом деле это не обязательно, ведь в докер compose файле порты можно прокинуть, и при открытии портов в настройках нашего инстанса все заработает, но для чего-то мне это было нужно, уже не помню для чего, в общем, зависит от нужд вашего приложения. Далее остается настройка самого файла CircleCi. Т.к. он получается достаточно длинный, повторящиеся вещи уберу и приведу частями, с объяснением:

version: 2.1

jobs:
  deploy-to-development:
    docker:
      - image: docker:20.10.9
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Build Docker Image
          command: |
            chmod +x ./build.sh
            ./build.sh
            docker build -f Dockerfile-migrations -t migrations-service:latest .
      - run:
          name: Compress Docker Image
          command: |
            docker save migrations-service:latest | gzip > migrations-service.tar.gz
      - run: ls -lh
      - persist_to_workspace:
          root: .
          paths:
            - migrations-service.tar.gz
            - ./docker/

Здесь мы используем образ Докера, чтобы использовать его команды. Я запускаю свой sh файл для сборки приложения (тот, что собирает и копирует jarники, у вас скорее всего такого не будет), далее собирается докер образ вашего приложения, обязательно задайте ему тэг. Далее мы сохраняем этот образ и сжимаем его. И сохраняем получившийся файл и нужную папочку с файлами docker compose (в моем случае это папка, у вас может быть просто 1 файл).

  transfer-and-run:
    machine:
      image: ubuntu-2004:202010-01
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Install SSH And Configure
          command: |
            echo $SSH_PRIVATE_KEY | base64 --decode > ./id_rsa
            chmod 400 id_rsa
      - run:
          name: Stop Remote Docker-Compose
          command: |
            ssh -o "StrictHostKeyChecking=no" -i ./id_rsa $USER@$HOST '
            if [ -f compose.yml ]; then
            sudo docker-compose -f docker/compose.yml down --rmi all
            sudo rm compose.yml
            else
            echo "compose.yml not found"
            fi
            '
      - run:
          name: Transfer Files
          command: |
            scp -o "StrictHostKeyChecking=no" -i ./id_rsa ./shows-frontend-service.tar.gz $USER@$HOST:~/
            scp -o "StrictHostKeyChecking=no" -i ./id_rsa -r ./docker $USER@$HOST:~/
      - run:
          name: Decompress Docker Image | Run Compose
          command: |
            ssh -o "StrictHostKeyChecking=no" -i ./id_rsa $USER@$HOST '
            sudo gunzip -c ./shows-frontend-service.tar.gz | sudo docker load
            sudo rm ./*.tar.gz
            cd docker
            sudo docker compose -f compose.yml up -d                    
            '

workflows:
  deploy-to-dev:
    jobs:
      - deploy-to-development:
          filters:
            branches:
              only:
                - deploy
      - transfer-and-run:
          requires:
            - deploy-to-development
          filters:
            branches:
              only:
                - deploy

И так, здесь как, мы видим, мы определяем вторую часть нашего деплоя, а именно трансфер файлов и их запуск. Делаем мы это используя уже образ Ubuntu, а не Докера. Здесь мы используем файлы с предыдущего шага, которые сохранили (workspace). Далее мы берем SSH_KEY, который мы заранее сохранили в ENV переменных проекта в Circleci в base64 виде, и сохраняем его в файлик, даем ему нужный доступ. Сами переменные Circleci хранит в зашифрованном виде и вроде как это безопасно. Альтернатива: делать то же самое, но на машине хоста и давать какой-то ключ на доступ к гиту, клонить проект гита и уже все делать на машине хоста, но как будто бы это не особо лучше. Далее мы используем этот ключ, чтобы подключиться по ssh к хосту, также используя переменные $USER и $HOST. Останавливаем докер файлик, удаляем лишние образы. Копируем нужные нам файлы на машину хоста (напоминаю, что некоторые я опустил, для краткости, так что не удивляйтесь, что названия скачут), и загружаем сжатые нами образы в докер, затем удаляем сжатые файлы. После чего запускаем compose файлик, и все сервисы должны запуститься. Также ниже вы видите настройки того, с какой ветки запускать нажи джобы, в моем случае это deploy. После этого вы увидите в панели CircleCi, что все ваши шаги прошли и отмечены галочкой, после чего можете подключиться к машине хоста и проверить вручную, что все работает (правда не в моем случае, т.к. я накосячил с настройкой, но задеплоиться и запуститься все запустилось, так что это уже другой разговор).

И, пока не забыл, покажу как добавить ENV переменные в проект: заходите в Settings Project и добавляете в Environment Variables (кстати только сейчас заметил, что оказывается там можно напрямую и SSH ключи добавить, наверное лучше так и делать в дальнейшем):

62ca0f341d0dc62cfdc5f835421881fd.png

И как открыть порты в google console: заходите на страницу вашего проекта (важно, не инстанса, а именно проекта), заходите в левом меню в «VPC Network → Firewall», нажимаете сверху «Create firewall rule», даете название по желанию, обязательно ставите какой-нибудь тег, и разрешаете нужные вам порты (например, 8081). После чего заходите уже в ваш инстанс, нажимаете «Edit» и в «Network tags» добавляете тот тег, который задали на правило, и порт у вас откроется.

3c14f98cbfdc9b64e1a6ec0f5c9abfa7.png

Полный код проекта и сам проект доступны на моем гите, если кто-то что-то не понял и ему нужен source (сам прекрасно понимаю это чувство, когда в гайде что-то не понятно, но при этом нет ссылки на исходники, надеюсь я в статье и гите никаких лишних данных не спалил). В любом случае, думаю этот гайд будет кому-то полезен, в конце концов это полезный опыт для разработчиков, попробовать подеплоить свои пет проекты самостоятельно, тем более что, если вам удастся зарегистрироваться на Амазоне удачно, то у вас будет целый год бесплатного хостинга, что вполне неплохо, чтобы задеплоить какой-то свой пет проект и указать ссылку на него в гите/резюме, за год то уж найдете работу (хотя, in this economy не факт, конечно). А ну и чуть не забыл сказать, у CircleCi также есть ограничение по времени билда, используемого за месяц, поэтому рекомендую особо не злоупотреблять и деплоить только с какой-то стабильной ветки. Ну и, конечно, сейчас модно везде использовать Кубер, и голым докером почти никто не деплоит, но для простого приложения вполне сойдет, да и в случае падения, свойство «restart: always» его запустит заново, так что какая-то стабильность все-таки есть. В любом случае, если вам понравилась статья, ссылки на меня найдете в профиле, всем спасибо за внимание.

© Habrahabr.ru