使用 Docker 为你的 Grails 应用程序提供外部服务
了解如何在使用 Docker 容器的 Grails 应用程序中使用 Postgresql 数据库
作者:Iván López、Sergio del Amo
Grails 版本 3.3.2
1 培训
Grails 培训 - 由创建和积极维护 Grails 框架的人员开发和提供!。
2 开始
如果你从事任何真正的 Grails 开发,则需要连接到外部服务;数据库、ElasticSearch、Redis 等服务。
通常,你需要在你的开发机器上安装这些服务,以复制生产环境。你可能会遇到这样的情况:在 MacOS 中开发一个连接到 MSSQL 数据库的 Grails 应用程序。在你的开发环境中安装这样的外部服务可能会很困难,甚至不可能。
在许多其他事情中,Docker 可以帮助我们简化此类场景。在本指南中,你将编写一个连接到运行在 Docker 容器中的 PostgreSQL 数据库的 Grails 应用程序。你不会在你的机器上安装 Postgres。相反,你将提取一个 PostgreSQL 映像,使用该映像创建一个容器,并启动该容器。你将修改你的 Grails 应用程序以连接到容器中运行的 PostgreSQL 数据库。
2.1 如何完成该指南
要开始,请执行以下操作
-
下载并解压缩源代码
或
-
克隆 Git 代码库
git clone https://github.com/grails-guides/grails-docker-external-services.git
Grails 指南代码库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用程序,其中包含一些额外代码,让你可以抢先一步。 -
complete
一个已完成的示例。它是按照指南介绍的步骤操作并对initial
文件夹应用这些更改的结果。
要完成指南,请进入 initial
文件夹
-
在
grails-guides/grails-docker-external-services/initial
中执行cd
并按照后续章节中的说明操作。
如果您在 grails-guides/grails-docker-external-services/complete 中执行 cd ,则可以直接转到 已完成的示例 |
3 编写指南
3.1 安装和配置 Docker
如果您尚未安装 Docker,则需要 对其进行安装。
根据您的环境,您可能需要将 Docker 的可用内存增加到 4GB 或更多。
提示:获取 Kitematic
Kitematic 是一个用于管理 Docker 容器的优秀的桌面应用程序。首次单击 Open Kitematic 时,它会提示您下载并安装它。然后,您可以使用 Kitematic 查看容器的输出、管理其设置等。
3.2 域类
创建一个域类。它将帮助您验证在 docker 容器内运行的 PostgreSQL 数据库中创建的数据库架构。
package demo
import groovy.transform.CompileStatic
@CompileStatic
class Book {
String title
}
3.3 Gradle Docker 插件
在本指南中,我们使用 Gradle Docker 插件;一个用于管理 Docker 镜像和容器的 Gradle 插件。
要安装 Gradle 插件,请修改 build.gradle
buildscript {
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "gradle.plugin.com.energizedwork.webdriver-binaries:webdriver-binaries-gradle-plugin:1.1"
classpath "gradle.plugin.com.energizedwork:idea-gradle-plugins:1.4"
classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.14.6"
classpath "com.bmuschko:gradle-docker-plugin:3.2.1" (1)
}
}
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"com.energizedwork.webdriver-binaries"
apply plugin:"com.energizedwork.idea-project-components"
apply plugin:"asset-pipeline"
apply plugin:"org.grails.grails-gsp"
apply plugin:"com.bmuschko.docker-remote-api" (1)
1 | Gradle Docker 插件 |
3.4 Gradle Docker 任务
配置若干 Gradle 任务,这些任务允许我们获取一个 Docker 镜像,并使用 Gradle 创建/启动/停止一个 PostgreSQL 容器
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStopContainer
import com.bmuschko.gradle.docker.tasks.image.DockerPullImage
...
..
.
task pullPostgresImage(type: DockerPullImage) {
group = 'docker' (1)
ext {
imageName = 'postgres' (2)
imageTag = '9.6.6' (3)
}
description = 'Pull PostgreSQL image'
repository = imageName
tag = imageTag
}
task createPostgresContainer(type: DockerCreateContainer, dependsOn: pullPostgresImage) {
group = 'docker' (1)
ext {
pgContainerName = "demo-postgres" (4)
dbName = "demo_db" (5)
dbPort = 5432 (6)
dbPassword = 'dev_password' (7)
}
description = 'Creates PostgreSQL container'
containerName = pgContainerName
imageId = "${pullPostgresImage.imageName}:${pullPostgresImage.tag}"
portBindings = ["${dbPort}:5432"]
env = [
"POSTGRES_PASSWORD=${dbPassword}",
"POSTGRES_DB=${dbName}",
] as String[]
onError { e ->
if (e.class.simpleName in ['BadRequestException', 'ConflictException']) {
logger.warn 'Container already exists' (8)
} else {
throw e
}
}
}
task startPostgresContainer(type: DockerStartContainer, dependsOn: createPostgresContainer) {
group = 'docker' (1)
description = 'Starts Postgres container'
containerId = createPostgresContainer.pgContainerName
onError { e ->
if (e.class.simpleName == 'NotModifiedException') {
logger.warn 'Container already started' (8)
} else {
throw e
}
}
onComplete {
logger.info "Postgres is listening on port ${createPostgresContainer.dbPort}"
}
}
task stopPostgresContainer(type: DockerStopContainer) {
group = 'docker' (1)
description = 'Stops Postgres container'
containerId = createPostgresContainer.pgContainerName
onError { e ->
if (e.class.simpleName == 'NotModifiedException') {
logger.warn 'Container already stopped' (8)
} else {
throw e
}
}
}
1 | 在组中对相关任务进行分组。当您运行 gradlew tasks --all 时,它有助于您可视化 Gradle 任务 |
2 | Docker 镜像名称 |
3 | Docker 镜像标签 |
4 | Docker 容器名称 |
5 | 数据库名称 |
6 | 数据库端口 |
7 | 数据库用户的密码。默认情况下,用户名为 postgres |
8 | 如果容器已创建、已启动或已停止,并且调用了 Gradle 任务,则不失败 |
3.5 配置 PostgreSQL
修改 build.gradle
并添加 PostgreSQL 依赖项
provided "org.postgresql:postgresql:9.4.1211.jre7"
将开发环境的 dataSource
配置为使用在 Docker 中运行的 PostgreSQL 数据库。
dataSource:
dialect: org.hibernate.dialect.PostgreSQLDialect
driverClassName: org.postgresql.Driver
username: postgres
password: dev_password
dbCreate: update
url: jdbc:postgresql://localhost:5432/demo_db
4 运行应用程序
在运行应用程序之前,首先启动 PostgreSQL
./gradlew startPostgresContainer
然后启动 Grails 应用程序
./gradlew bootRun
如果您连接到数据库,则会看到预期的数据库架构。
bootRun 依赖于 PostgreSQL
您可以将 bootRun
任务配置为依赖于 startPostgresContainer
任务,在 build.gradle
中添加以下内容
bootRun.dependsOn startPostgresContainer
这将创建以下依赖关系路径
bootRun → startPostgresContainer → createPostgresContainer → pullPostgresImage
这种路径可能对持续集成环境感兴趣。
5 仅使用 Docker
还有一种可选项不需要使用 Gradle 插件。通过这种方法,我们只需使用普通 Docker来构建 Docker 映像并启动容器。
定义 Docker 文件
创建以下 Dockerfile
FROM postgres:9.6.6 (1)
VOLUME /var/lib/postgresql/data (2)
COPY ["setup.sh", "/docker-entrypoint-initdb.d/"] (3)
EXPOSE 5432 (4)
1 | 使用与之前相同的 PostgreSQL 映像和版本 |
2 | 为存储数据定义卷 |
3 | 将脚本复制到入口目录。启动容器时会执行此目录中的任何 .sh 或 .sql 文件 |
4 | 公开端口 |
还要创建文件 setup.sh
#!/bin/bash
echo "######### CREATING DATABASE ##########"
# Perform all actions as user 'postgres'
export PGUSER=postgres
# Create specific users for the application and also the databases for dev and test
psql <<EOSQL
(1)
CREATE DATABASE dev_demo_db;
CREATE ROLE dev_user WITH LOGIN PASSWORD 'dev_password';
GRANT ALL PRIVILEGES ON DATABASE dev_demo_db TO dev_user;
(2)
CREATE DATABASE test_demo_db;
CREATE ROLE test_user WITH LOGIN PASSWORD 'test_password';
GRANT ALL PRIVILEGES ON DATABASE test_demo_db TO test_user;
EOSQL
echo ""
echo "######### DATABASE CREATED ##########"
1 | 为开发环境创建数据库、用户和密码 |
2 | 为测试环境定义另一个数据库、用户和密码 |
构建映像
创建了这些文件后,我们来构建 docker 映像
$ docker build -t postgres-custom src/main/docker/
启动容器
基于我们刚刚创建的映像创建并启动容器(首次)
$ docker run -d -p 5432:5432 --name demo-postgres postgres-custom
从现在开始,我们每次想要启动/停止容器时,只需要执行
$ docker start demo-postgres
$ docker stop demo-postgres
请记住,您还需要更改 application.yml 以使用 setup.sh 文件中定义的新凭证。 |
将数据库开发环境配置替换为
environments:
development:
dataSource:
dialect: org.hibernate.dialect.PostgreSQLDialect
driverClassName: org.postgresql.Driver
username: dev_user
password: dev_password
dbCreate: update
url: jdbc:postgresql://localhost:5432/dev_demo_db