1. 개요
2개의 스프링 컨테이너를 생성하고, 통신이 원활한지 확인한다.
2. service-b
service-a 보다 service-b를 먼저 작성하는 이유는 a가 b를 호출하는 구조이기 때문이다.
2 - 1. build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.6'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.study'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2023.0.3")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
- web
- lombok
- openFeign (호출을 당하는 쪽이라 없어도 무방한 dependency이다)
2 - 2. application.properties
spring.application.name=service-b
server.port=8080
도커 컨테이너 내부 port는 8080으로 통일한다.
호스트의 port만 중복되지 않으면 되기 때문에, 컨테이너 내부 port는 기본 port 통일하는게 좋다.
service-a는 service-b를 컨테이너 이름으로 호출할 것이기 때문에 사용자 지정 네트워크를 만들어야 한다.
이 내용은 아래에서 다시 다루기로 하자.
2 - 3. BController
@RestController
public class BController {
@GetMapping("/hello")
public String hello() {
return "Hello";
}
}
그저 hello를 반환하는 간단한 컨트롤러이다.
3. service-a
3 - 1. build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.6'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.study'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2023.0.3")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
- web
- openFeign
- lombok
3 - 2. AAplication
package com.study.a;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication
public class AApplication {
public static void main(String[] args) {
SpringApplication.run(AApplication.class, args);
}
}
service-a는 service-b를 호출하기 위해 `@EnableFeignClients` 를 선언해준다.
3 - 3. application.properties
spring.application.name=service-a
server.port=8080
service.b.url=http://service-b:8080
도커 컨테이너 내부의 port는 8080로 통일한다.
호스트의 port만 중복되지 않으면 되기 때문에, 컨테이너 내부 port는 기본 port 통일하는게 좋다.
3 - 4. BClient
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "service-b", url= "${service.b.url}")
public interface BClient {
@GetMapping("/hello")
public String getHello();
}
url은 application.properties에서 가져오는 값으로, "http://service-b:8080"이다.
3 - 5. AController
package com.study.a;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController
public class AController {
private final BClient bClient;
@GetMapping("/hi")
public String hi() {
String hello = bClient.getHello();
return "service-a : hi ###### service-b: " + hello;
}
}
BClient를 통해 데이터를 가져와서 합친 뒤에 사용자에게 반환하는 코드이다.
service-b와 통신하고 있는지를 테스트해보기 위한 용도이다.
4. Dockerfile
service-a와 service-b에 각각 Dockerfile을 생성한다.
FROM openjdk:17-jdk-slim
VOLUME /tmp
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
둘 다 동일한 내용을 담고 있다.
4 - 1. FROM openjdk:17-jdk-slim
해당 명령어는 베이스 이미지로 `openjek:17-jdk-slim`을 사용하도록 지정한다.
4 - 2. `VOLUME /tmp`
`/tmp` 디렉토리를 호스트와 공유하는 익명 볼륨으로 설정한다.
컨테이너가 종료되어도 해당 디렉토리의 데이터를 유지할 수 있게 한다.
4 - 3. `ARG JAR_FILE=build/libs/*.jar`
빌드 시점에 사용할 수 있는 변수 `JAR_FILE`을 정의한다.
기본값으로 `build/libs/*.jar` 경로를 설정하여, 빌드된 JAR 파일의 위치를 지정한다.
해당 변수는 `docker build` 명령어 실행시 다른 값으로 대체 가능하다.
4 - 4. `COPY ${JAR_FILE} app.jar`
호스트 시스템의 JAR 파일을 컨테이너 내부의 `app.jar`로 복사한다.
4 - 5. `ENTRYPOINT ["java", "-jar", "/app.jar]`
컨테이너가 시작될 때 실행할 명령어를 지정한다.
Java 애플리케이션을 실행하기 위해 `java -jar /app.jar` 명령어를 사용한다.
ENTRYPOINT는 컨테이너가 시작될 때 항상 실행되는 기본 프로그램을 설정한다.
5. 도커 네트워크 생성
도커끼리 컨테이너 이름으로 호출하기 위해서는 기본 브리지 네트워크가 아닌 사용자 지정 네트워크에서 진행해야 한다.
docker network create my-network
6. service-b 실행하기
6 - 1. 프로젝트 빌드
프로젝트 루트 폴더에서 진행한다.
./gradlew clean bootJar
6 - 2. 이미지 생성
- 이미지 이름을 img-service-b로 만든다.
- 마지막 . 은 Dockerfile의 위치
docker build -t img-service-b .
- -t 옵션은 이미지에 이름과 태그를 부여하는 데 사용된다.
- 태그를 명시하지 않으면 자동으로 `latest` 태그가 부여된다.
6 - 3. 컨테이너 생성
- 컨테이너 이름을 service-b, 외부 포트는 18081로 지정한다.
- 네트워크는 만들어 두었든 `my-network`를 지정한다.
docker run -d --name service-b --network my-network -p 18081:8080 img-service-b
7. service-a 실행하기
7 - 1. 프로젝트 빌드
- 프로젝트 루트 폴더에서 진행한다.
./gradlew clean bootJar
7 - 2. 이미지 생성
docker build -t img-service-a .
7 - 3. 컨테이너 생성
docker run -d --name service-a --network my-network -p 18080:8080 img-service-a
- 이름은 service-a
- 네트워크는 my-network
- 18080 Host Port
- 이미지는 img-service-a
8. API 호출
두 인스턴스를 실행시킨 상태이므로, API 호출을 해보자.
service-a에 요청을 보냈더니, 정상적으로 service-b와 통신 후 응답을 내려줬다.
'인프라 > docker' 카테고리의 다른 글
2024 12 4 TIL - Docker Compose (1) | 2024.12.04 |
---|---|
2024 12 2 TIL - Docker (1) | 2024.12.02 |