【问题标题】:JDBC connection between Docker containers (docker-compose)Docker 容器之间的 JDBC 连接(docker-compose)
【发布时间】:2024-06-11 16:10:01
【问题描述】:

我尝试将在 tomcat 8 上运行的 Web 应用程序连接到 oracle 数据库。它们都作为 Docker 容器运行:

docker-compose.yml:

version: "3"
services:
  appweb:
   build: ./app
   image: "servlet-search-app:0.1"
   ports:
    - "8888:8080"
   links:
    - appdb
   environment:
    - DATA_SOURCE_NAME="jdbc:oracle:thin:@appdb:1521/XE"

  appdb:
   build: ./db
   image: "servlet-search-db:0.1"
   ports:
    - "49160:22"
    - "1521:1521"
    - "8889:8080"

我的 oracle DB 映像的 Dockerfile(构建:./db):

FROM wnameless/oracle-xe-11g
ADD createUser.sql /docker-entrypoint-initdb.d/
ENV ORACLE_ALLOW_REMOTE=true

tomcat 镜像的 Dockerfile(构建:./app)

FROM tomcat:8.0.20-jre8
COPY servlet.war /usr/local/tomcat/webapps/
COPY ojdbc14-1.0.jar /usr/local/tomcat/lib/

所以应用按预期启动,但在尝试连接数据库时抛出异常:

java.lang.IllegalStateException: java.sql.SQLException: Io exception: Invalid connection string format, a valid format is: "host:port:sid" 
    org.se.lab.ui.ControllerServlet.createConnection(ControllerServlet.java:115)
    org.se.lab.ui.ControllerServlet.handleSearch(ControllerServlet.java:78)
    org.se.lab.ui.ControllerServlet.doPost(ControllerServlet.java:53)
    org.se.lab.ui.ControllerServlet.doGet(ControllerServlet.java:38)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

现在问题似乎很明显,但是当我将 DATA_SOURCE_NAME 字符串修复为:

DATA_SOURCE_NAME="jdbc:oracle:thin:@appdb:1521:XE"

我得到以下异常:

java.lang.IllegalStateException: java.sql.SQLException: Listener refused the connection with the following error:
ORA-12505, TNS:listener does not currently know of SID given in connect descriptor
The Connection descriptor used by the client was:
appdb:1521:XE"

    org.se.lab.ui.ControllerServlet.createConnection(ControllerServlet.java:115)
    org.se.lab.ui.ControllerServlet.handleSearch(ControllerServlet.java:78)
    org.se.lab.ui.ControllerServlet.doPost(ControllerServlet.java:53)
    org.se.lab.ui.ControllerServlet.doGet(ControllerServlet.java:38)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

现在我试图找出其中哪一个应该真正起作用。因此,我只启动了 DB 容器:

docker build -t dbtest .
docker run -it -d --rm -p 1521:1521 --name dbtest dbtest
docker inspect dbtest | grep IPAddress
>> "IPAddress": "172.17.0.4"

接下来,我尝试用sqlplus连接:

sqlplus system/oracle@172.17.0.4:1521/XE # works
sqlplus system/oracle@172.17.0.4:1521:XE #ERROR: ORA-12545: Connect failed because target host or object does not exist

那么问题是什么?由于 docker-compose 文件中的链接,tomcat 容器可以将“appdb”解析为容器的 IP。

这是建立连接的代码:

protected Connection createConnection() {
        String datasource = System.getenv("DATA_SOURCE_NAME");
        try {
            // debug
            InetAddress address = null;
            try {
                address = InetAddress.getByName("appdb");
                System.out.println(address); // resolves in appdb/10.0.0.2
                System.out.println(address.getHostAddress()); // resolves in 10.0.0.2
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }

            Class.forName("oracle.jdbc.driver.OracleDriver");
            return DriverManager.getConnection(datasource, "system", "oracle");
        } catch (SQLException | ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }
    }

最后是 tnsnames.ora 文件:

 cat $ORACLE_HOME/network/admin/tnsnames.ora
# tnsnames.ora Network Configuration File:

XE =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = fcffb044d69d)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = XE)
    )
  )

EXTPROC_CONNECTION_DATA =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC_FOR_XE))
    )
    (CONNECT_DATA =
      (SID = PLSExtProc)
      (PRESENTATION = RO)
    )
  )

谢谢!

【问题讨论】:

  • 我猜你必须先尝试在没有 docker 图像/容器的情况下进行连接,以确保与 DB 建立连接?

标签: java oracle tomcat docker jdbc


【解决方案1】:

oracle 默认监听器将配置的主机解析到错误的 IP 地址:

vim $ORACLE_HOME/network/admin/listener.ora:

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = /u01/app/oracle/product/11.2.0/xe)
      (PROGRAM = extproc)
    )
  )

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC_FOR_XE))
      (ADDRESS = (PROTOCOL = TCP)(HOST = f4c4a3638c11)(PORT = 1521))
    )
  )

DEFAULT_SERVICE_LISTENER = (XE)

HOST 值是 Docker 容器 ID。如果我们查看 /etc/hosts,它在 docker-compose 链接中正确设置了服务链接:

10.0.0.5        f4c4a3638c11

它也可以从 tomcat 容器中正确解析

ping f4c4a3638c11
PING f4c4a3638c11 (10.0.0.5): 56 data bytes
...

如果我尝试连接另一个接口的 IP 地址,即主机系统的 docker 接口,则从 Web 应用程序到数据库的连接正常

String datasource = "jdbc:oracle:thin:@172.17.0.4:1521:XE";

所以解决方法是配置监听器监听正确的IP地址

(ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.0.5)(PORT = 1521))

现在这个连接字符串可以工作了:

jdbc:oracle:thin:@appdb:1521:XE

我会将此行为作为错误报告给 wnameless/oracle-xe-11g

【讨论】:

    【解决方案2】:

    抱歉,这不是一个确定的答案。让我们将其视为长评论:)

    你的设置对我来说非常复杂,但你的错误信息很有趣:

    The Connection descriptor used by the client was:
    appdb:1521:XE"
    ...
    

    看起来环境值被砍成appdb:1521:XE。如果你尝试硬编码怎么样:

    String datasource = "jdbc:oracle:thin:@appdb:1521/XE";
    

    如果可行,那么可能需要以某种方式转义您的 docker DATA_SOURCE_NAME 环境变量。

    我可能完全错了,但我认为值得一试。

    【讨论】:

    • 感谢您的回复,我也是这么想的。但是,这似乎是常见的错误消息。