ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 호스트와 컨테이너 사이의 통신
    인프라/도커 2024. 1. 3. 17:13

     

     

    지난 글에 이어서 서버와 DB를 함께 다양한 환경에서 사용해보겠습니다.

    아래 순서로 글을 진행해보겠습니다.

     

    - 호스트와 컨테이너 사이의 통신 

    1. DB (호스트) & 서버 (호스트) 실행시키기

    2. DB (호스트) & 서버 (도커) 실행시키기

    3. DB (도커) & 서버 (호스트) 실행시키기

     

    - 컨테이너 사이의 통신

    4. 한 개의 컨테이너에서 DB & 서버 실행시키기

    5. 각각 다른 컨테이너에서 DB & 서버 실행시키기 

    6. DB & 서버 도커 컴포즈로 관리하기

     

     


     

    1.  DB (호스트) & 서버 (호스트) 실행시키기

    기본적으로 프로젝트 생성시 사용하는 세팅입니다.

    localhost의 8080 포트와 3306 포트에서 서버와 db를 각각 실행합니다.

     

     

    스프링 cli를 이용해서 프로젝트를 생성하고 8080에 접속했을 때 'Hello World'가 보이도록 index.html을 추가합니다.

    mysql을 사용할 수 있도록 connector-j 의존성을 추가해 줍니다.

    $ spring init --dependencies=web,jpa,lombok myapp
    
    // implementation 'com.mysql:mysql-connector-j:8.2.0' 추가

     

    간단하게 Repository와 DB 작동을 확인할 수 있는 api를 작성합니다.

    // post를 생성하고 조회하는 메소드 생성
    
    @Slf4j
    @RestController
    @SpringBootApplication
    public class DemoApplication {
    
      @Autowired
      PostRepository postRepository;
    
      public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
      }
    
      @GetMapping("/post/{id}")
      public Post getPost(@PathVariable("id") long id) {
        Optional<Post> post = postRepository.findById(id);
        return post.orElse(null);
      }
    
      @PostMapping("/post")
      public boolean createPost(@RequestBody Map<String, String> body) {
        try {
          Post post = new Post(body.get("title"));
          postRepository.save(post);
          return true;
        } catch (Exception e) {
          return false;
        }
      }
    }

     

     

    빌드명령어를 실행해서 jar파일을 생성합니다.

    $ ./gradlew build

     

     

    빌드된 파일을 실행시켜서 웹서버를 띄웁니다.

    $ java -jar ./build/libs/myapp-0.0.1-SNAPSHOT.jar



    생성 요청과 조회 요청을 보내서 제대로 작동하는지 확인합니다.

    // 생성
    $ curl -X POST http://localhost:8080/post \
    	-H "Content-Type: application/json" \
    	-d '{"title": "This is test"}'
        
        
    // 조회
    $ curl http://localhost:8080/post/1

     

     

     


     

     

    2.  DB (호스트) & 서버 (도커) 실행시키기

    이전 글에서 했던 것처럼 서버를 도커의 컨테이너 환경에서 실행시키고 호스트에 있는 데이터베이스와 연결해보겠습니다.

    에러 상황을 빌드 후에 콘솔에서 확인하기 위해서 테스트를 주석처리하고 진행하겠습니다.

     

     

    테스트 주석를 처리 합니다.

    @SpringBootTest
    class DemoApplicationTests {
    
    //	@Test
    //	void contextLoads() {
    //	}
    
    }

     

     

    이미지 생성을 위해서 Dockerfile을 작성합니다.

    이전 글에서 살펴보았던 멀티 스테이지 빌드 방식으로 작성하였습니다.

    FROM eclipse-temurin:17-jdk
    
    WORKDIR workspace/app
    
    COPY ./settings.gradle /workspace/app/
    COPY ./build.gradle /workspace/app/
    COPY ./gradlew /workspace/app/
    COPY ./gradle /workspace/app/gradle/
    COPY ./src /workspace/app/src/
    
    RUN /workspace/app/gradlew build
    RUN mv /workspace/app/build/libs/myapp-0.0.1-SNAPSHOT.jar /workspace/app/app.jar
    
    
    FROM eclipse-temurin:17-jre
    
    WORKDIR workspace/app
    
    COPY --from=0 /workspace/app/app.jar /workspace/app/app.jar
    
    EntryPoint ["java", "-jar", "app.jar"]

     

     

    도커 이미지를 생성하고 컨테이너를 실행합니다.

    $ docker build -t myapp .
    $ docker run -it -p 8090:8080 myapp

     

     

    디비 연결에 실패한 것을 확인할 수 있습니다. 왜 서버로부터 어떠한 패킷도 받지 못하였을까요?

     

     

    문제는 application.yml에서 찾을 수 있습니다.

     

    기존 호스트 환경에서 실행할 때는 localhost(127. 0. 0. 1)이 호스트를 가리키고 있기 때문에  3306 실행되던 mysql에 접속이 가능했습니다. 하지만 지금 웹서버는 docker의 컨테이너에서 실행 중이기 때문에 localhost는 더 이상 호스트환경을 가리키지 않습니다. 따라서 현재 컨테이너의 3306 포트에는 아무것도 떠있지 않기 때문에 다음과 같은 문제가 발생하는 것입니다.

     

     

     

     

    * 해결방법1 - 직접 호스트의 ip를 사용해서 호출한다

     

    호스트의 Ip를 찾아보겠습니다.

    // 맥 명령어
    $ ifconfig

     

    터미널에서 위 명령어를 실행시키면 다양한 네트워크 정보를 보여주고, 잘 찾아보면 ip를 찾을 수 있습니다.

    application.yml에 있는 localhost를 방금 전에 찾은 ip로 바꿔주고 다시 이미지를 생성해서 컨테이너를 실행합니다.

    제 ip를 임의로 192.168.255.255 라고 해보겠습니다.

     

     

    이제 application.yml에서 localhost라고 되어 있는 부분을 192.168.255.255로 변경해보겠습니다.

     

    보통은 이런 식으로 호스트 ip를 직접 사용해서 호스트에 떠있는 프로그램에 접근이 가능합니다.

    하지만 mysql의 경우에는 보안 상의 이유로 조금 더 조치가 필요합니다.

     

    검색을 해보니 mysql은 보안상의 이유로 기본적으로 localhost에서의 접근만을 허용하고 있습니다.

    이러한 설정은 my.conf 파일에 되어있습니다. 저는  m1 에서 brew로 mysql을 설치하였고 아래의 경로에서 찾을 수 있었습니다.

    opt/homebrew/etc/my.conf

     

     

     

    bind address를 0.0.0.0 으로 변경하고 mysql을 재시작해주면 모든 ip로 부터 요청을 받을 수 있습니다.

    *보안상 좋지 않기 때문에 테스트 이후에 돌려놓아야합니다.*

     

    그리고 어떤 호스트에서도 접속이 가능한 유저를 생성합니다.

    mysql> CREATE USER 'user'@'%' IDENTIFIED BY 'password';
    mysql> GRANT ALL PRIVILEGES ON database_name.* TO 'user'@'%';
    mysql> FLUSH PRIVILEGES;

     

    위와 같은 설정을 해주고 application.yml에 Ip와 유저 정보를 변경해준다면 컨테이너에서 호스트로 요청이 가능합니다.

     

     

     

    * 해결방법2 - host.docker.internal

    host.docker.internal 이라는 특별한 이름을 사용하면 Docker의 호스트에 접근할 수 있습니다.

     

    빌드 후 실행시켜보면 위의 해결책과 다르게 아주 간단하게 실행되는 것을 확인할 수 있습니다.

     

     

     


     

     

     

    3.  DB (도커) & 서버 (호스트) 실행시키기

    2번과 반대로 DB를 도커 컨테이너에서 실행하고 서버를 호스트에서 실행시켜보겠습니다.

     

     

    먼저 도커 명령어를 사용해서 mysql을 실행시킵니다.

    $ docker run -d --name=mydb -p 3308:3306 -e MYSQL_ROOT_PASSWORD=12345678 mysql

     

     

    3308번 포트에 떠있는 mysql에 요청을 하기 위해 application.yml을 수정합니다.

     

     

    이번에는 로컬에서 서버를 빌드 후 실행을 해봅니다.

    $ ./gradlew build && ./gradle bootRun

     

    sqltest 테이블이 없어서 에러가 발생합니다.

     

     

    컨테이너에 부여한 이름을 사용해서 bash를 실행시키고 mysql 클라이언트를 사용해서 테이블을 만들어 줍니다.

    $ docker exec -it mydb bash
    $ mysql -u rooot -p
    
    mysql> create database sqltest;

     

     

    다시 서버를 실행하고 요청을 해보면 잘 작동하는 것을 확인할 수 있습니다.

     

    도커의 컨테이너에서 호스트로 요청을 하는 것은 어려움이 있었으나, 호스트에서 도커의 컨테이너로 요청을 하는 것에는 크게 어려움이 없습니다.

     

     

     

     

    컨테이너 사이의 통신은 다음 글에서 살펴보겠습니다.

     

     


    [출처]

    https://docs.docker.com

     

Designed by Tistory.