Публикация Spring Boot приложения в GitHub Packages с помощью GitHub Actions для самых маленьких
Сегодня я расскажу вам как можно опубликовать своё Spring Boot приложение в GitHub Packages с помощью GitHub Actions. Вот так. В общем-то всё. Вот. Спасибо за внимание.
Ну, а здесь, для самых любознательных, приведу немного информации о том, что делает этот GitHub Action.
Старт
Прежде чем перейти к самому GitHub Action приведу сначала то, что он должен уметь делать.
Он должен собрать докер образ.
Выложить его в GitHub Packages.
В качестве примера собирать буду вот этот проект. Подробнее о нём я писал в этой статье. Здесь лишь отмечу, что в качестве системы сборки в нём используется gradle.
Сборка docker-образа
Как же можно собрать docker-образ своего приложения? Ну конечно же, написать Dockerfile! Делать я этого, конечно же, не буду, а воспользуюсь замечательным проектом Cloud Native Buildpacks.
Что же в этом проекте такого замечательного?!. В Spring Boot имеется поддержка этого проекта, что позволяет собрать docker-образ без написания Dockerfile всего одной командой. Например, при использовании в качестве сборщика gradle это команда будет выглядеть так:
./gradlew bootBuildImage
А при использовании maven - так:
mvn spring-boot:build-image
В результате выполнения команды будет создан и локально опубликован docker-образ. Версия java определиться автоматически из настроек сборщика, а в качестве JDK по умолчанию будет использоваться сборка BellSoft Liberica.
Параметры по умолчанию можно изменить. Как это сделать можно посмотреть в настройках плагина для соответствующей системы сборки (maven, gradle). Возможно, через конфигурацию плагинов получится изменить не все параметры, которые есть в buildpack. В этом случае собирать образ можно будет с помощью утилиты pack. Подробнее о сборке java приложений с помощью этой утилиты можно почитать, например, тут. Правда, на мой взгляд, это будет уже не так лаконично, как было с помощью соответствующих плагинов.
Немного о возможных проблемах
Изначально в проекте у меня была java 15. Но при вызове таскиbootBuildImage я получал ошибку вида:
[creator] Paketo BellSoft Liberica Buildpack 8.4.0
[creator] unable to find dependency
[creator] no valid dependencies for jdk, 15.*, and io.buildpacks.stacks.bionic in [(jdk, 8.0.302, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *]) (jre, 8.0.302, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *]) (jdk, 11.0.12, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *]) (jre, 11.0.12, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *]) (native-image-svm, 11.0.12, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *]) (jdk, 16.0.2, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *]) (jre, 16.0.2, [io.buildpacks.stacks.bionic io.paketo.stacks.tiny *])]
[creator] ERROR: failed to build: exit status 1
Как оказалось, ошибка была связана с тем, что у BellSoft нет сборки java 15. Но когда-то она была, но после того как они собрали 16-ю версию, 15-ю решили удалить. И это выглядит, мягко говоря, не очень надёжным. Ну и с выходом 17-й так же удалили и 16-ю.
Сборка и публикация через GitHub Actions
Немного о GitHub Actions. Это штука, которая позволяет автоматизировать рабочий процесс прямо в GitHub (настроить CI/CD). Например, с помощью неё можно собирать приложение по коммиту, где-то публиковать, прогонять тесты и делать многое-многое другое.
Переходим к сборке. Для этого нужно создать свой action
, который будет этим заниматься. Либо можно добавить существующий из marketplace. Мне, правда, не удалось найти нужный, который бы использовал для сборки docker-образа Cloud Native Buildpacks через gradle-плагин, но, возможно, на момент, когда вы читаете эту статью, этот плагин там есть, либо он и был, а я просто плохо искал.
Для создания своего action
в GitHub-репозитории нужно перейти на вкладку Actions
. Здесь можно либо самостоятельно настроить workflow
нажав на set up a workflow yourself
. Либо можно выбрать уже готовое workflow
из предложенного списка.
Проще всего, конечно же, выбрать уже готовое workflow
и видоизменить его требуемым образом. Так как для сборки проекта используется gradle
, то и взять лучше всего workflow
для gradle-проекта. На текущих момент чтобы найти это workflow
, нужно пролистать страницу вниз до кнопки More continuous integration workflows...
и нажать на неё.
Среди предложенных workflow
нужно найти Java with Gradle
и нажать на кнопку Set up this workflow
.
После этого мы перейдём на страницу создания нового action
для проекта, на которой уже будет доступен код, который можно редактировать.
В верху страницы для редактирования доступно имя файла, в котором будет находится код этого action
.
По умолчанию файл будет называться gradle.yml
. Так как данный action
будет заниматься сборкой проекта, предлагаю переименовать его в build.yml
.
Ниже представлен код данного action
, который был сгенерирован автоматически.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
Немного разберём что здесь происходит.
name: Java CI with Gradle
- имя workflow
, будет отображаться на страничке Actions
в GitHub.
Блок on
описывает, какие события должны запускать данный workflow
. В данном случае это push
и pull_request
, кроме того можно указать, в каких ветках должны происходить данные события с помощью branches
. Более подробно о синтаксисе можно почитать в документации.
jobs
. В этом блоке описываются job'ы
(например, сборка, тестирование и т.п.). Их может быть несколько. По умолчанию они будут запускаться параллельно, но документация говорит, что можно запустить их последовательно. В данном случае есть лишь одна job
, которая называется build
.
runs-on
. В этом блоке указывается runner
, т.е., по сути, виртуальная машина, на которой будет выполняться job
. В данном случае это ubuntu-latest
.
steps
. Блок, в котором описаны шаги, из которых состоит job
. Каждый шаг является отдельным GitHub Action
или shell-командой.
uses: actions/checkout@v2
. uses
означает, что требуется взять GitHub Action
, далее указывается имя GitHub Action
, в данном случае actions/checkout@v2. Данный GitHub Action
вытягивает код из репозитория в runner
.
uses: actions/setup-java@v2
. GitHub Action
actions/setup-java@v2 устанавливает java в runner
. С помощью with
можно передавать параметры для данного GitHub Action
. java-version
задаёт версию java. distribution
задаёт дистрибутив. cache: gradle
указывает, что необходимо сохранять локальный кэш Gradle в инфраструктуре GitHub Actions таким образом, чтобы он использовался в будущих запусках workflow
. Подробнее о допустимых значениях параметров можно почитать на страничке данного GitHub Action
. Так же для данного шага указано name
. Будет отображаться в логах выполнения job
на GitHub.
run: chmod +x gradlew
. run
позволяет выполнять shell-команды. Команда chmod +x gradlew
даёт права на выполнение файла gradlew
.
run: ./gradlew build
- cобирает проект.
Изменим скрипт таким образом чтобы он делал требуемые нам действия. В результате получится так.
name: build
on: [push]
env:
IMAGE_NAME: list-keep
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'adopt'
- name: Login to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build a container image from our Spring Boot app using Paketo.io / Cloud Native Build Packs
run: ./gradlew bootBuildImage --imageName=$IMAGE_NAME
- name: Tag & publish to GitHub Container Registry
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
VERSION=latest
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
Разберём что изменилось.
on: [push]
. Данный workflow
теперь запускает любое событие push
.
В блоке env
объявлена переменна окружения IMAGE_NAME
, которая используется далее в скрипте.
java-version: '17'
. Используется 17-я версия java
. Так же, как можно заметить, в данной конфигурации GitHub Action
actions/setup-java@v2 не используется cache
для gradle
.
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
. Команда помогает залогиниться в GitHub Packages. С помощью docker login
логинимся на ghcr.io
, с помощью параметра -u
передаём логин пользователя GitHub. Сам логин берём из github-контекста, в котором хранится различная информация, как о workflow
, так и о событии которое его запустило. Параметр --password-stdin
говорит о том, что пароль, а точнее токен, будет получен из stdin
. С помощью echo "${{ secrets.GITHUB_TOKEN }}"
передаём токен в stdin
. В качестве токена здесь используется GITHUB_TOKEN. Этот токен создаётся в автоматическом режиме в начале каждого запуска workflow
, и он доступен как секрет secrets.GITHUB_TOKEN
либо его можно получить из github
-контекста как github.token
.
Немного о правах GITHUB_TOKEN
GITHUB_TOKEN
не всесилен. Его набор прав ограничен из коробки. Но его можно расширить с помощью блока permissions.
Так же, например, правами на packages
можно управлять на странице настроек пакета (думаю, другими правами так же можно управлять на соответствующих страницах).
На этой странице в блоке Manage Actions access
можно добавлять репозитории и давать им права на этот пакет. И если, например, дать права только на чтение репозиторию, то он не сможет с GITHUB_TOKEN
сюда публиковать, не смотря на то что у GITHUB_TOKEN
по умолчанию есть доступ на запись.
./gradlew bootBuildImage --imageName=$IMAGE_NAME
- собирает docker-образ с помощью Cloud Native Buildpacks. По умолчанию имя docker-образа будет docker.io/library/${project.name}:${project.version}
, а точнее, в данном конкретном примере, docker.io/library/list-keep:0.0.1-SNAPSHOT
. Гораздо более удобно было бы, на мой взгляд, если бы версия была latest
. Сделать это можно, переопределив имя docker-образ с помощью параметра --imageName
. В этот параметр можно передать как полное название docker-образа - docker.io/library/list-keep:latest
, так и часть его, например, list-keep
. В этом случае docker.io/library
и latest
(если версия не указана, то по умолчанию выставляется latest
) допишутся автоматически.
Далее идёт многострочный скрипт публикации docker-образа в GitHub Packages. Чтобы скрипт был многострочным, необходимо в самом начале скрипта указать символ |
.
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
превратится в ghcr.io/vanbv/list-keep
. Это адрес пакета в GitHub Packages, куда будет опубликован собранный docker-образ.
VERSION=latest
. В качестве версии будем использовать latest
. Если требуется делать что-то более сложное, например, проставлять версию в соответствии с версией приложения, то в документации GitHub Actions есть неплохой пример того, как это можно сделать.
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
превратится в docker tag list-keep ghcr.io/vanbv/list-keep:latest
. Команда docker tag создаст тег ghcr.io/vanbv/list-keep:latest
, который будет ссылаться на list-keep:latest
(т.к. версия не указана, то по умолчанию возьмётся latest
).
docker push $IMAGE_ID:$VERSION
превратится в docker push ghcr.io/vanbv/list-keep:latest
. docker push опубликует docker-образ в GitHub Packages.
Заключение
Вот, в общем-то, и всё. Надеюсь, было интересно и полезно. Берегите там себя.