null

Gradle Docker plugin. Добавляем базу данных

Добрый день!

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

Начнём с написания кода.

Добавим следующие зависимости

    compile 'org.springframework.boot:spring-boot-starter-data-jpa:2.1.0.RELEASE'
    compile 'org.projectlombok:lombok:1.18.4'
    compile 'org.postgresql:postgresql:42.2.5'

Создаём application.properties

spring.jpa.hibernate.ddl-auto = update
spring.datasource.url=jdbc:postgresql://db_host/
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

RequestEntity.java

package com.tune_it;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@AllArgsConstructor
@NoArgsConstructor
public class RequestEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
}

RequestEntityRepository.java

package com.tune_it;

import org.springframework.data.repository.CrudRepository;

public interface RequestEntityRepository extends CrudRepository<RequestEntity, Long> {
}

Модифицируем HelloController

package com.tune_it;

import org.springframework.beans.factory.annotation.Autowired;
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 {
    @Autowired
    private RequestEntityRepository entityRepository;

    @GetMapping
    public String hello() {
        entityRepository.save(new RequestEntity());
        return "Hello " + entityRepository.count() + " times\n";
    }

}

Теперь займёмся нашим build.gradle

Добавим задачи создания старта и удаления контейнера с базой данных.

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

def postgresHostDir = '/tmp/test_db'
def postgresDockerDir = '/var/lib/postgresql/data'
def dbContainerName = 'test_db'
def dbNetworkName = 'test_network'

task createContainer(type: DockerCreateContainer) {
    dependsOn buildImage, removeContainer
    targetImageId buildImage.getImageId()
    containerName = "$uniqueContainerName"
    portBindings = ['8080:8080']
    network = "$dbNetworkName"
    networkAliases = ["web_host"]
}

task createDbContainer(type: DockerCreateContainer) {
    targetImageId "postgres:11.0"
    containerName = "$dbContainerName"
    volumes = ["$postgresHostDir:$postgresDockerDir"]
    network = "$dbNetworkName"
    networkAliases = ["db_host"]
    onError { exc ->
        if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
            throw new RuntimeException(exc)
        }
    }
}

task startDbContainer(type: DockerStartContainer) {
    targetContainerId("$dbContainerName")
}

task stopDbContainer(type: DockerStopContainer) {
    targetContainerId("$dbContainerName")
    onError { exc ->
        if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
            throw new RuntimeException(exc)
        }
    }
}

task removeDbContainer(type: DockerRemoveContainer) {
    dependsOn stopDbContainer
    targetContainerId("$dbContainerName")
    onError { exc ->
        if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
            throw new RuntimeException(exc)
        }
    }
}

networkAliases = ["db_host"] - параметр, отвечающий за имя хоста с нашей БД, именно его мы указывали в application.properties

Итоговый 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
import com.bmuschko.gradle.docker.tasks.network.DockerCreateNetwork


def dockerBuildDir = 'build/docker/'
def imageVersion = '1.0'
def uniqueContainerName = 'test_docker_plugin'
def postgresHostDir = '/tmp/test_db'
def postgresDockerDir = '/var/lib/postgresql/data'
def dbContainerName = 'test_db'
def dbNetworkName = 'test_network'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter-data-jpa:2.1.0.RELEASE'
    compile 'org.projectlombok:lombok:1.18.4'
    compile 'org.postgresql:postgresql:42.2.5'
}

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']
    network = "$dbNetworkName"
    networkAliases = ["web_host"]
}

task startContainer(type: DockerStartContainer) {
    dependsOn createContainer
    targetContainerId("$uniqueContainerName")
}

task createNetwork(type: DockerCreateNetwork) {
    networkId = "$dbNetworkName"
}

task createDbContainer(type: DockerCreateContainer) {
    targetImageId "postgres:11.0"
    containerName = "$dbContainerName"
    volumes = ["$postgresHostDir:$postgresDockerDir"]
    network = "$dbNetworkName"
    networkAliases = ["db_host"]
    onError { exc ->
        if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
            throw new RuntimeException(exc)
        }
    }
}

task startDbContainer(type: DockerStartContainer) {
    targetContainerId("$dbContainerName")
}

task stopDbContainer(type: DockerStopContainer) {
    targetContainerId("$dbContainerName")
    onError { exc ->
        if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
            throw new RuntimeException(exc)
        }
    }
}

task removeDbContainer(type: DockerRemoveContainer) {
    dependsOn stopDbContainer
    targetContainerId("$dbContainerName")
    onError { exc ->
        if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
            throw new RuntimeException(exc)
        }
    }
}


Протестируем

$ gradle createNetwork
$ gradle createDbContainer
$ gradle startDbContainer
$ gradle startContainer
$ curl localhost:8080/hello
Hello 1 timesadpashnin
$ curl localhost:8080/hello
Hello 2 times
$ curl localhost:8080/hello
Hello 3 times

Успех! И теперь оно работает:D

В случае, если необходимо как-то кастомизировать базу данных, можно повторить для неё процедуру создания собственного образа на основе образа базы данных.
На это всё, спасибо за внимание