Добрый день!
Давайте сегодня рассмотрим такую полезную штуку, как bmuschko/gradle-docker-plugin
Важно! Данный плагин требует версию Gradle >=5.1
Для начала созданим маленькое приложение, которое будем разворачивать в докере.
build.gradle
plugins {
id 'java'
id 'application'
id 'org.springframework.boot' version '2.1.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
}
application {
mainClassName = 'com.tune_it.App'
}
jar {
manifest {
attributes 'Main-Class': 'com.tune_it.App'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
App.java
package com.tune_it;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String... args){
SpringApplication.run(App.class);
}
}
HelloController.java
package com.tune_it;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/hello")
public class HelloController {
@GetMapping
public String hello() {
return "Hello!\n";
}
}
Далее научим gradle создавать Dockerfile, дополнив наш build.gradle
plugins {
id 'java'
id 'application'
id 'org.springframework.boot' version '2.1.0.RELEASE'
id 'com.bmuschko.docker-remote-api' version '4.8.0'
}
import com.bmuschko.gradle.docker.tasks.image.Dockerfile
def dockerBuildDir = 'build/docker/'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
}
application {
mainClassName = 'com.tune_it.App'
}
jar {
manifest {
attributes 'Main-Class': 'com.tune_it.App'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
task createDockerfile(type: Dockerfile) {
destFile = project.file("$dockerBuildDir/Dockerfile")
from 'openjdk:8-jre-alpine'
copyFile jar.archiveName, '/app/test_service.jar'
entryPoint 'java'
defaultCommand '-jar', '/app/test_service.jar'
exposePort 8080
runCommand 'apk --update --no-cache add curl'
instruction 'HEALTHCHECK CMD curl -f http://localhost:8080/hello || exit 1'
}
Здесь мы добавили тот самый плагин, о котором сегодня идёт речь, добавили константу, определяющую путь к директории, в которой будет производится сборка докер образа, добавили свою таску, которая, собственно, и создаёт Dockerfile. Для создания таски нам потребовалось сделать импорт типа таски.
После запуска таски в указанной ранее директории должен появится Dockerfile со следующим содержимым:
FROM openjdk:8-jre-alpine
COPY gradle_docker_plugin.jar /app/test_service.jar
ENTRYPOINT ["java"]
CMD ["-jar", "/app/test_service.jar"]
EXPOSE 8080
RUN apk --update --no-cache add curl
HEALTHCHECK CMD curl -f http://localhost:8080/hello || exit 1
После этого начнём собирать сам образ, для этого добавим следующие импорты
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerRemoveImage
И следующие таски
def imageVersion = '1.0'
task syncJar(type: Copy) {
dependsOn assemble
from jar.archivePath
into dockerBuildDir
}
task removeImage(type: DockerRemoveImage) {
targetImageId("adpashnin/gradle_docker_plugin:$imageVersion")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task buildImage(type: DockerBuildImage) {
dependsOn createDockerfile, syncJar
inputDir = project.file(dockerBuildDir)
tags = ["adpashnin/gradle_docker_plugin:$imageVersion"]
}
removeImage нужна нам для того, чтобы удалить существуюй образ в случае, если он есть, в противном случае он будет переименован на null и дополнительно будет создан новый с указанным именем.
Однако могут возникнуть проблемы с удалением образа, если существует контейнер, который его использует. Чтобы этого избежать, добавим задачу остановки и удаления контейнера с определённым именем.
import com.bmuschko.gradle.docker.tasks.container.DockerRemoveContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStopContainer
def uniqueContainerName = 'test_docker_plugin'
task stopContainer(type: DockerStopContainer) {
targetContainerId("$uniqueContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeContainer(type: DockerRemoveContainer) {
dependsOn stopContainer
targetContainerId("$uniqueContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
Добавим removeContainer в зависимости задачи removeImage
task removeImage(type: DockerRemoveImage) {
dependsOn removeContainer
targetImageId("adpashnin/gradle_docker_plugin:$imageVersion")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
Теперь, наконец, научим наш грейдл создавать и запускать контейнеры
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
task createContainer(type: DockerCreateContainer) {
dependsOn buildImage, removeContainer
targetImageId buildImage.getImageId()
containerName = "$uniqueContainerName"
portBindings = ['8080:8080']
}
task startContainer(type: DockerStartContainer) {
dependsOn createContainer
targetContainerId("$uniqueContainerName")
}
Итоговый build.gradle должен выглядеть как-то так
plugins {
id 'java'
id 'application'
id 'org.springframework.boot' version '2.1.0.RELEASE'
id 'com.bmuschko.docker-remote-api' version '4.8.0'
}
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
import com.bmuschko.gradle.docker.tasks.container.DockerRemoveContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStopContainer
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerRemoveImage
import com.bmuschko.gradle.docker.tasks.image.Dockerfile
def dockerBuildDir = 'build/docker/'
def imageVersion = '1.0'
def uniqueContainerName = 'test_docker_plugin'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
}
application {
mainClassName = 'com.tune_it.App'
}
jar {
manifest {
attributes 'Main-Class': 'com.tune_it.App'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
task createDockerfile(type: Dockerfile) {
destFile = project.file("$dockerBuildDir/Dockerfile")
from 'openjdk:8-jre-alpine'
copyFile jar.archiveName, '/app/test_service.jar'
entryPoint 'java'
defaultCommand '-jar', '/app/test_service.jar'
exposePort 8080
runCommand 'apk --update --no-cache add curl'
instruction 'HEALTHCHECK CMD curl -f http://localhost:8080/hello || exit 1'
}
task syncJar(type: Copy) {
dependsOn assemble
from jar.archivePath
into dockerBuildDir
}
task stopContainer(type: DockerStopContainer) {
targetContainerId("$uniqueContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeContainer(type: DockerRemoveContainer) {
dependsOn stopContainer
targetContainerId("$uniqueContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeImage(type: DockerRemoveImage) {
dependsOn removeContainer
targetImageId("adpashnin/gradle_docker_plugin:$imageVersion")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task buildImage(type: DockerBuildImage) {
dependsOn createDockerfile, syncJar
inputDir = project.file(dockerBuildDir)
tags = ["adpashnin/gradle_docker_plugin:$imageVersion"]
}
task createContainer(type: DockerCreateContainer) {
dependsOn buildImage, removeContainer
targetImageId buildImage.getImageId()
containerName = "$uniqueContainerName"
portBindings = ['8080:8080']
}
task startContainer(type: DockerStartContainer) {
dependsOn createContainer
targetContainerId("$uniqueContainerName")
}
Протестируем что же у нас получилось
Выполним gradle startContainer
в лог дожны вывестись сообщения от докера о процессе сборки образа.
Выполним docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e7df554a0573 80fcf9fc17d7 "java -jar /app/test…" About a minute ago Up About a minute (healthy) 0.0.0.0:8080->8080/tcp test_docker_plugin
Протестируем
$ curl localhost:8080/hello
Hello!
Успех! Оно даже работает:D
Если необходимо внести изменения в код и перезапустить контейнер, просто делаем gradle startContainer и всё, gradle сам остановит и удалит старые версии контейнера и образа, затем создаст и запустит уже обновлённые.
На это всё, спасибо за внимание