Docker Создает тома — Опция устройства — Windows — JHipster

#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.

ВОПРОСЫ

  1. Как получилось, что крепление привязки работает именно так, а у тома docker вместо этого возникают проблемы?
  2. Допустим, я хочу использовать том docker и сохранить эти файлы БД на хост-компьютере, как мне настроить driver_opts на компьютере с Windows?