#spring-boot #docker #docker-compose #jhipster #h2
Вопрос:
Я пытаюсь настроить веб-приложение JHipster (Java Spring Boot Angular) для использования встроенной базы данных H2 в производственной среде.
Чего я хочу добиться, так это сохранить файлы базы данных, сгенерированные H2, на хост-машине, чтобы, если мне по каким-либо причинам понадобится создать резервную копию этих файлов, у меня был легкий доступ.
Приложение будет развернуто с помощью docker compose.
СРЕДА РАЗРАБОТКИ
- Я использую машину Windows для разработки этого веб-приложения.
- Я установил Docker Desktop на свою машину и работаю на движке WSL 2.
ИЗОБРАЖЕНИЕ ДОКЕРА JHIPSTER
- Образ docker, который я использую для развертывания приложения, является стандартом, используемым проектом JHipster. Это означает, что веб-приложение внутри docker работает с UID = 1000
- UID пользователя = 1000 не имеет корневого доступа (что, конечно, правильно по соображениям безопасности).
КОМПОНОВКА ДОКЕРА И КОНФИГУРАЦИИ ТОМОВ
Первая конфигурация компоновки докера, которую я попробовал, была следующей:
version: '3.8'
services:
app:
image: scriba_webapp
environment:
- ...
ports:
- ...
volumes:
- scriba-db:/tmp/h2
volumes:
scriba-db:
но когда запускаются веб-приложения, я получаю ошибку «отказано в разрешении». Просматривая журналы, кажется, что проблема возникает, когда приложение пытается инициализировать базу данных (что означает создание и запись файла в каталог контейнера /tmp/h2).
Here the stacktrace:
org.h2.message.DbException: Log file error: "/tmp/h2/db.trace.db", cause: "java.io.FileNotFoundException: /tmp/h2/db.trace.db (Permission denied)" [90034-200]
at org.h2.message.DbException.get(DbException.java:194)
at org.h2.message.TraceSystem.logWritingError(TraceSystem.java:294)
at org.h2.message.TraceSystem.openWriter(TraceSystem.java:315)
at org.h2.message.TraceSystem.writeFile(TraceSystem.java:263)
at org.h2.message.TraceSystem.write(TraceSystem.java:247)
at org.h2.message.Trace.error(Trace.java:180)
at org.h2.engine.Database.setBackgroundException(Database.java:2230)
at org.h2.mvstore.db.MVTableEngine$1.uncaughtException(MVTableEngine.java:93)
at org.h2.mvstore.MVStore.handleException(MVStore.java:2877)
at org.h2.mvstore.MVStore.panic(MVStore.java:481)
at org.h2.mvstore.MVStore.<init>(MVStore.java:402)
at org.h2.mvstore.MVStore$Builder.open(MVStore.java:3579)
at org.h2.mvstore.db.MVTableEngine$Store.open(MVTableEngine.java:170)
at org.h2.mvstore.db.MVTableEngine.init(MVTableEngine.java:103)
at org.h2.engine.Database.getPageStore(Database.java:2659)
at org.h2.engine.Database.open(Database.java:675)
at org.h2.engine.Database.openDatabase(Database.java:307)
at org.h2.engine.Database.<init>(Database.java:301)
at org.h2.engine.Engine.openSession(Engine.java:74)
at org.h2.engine.Engine.openSession(Engine.java:192)
at org.h2.engine.Engine.createSessionAndValidate(Engine.java:171)
at org.h2.engine.Engine.createSession(Engine.java:166)
at org.h2.engine.Engine.createSession(Engine.java:29)
at org.h2.engine.SessionRemote.connectEmbeddedOrServer(SessionRemote.java:340)
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:173)
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:152)
at org.h2.Driver.connect(Driver.java:69)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:364)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:476)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:561)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112)
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:266)
at org.springframework.boot.autoconfigure.liquibase.DataSourceClosingSpringLiquibase.afterPropertiesSet(DataSourceClosingSpringLiquibase.java:46)
at tech.jhipster.config.liquibase.AsyncSpringLiquibase.initDb(AsyncSpringLiquibase.java:118)
at tech.jhipster.config.liquibase.AsyncSpringLiquibase.afterPropertiesSet(AsyncSpringLiquibase.java:103)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
at it.brainylabs.scriba.ScribaApp.main(ScribaApp.java:69)
Caused by: org.h2.jdbc.JdbcSQLNonTransientException: Log file error: "/tmp/h2/db.trace.db", cause: "java.io.FileNotFoundException: /tmp/h2/db.trace.db (Permission denied)" [90034-200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:505)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)
... 56 more
Caused by: java.io.FileNotFoundException: /tmp/h2/db.trace.db (Permission denied)
at java.base/java.io.FileOutputStream.open0(Native Method)
at java.base/java.io.FileOutputStream.open(Unknown Source)
at java.base/java.io.FileOutputStream.<init>(Unknown Source)
at java.base/java.io.FileOutputStream.<init>(Unknown Source)
at org.h2.store.fs.FilePathDisk.newOutputStream(FilePathDisk.java:306)
at org.h2.store.fs.FileUtils.newOutputStream(FileUtils.java:239)
at org.h2.message.TraceSystem.openWriter(TraceSystem.java:311)
... 53 more
Exception in thread "Logback shutdown hook [default]" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.base/java.util.ArrayList$Itr.next(Unknown Source)
at ch.qos.logback.classic.LoggerContext.fireOnReset(LoggerContext.java:323)
at ch.qos.logback.classic.LoggerContext.reset(LoggerContext.java:226)
at ch.qos.logback.classic.LoggerContext.stop(LoggerContext.java:348)
at ch.qos.logback.core.hook.ShutdownHookBase.stop(ShutdownHookBase.java:39)
at ch.qos.logback.core.hook.DelayingShutdownHook.run(DelayingShutdownHook.java:57)
at java.base/java.lang.Thread.run(Unknown Source)
The next configuration I tried was using the «old» bind mounts instead of the new docker volumes:
version: '3.8'
services:
app:
image: scriba_webapp
environment:
- ...
ports:
- ...
volumes:
- type: bind
source: D:...h2
target: /tmp/h2
With this everything works fine, the web applications startup correctly and I can see the DB files in the source directory. So without other changes to the project a bind mount works correctly while a docker volume doesn’t.
The final test I did was to configure the docker volume like this:
version: '3.8'
services:
app:
image: ...
environment:
- ...
ports:
- ...
volumes:
- scriba-db
volumes:
scriba-db:
driver_opts:
type: "volume"
o: "uid=1000,rw"
device: **???**
Since the original error was a permission denied I figured I should try and give that user read/write access to the docker volume but I didn’t manage to do so since it’s not clear to me how the volume option device should be configured for this case.
If I configure the driver_opts like this:
scriba-db:
driver_opts:
type: "tmpfs"
o: "uid=1000,rw"
device: "tmpfs"
The web application starts correctly but I can’t see the DB files in the docker volume since tmpfp (If I understood this correctly) means that the FS is just temporary and will be delete once the containers stops.
ВОПРОСЫ
- Как получилось, что крепление привязки работает именно так, а у тома docker вместо этого возникают проблемы?
- Допустим, я хочу использовать том docker и сохранить эти файлы БД на хост-компьютере, как мне настроить driver_opts на компьютере с Windows?