Spring 단위 테스트

테스트 종류

Spring는 다양한 시나리오 별로, 여러 개의 테스트를 수행할 수 있는 기능을 제공한다. 테스트는 다음과 같다.

SpringBootTest

전체 애플리케이션 컨텍스트를 로드하여 통합 테스트를 수행한다.

@SpringBootTest는 Spring Boot 애플리케이션을 완전하게 로드하여 실제 애플리케이션과 같은 환경에서 테스트를 수행한다. 데이터베이스, 메시지 큐, REST API 등 모든 컴포넌트를 포함한 전체 애플리케이션을 테스트하고자 할 때 유용하다.

 

사용 예)

@SpringBootTest private class Test() {
@Autowired private MyService myService;
@Test void testServiceMethod() {
}
}

 

DataJpaTest

JPA 리포지토리 관련 테스트를 위한 어노테이션이다.

JPA와 관련된 빈만 로드하고, 데이터베이스와 관련된 테스트를 진행한다.

사용 예)

@DataJpaTest public class Test {
@Autowired private UserRepository userRepository;
@Test void test() {
User user = new User(“John”, “john@example.com“);
userRepository.save(user);

User foundUser = userRepository.findById(user.getId()).orElse(null);
assertThat(foundUser).isNotNull;
}
}

 

WebMvcTest

웹 계층(컨트롤러, HTTP 요청/응답)만 테스트를 진행하는 어노테이션이다.

Spring MVC의 웹 컴포넌트만 로드하여 컨트롤러 테스트에 사용한다.

사용 예)

@WebMvcTest(Controller.class) public class Test {
@Autowired private MockMvc mockMvc;
@Test void testController() throws Exception {
mockMvc.perform(get(“/api/endpoint”))
.andExpect(status().isOk())
.andExpect(jsonPath(“$.name”).value(“John”));
}
}

 

MockBean

목적: 테스트에서 빈을 mock으로 대체할 때 사용한다.

설명: 실제 빈 대신 mock 객체를 주입하여 테스트에서 의존성을 격리할 때 사용한다.

사용 예)

@WebMvcTest(Controller.class) public class Test {
@MockBean private MockService mockService;
@Test void testController() throws Exception {

 

Mockito.when(mockService.getUser()).thenReturn(new User(“John”));

mockMvc.perform(get(“/api/endpoint”))
.andExpect(status().isOk())
.andExpect(jsonPath(“$.name”).value(“John”));
}
}

 

TestConfiguration

테스트 환경에서만 사용하는 설정을 정의할 때 사용한다.

테스트 전용 설정을 작성할 수 있는 어노테이션이다.

사용 예)

@TestConfiguration static class MyTestConfig {
@Bean public MyService myService() {
return new MyServiceImpl();
}
}

 

JsonTest

JSON 직렬화/역직렬화 테스트.

객체를 JSON으로 직렬화하거나 JSON을 객체로 역직렬화하는 로직을 테스트할 때 사용.

사용 예)

@JsonTest public class UserJsonTest {
@Autowired private ObjectMapper objectMapper;

@Test void testSerialization() throws JsonProcessingException {
User user = new User(“John”, “john@example.com”);
String json = objectMapper.writeValueAsString(user);
assertThat(json).contains(“John”);
}
}

 

ConfigurationPropertiesTest

@ConfigurationProperties로 설정된 프로퍼티 클래스 테스트

프로퍼티 값이 제대로 바인딩되는지 테스트.

사용 예)

@ConfigurationPropertiesTest public class MyConfigPropertiesTest {
@Autowired private MyConfigProperties properties;

@Test void testProperties() {
assertThat(properties.getName()).isEqualTo(“John”);
}
}

 

ActiveProfiles

특정 프로파일을 활성화하여 테스트 환경 설정.

설정 파일이나 환경을 테스트용으로 변경하고 싶을 때 사용.

사용 예)

@SpringBootTest
@ActiveProfiles(“test”)
public class MyServiceTest {
@Autowired
private MyService myService;

@Test
void testService() {
// ‘test’ 프로파일 환경에서 테스트
}
}

 

기타 문제 해결

Spring Application 없이 DataJpaTest 설정 시 Configuration 관련 에러

DataJpaTest는, JPA로 설정된 데이터들만 불러오는 것이 아니라, 어플리케이션을 초기화 하고, JPA와 관련 없는 Bean들을 무시하고 로딩되는 테스트이다. 따라서, Application 설정 파일이 존재해야만 한다. 다음과 같은 작업으로, 클래스를 만들고, 테스트 케이스를 구동할 수 있다.

  1. TestCase 내부에 Application 클래스 생성

    클래스 내부에, 임시 구동을 위한 Application을 아래와 같이 생성한다.

     

    @SpringBootApplication

    public static class TestApplication {}

     

  2. 만약 외부에 참고해야 할 Component 들이 존재한다면, 해당 경로를 설정해 준다.

    보통 SprinbBootApplication Annotation을 추가하게 되면, 자동으로 Component들을 Import 한다. 하지만, 대상이 되는 Component들이 해당 클래스의 하위 클래스 들로만 한정되기 때문에, 1번으로 인해 TestCase 내부에 생성된 TestApplication은 외부 참조를 수행하지 못하게 된다. Pakcage나 Classes 레벨로 추가할 수 있다.

     

    @ComponentScan(basePackages = {…})

     

  3. Entity 클래스와 Repository Interface의 필수 Annotation을 확인한다.

    Entity 클래스의 경우는 필수적으로 Entity Annotation이 클래스에 추가되어 있어야 하며, Repository의 경우는, Repository Annotation이나, JpaRepository<T, U>를 상속 받아야 한다.

Spring boot – Quartz

Quartz

Java 기반의 오픈소스 “작업 스케줄링 라이브러리”이다. 이를 사용하여 특정 시간에 작업을 실행하거나 특정 간격으로 작업을 수행할 수 있다.

이와 같이 많이 쓰이는 Spring Scheduler와의 차이점은 다음과 같다.

Quartz

  • 매우 강력하고 유연한 스케줄링 라이브러리로, 복잡한 스케줄링 작업을 처리하는데 특화되어 있다. 예를 들어, Cron 표현식을 지원하고, 반복적인 작업, 일정한 시간 간격으로 작업을 실행하거나, 특정 시간대에 작업을 실행하는 등의 고급 기능을 제공한다.
  • Quartz는 독립적으로 사용할 수도 있으며, 자체적인 데이터베이스나 클러스터링 기능도 지원한다.
  • 데이터베이스를 통한 클러스터링이나, 분산 작업이 가능하다.

Spring Scheduler

  1. Spring Framework의 일부로 비교적 단순한 스케줄링 작업을 다루기 위해 설계되었다. 기본적으로 @Scheduled 어노테이션을 사용하여 간단한 주기적인 작업을 설정할 수 있다. 주기적인 실행, 딜레이 및 고정된 속도 기반의 작업에 적합하다.
  2. Quartz에 비해 기능이 단순하고 복잡한 작업을 처리 하는 데는 한계가 있다.

 

주요 클래스 및 인터페이스

  1. Job (인터페이스): 실행할 작업을 정의하는 인터페이스
  2. JobDetail (인터페이스): 실행될 작업을 정의하고 구성하는 인터페이스
  3. JobBuilder (클래스): JobDetail인스턴스를 생성하는데 사용되는 유틸리티 클래스
  4. JobListener (인터페이스): 작업의 생명 주기 동안 발생하는 이벤트를 처리하는 인터페이스
  5. JobDataMap (클래스): 작업 실행시 필요한 데이터를 저장하는 맵
  6. Trigger (인터페이스): 작업의 실행 시간을 결정하는 인터페이스
  7. CronTrigger (인터페이스): 복잡한 실행 스케쥴을 정의할 수 있는 Trigger 인터페이스
  8. TriggerBuilder (클래스): Trigger 인스턴스를 생성하는 데 사용되는 유틸리티 클래스
  9. SimpleScheduleBuilder (클래스): 간단한 실행 크세줄을 정의할 수 있는 클래스
  10. CronScheduleBuilder (클래스): 복잡한 실행 스케줄을 cron표현식으로 정의할 수 있는 클래스
  11. Schedule (인터페이스): 작업과 트리거를 관리하고 실행하는 인터페이스
  12. ScheudlerFactory (클래스): Scheduler 인스턴스를 생성하는 클래스

Spring boot 상에서의 구현

의존성 추가

의존성은 org.springframework.boot:spring-boot-starter-quartz를 추가해 준다.

설정 추가

Spring boot의 설정 파일 (application.yml)에 다음의 설정 정보를 추가해 준다.

 

상세 설정 정보

  1. spring.quartz.job-store-type=jdbc

    설명: Quartz가 작업과 트리거 정보를 저장할 방식으로 JDBC를 사용하도록 지정한다. 이 설정은 Quartz가 메모리 대신 데이터베이스에 정보를 저장하게 한다. 이로 인해 애플리케이션이 재시작되더라도 작업 정보를 유지할 수 있다.

     

  2. spring.quartz.jdbc.initialize-schema=always

    설명: JDBC저장소를 사용하면 다음 예와 같이 시작 시 스키마를 초기화 할 수 있다. 기본적으로 제공되는 스크립트를 사용하여 감지되고 초기화 된다. 이렇게 설정하게 되면, 기존 테이블을 삭제하고 모든 재시작 시 모든 트리거를 자동 삭제한다.

     

  3. spring.quartz.scheduler-name=SpringBootQuartzScheduler

    설명: Quartz 스케줄러의 이름을 설정한다. 이 이름은 Quartz 인스턴스를 구별할 때 사용된다. 예를 들어, 여러 개의 스케줄러가 동시에 실행되는 경우, 각 스케줄러를 구분할 수 있도록 이름을 지정할 수 있다.

     

  4. spring.quartz.properties.org.quartz.scheduler.instanceName=MyScheduler

    설명: Quartz 스케줄러의 인스턴스 이름을 설정한다. MyScheduler는 이 인스턴스를 구별하는 이름으로 사용되며, Quartz 스케줄러가 여러 개의 인스턴스를 가질 경우 이를 식별하는 데 유용하다.

     

  5. spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO

    설명: instanceId는 Quartz 스케줄러의 인스턴스를 고유하게 식별하는 데 사용된다. AUTO로 설정하면 Quartz가 자동으로 인스턴스 ID를 할당한다. 이 설정은 클러스터 환경에서 각 스케줄러 인스턴스가 고유한 ID를 가질 수 있도록 돕는다.

     

  6. spring.quartz.properties.org.quartz.threadPool.threadCount=5

    설명: Quartz 스케줄러에서 사용할 스레드 수를 설정한다. 여기서는 5개의 스레드를 사용할 수 있도록 지정했다. 여러 작업을 동시에 처리하기 위해 사용되는 스레드의 개수를 제한하는 설정이다. 작업이 많을 때 이 수치를 늘리면 성능을 개선할 수 있다.

     

  7. spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX

    설명: JobStore는 Quartz에서 작업과 트리거를 어떻게 저장할지 결정하는 클래스다. JobStoreTX는 트랜잭션을 지원하는 JobStore 클래스로, 작업 저장과 관련된 데이터베이스 트랜잭션을 처리한다. 이 설정은 JDBC를 사용할 때 트랜잭션을 지원하는 JobStoreTX를 선택하도록 지정한 것이다.

     

  8. spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

    설명: DriverDelegate는 Quartz의 JobStore가 사용하는 데이터베이스에 맞는 SQL 문을 생성하는 클래스다. StdJDBCDelegate는 일반적인 데이터베이스 (MySQL, PostgreSQL, Oracle 등)에서 사용되는 기본 Delegate 클래스다. 이 설정은 기본적인 SQL 구문을 사용하여 데이터베이스와 상호작용하도록 지정한다.

     

  9. spring.quartz.properties.org.quartz.jobStore.dataSource=QuartzDataSource

    설명: Quartz에서 사용할 데이터 소스의 이름을 설정한다. QuartzDataSource는 Quartz가 작업과 트리거 데이터를 저장할 데이터베이스 연결을 지정하는데 사용된다. 이 설정을 통해 QuartzDataSource라는 이름의 데이터 소스를 참조하도록 설정한다.

     

  10. spring.quartz.properties.org.quartz.dataSource.QuartzDataSource.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

    설명: 위에서 언급한 QuartzDataSource에 대한 설정이다. 이 속성은 QuartzDataSource 데이터 소스가 사용할 SQL 구문을 정의하는 DriverDelegate 클래스를 설정한다. StdJDBCDelegate는 기본적인 SQL 작업을 처리할 수 있도록 지정하는 클래스이다.

     

  11. spring.quartz.properties.org.quartz.dataSource.QuartzDataSource.URL=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1

    설명: Quartz가 사용할 데이터베이스 URL을 설정한다. 여기서는 H2 데이터베이스를 메모리 모드에서 사용하도록 설정되어 있다. jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1은 H2 데이터베이스가 메모리에 생성되며, 애플리케이션이 종료되더라도 데이터베이스가 닫히지 않도록 설정하는 URL이다. 실제 운영 환경에서는 MySQL, PostgreSQL 등을 사용할 수 있다.

     

  12. spring.quartz.properties.org.quartz.dataSource.QuartzDataSource.user=sa

    설명: Quartz가 사용할 데이터베이스의 사용자 이름을 설정한다. 여기서는 H2 데이터베이스의 기본 사용자 이름 sa로 설정되어 있다. 실제 환경에서는 데이터베이스 연결에 사용될 적절한 사용자 이름을 지정해야 한다.

     

  13. spring.quartz.properties.org.quartz.dataSource.QuartzDataSource.password=password

    설명: Quartz가 데이터베이스에 접속할 때 사용할 비밀번호를 설정한다. password는 H2 데이터베이스에 대한 기본 비밀번호다. 운영 환경에서는 데이터베이스에 적합한 비밀번호를 설정해야 한다.

     

  14. spring.quartz.properties.org.quartz.dataSource.QuartzDataSource.maxConnections=5

    설명: Quartz가 사용할 최대 연결 수를 설정한다. 5로 설정하면 데이터베이스에 동시에 최대 5개의 연결을 유지할 수 있다. 데이터베이스 연결 풀의 크기를 제한하여 리소스 관리를 최적화하는 데 유용하다.

     

  15. spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_

    설명: Quartz가 자동 생성할 테이블의 접두사를 설정한다. QRTZ_로 설정하면, 생성되는 테이블이 다음과 같이 된다. QRTZ_JOB_DETAILS, QRTZ_TRIGGERS

     

  16. spring.quartz.properties.org.quartz.jobStore.isClustered=true

    설명: 클러스터링 환경을 사용할 경우, 설정한다. 이 설정을 사용하면, 여러 대의 서버나 인스턴스가 하나의 Quartz 스케줄러로서 작업과 트리거를 공유하고 중복 없이 실행할 수 있는 조건을 만들어 준다. 이를 통해 중복 실행 방지, 부하 분산, 고가용성을 확보할 수 있다.

     

  17. spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000

    설명: 각 Quartz 인스턴스가 주기적으로 자신을 데이터베이스에 “체크인” 하는 간격을 설정한다. 이를 통해 각 인스턴스들이 자신이 정상 상태인지를 서버에 알리게 된다. 기본값은 20초이며 단위는 ms 이다.

     

  18. spring.quartz.properties.org.quartz.jobStore.maxMisfiresToHandleAtATime=5

    설명: 미처리된 작업을 처리할 때, 한번에 처리할 수 있는 최대 개수를 지정하는 옵션이다.

Java Application Config

QuartzConfig를 담음과 같이 생성하여, Job Schedule을 생성할 수 있다.

 

실제 Job 클래스

실제 작업을 수행하는 클래스이다. execute에서 작업을 수행하는 코드를 작성하여 실행한다.

@Component

public class MyJob implements Job {

 

@Override

public void execute(JobExecutionContext context) throws JobExecutionException {

System.out.println(“Quartz Job is running…”);

}

}

 

JobDetail 및 Trigger 설정

작업의 트리거 및 작업 세부 내용을 설정하는 클래스다. 작업을 언제 시작할지 설정하고, 어떤 정보를 통해 실행할 지 등의 설정을 수행한다.

@Configuration

public class QuartzConfig {

 

@Bean

public JobDetail jobDetail() {

return JobBuilder.newJob(MyJob.class)

.withIdentity(“myJob”)

.storeDurably()

.build();

}

 

@Bean

public Trigger trigger() {

return TriggerBuilder.newTrigger()

.withIdentity(“myTrigger”)

.withSchedule(CronScheduleBuilder.cronSchedule(“0/5 * * * * ?”)) // 매 5초마다 실행

.forJob(jobDetail())

.build();

}

 

@Bean

public Scheduler scheduler() throws Exception {

SchedulerFactory schedulerFactory = new StdSchedulerFactory();

Scheduler scheduler = schedulerFactory.getScheduler();

scheduler.scheduleJob(jobDetail(), trigger());

scheduler.start();

return scheduler;

}

}

 

데이터베이스를 이용한 처리

Quartz는 기본적으로 작업과 트리거를 데이터베이스 저장할 수 있다. 단! 주의할 것이 이렇게 DB를 통한다는 의미가, 코드 수정 없이 Job, JobDetail, Trigger를 추가한다는 것이 아니라, 해당 정보의 Metadata만을 저장하고, 이를 통해 어플리케이션이 종료되거나 서버가 재시작 되더라도 스케줄링 작업을 계속 추적하고 실행할 수 있다는 것이다.

JobDetail => JOB_DETAILS

Trigger => TRIGGERS

Quartz는 자체적으로 스케줄러의 상태를 저장하기 위해 필요한 테이블을 제공한다. 이를 위해Quartz의 데이터베이스 테이블을 생성하는 SQL스크립트를 제공하며, 이를 싱행하여 필요한 테이블을 설정해야 한다.

데이터베이스는 기본적으로, H2, MySQL, PostgreSQL 등에 맞는 SQL 테이블 생성 스크립트를 제공한다.

저장되는 정보와 동작 방식

Job클래스는 Quartz 스케줄러가 실행할 작업을 정의한다. 이 클래스에는 실제 동작할 비즈니스 로직이 포함되어 있다.

JobDetail객체와 Trigger객체는 Quartz 스케줄러에서 작업이 언제, 어떻게 실행될지를 정의하는 데 사용하게 된다.

Quartz는 JobDetail 및 Trigger 정보를 데이터베이스에 저장한다. 하지만 클래스 정보 자체를 저장하지 않고, 메타데이터(작업 이름, 그룹, 클래스 이름 등)만을 저장한다.

 

클러스터링

클러스터링의 장점

  1. 확장성: 여러 서버가 동일한 Quartz 스케줄러를 공유하므로, 클러스터에 새로운 서버를 추가하여 처리 능력을 확장할 수 있다.
  2. 내결함성: 하나의 서버가 다운되더라도 다른 서버가 작업을 계속해서 처리할 수 있어 시스템의 고가용성을 제공한다.
  3. 작업 중복 실행 방지: 클러스터링된 환경에서, 동일한 작업이 여러 번 실행되지 않도록 보장한다.

클러스터링 사용 시 주의점

  1. 데이터베이스: 클러스터 모드를 사용할 때는 모든 서버 인스턴스가 동일한 데이터베이스에 접근할 수 있어야 한다. 따라서, DB의 성능과 동시 처리 능력을 고려해야 한다.
  2. 네트워크 지연: 여러 서버가 분산 환경에서 통신해야 하므로, 네트워크의 지연이나 장애가 작업 처리에 영향을 미칠 수 있다.
  3. 잠금 관리: 분산 잠금 메커니즘을 통해 작업의 중복 실행을 방지하는데, 이 과정에서 잠금 경합(lock contention)이 발생할 수 있다.

상황 별 정보

멀티 스레드 관련

작업이 종료되기 전에, 동일 작업에 대한 요청이 들어오게 되면, Quartz는 일단 이전 작업이 종료될 때 까지 대기하게 된다. 클러스터 환경에서도 동일하다.

하지만, 동시 설정을 위하여 다음과 같이 설정하면, 설정한 정보를 활용하여 동시 수행이 가능하게 설정할 수 있다.

spring.quartz.properties.org.quartz.threadPool.threadCount=[동시수행 Thread개수]

Apache – Guacamole를 통한 웹기반 원격 접근

Apache – Guacamole

Apache Guacamole은 VNC, RDP, SSH 등을 지원하는 웹 기반 원격 데스크탑 솔루션이다. 웹 브라우저 만으로 서버에 접속 기능을 제공하며, 인증 및 권한 관리 기능도 제공하고 있다.

Docker 설치

Apache Guacamole은 Docker Hub를 통해 “guacamole/guacd”, “guacamole/guacamole” 이름으로 이미지를 제공하고 있다.

# docker run –name [DOCKER_NAME] -d guacamole/guacamole

Guacd

Guacd는 백엔드 서버 역할을 수행하는 서버다. 4822포트를 사용하고 있으며, 아래와 같이 서비스를 설치할 수 있다. 다만 주의할 점은, 해당 서버는 일체의 인증 관련 기능을 제공하지 않고, 서비스되고 있기 때문에, 해당 포트의 공개에 신중해야 할 것이다.

되도록 docker 내부에서만 서비스 되도록 설정하고, 외부 포트로 공개하지 않는 것이 좋은 방법일 것이다.

# docker run –name [DOCKER_NAME] -d guacamole/guacd
# docker run –name [DOCKER_NAME] –d -p 4822:4822 guacamole/guacd

 

Guacamole

Guacamole은 웹 어플리케이션으로 8080 포트를 이용하여 서비스를 제공하고 있다. 또한 Docker 로 구성할 경우 반드시 guacd 먼저 설치하고, 해당 Container와 링크를 정의하여 배치해 주어야 한다.

# docker run –name [DOCKER_NAME] -d -p 8080:8080 –link [GUACAD_NAME]:guacd guacamole/guacamole

수동 정보 입력

만약 Guacad 서버를 링크하지 않거나, 외부 포트를 통해 직접 연결 정보를 입력해야 할 경우, 아래와 같이 환경 변수를 통해 정의해 줄 수 있다.

  1. GUACD_HOSTNAME : Guacd 호스트 인스턴스 이름
  2. GUACD_PORT : Guacd 호스트 서비스 포트 이름

# docker run –name [DOCKER_NAME] -e GUACD_HOSTNAME=172.17.42.1 -e GUACD_PORT=4822 -d -p 8080:8080 guacamole/guacamole

 

MySQL 인증 정보 설정

Guacamole은 프로젝트 웹사이트에서 제공하는 확장 기능을 통해 MySQL, PostgreSQL 또는 SQL Server 데이터베이스를 통한 인증을 지원한다. 인증에 데이터베이스를 사용하면 연결의 부하 분산 그룹과 웹 기반 관리 인터페이스를 사용할 수 있는 기능과 같은 추가 기능이 제공된다.

DB 스크립트 추출 및 테이블 생성

아래 명령을 통해, 인증 DB 생성 스크립트를 추출하고,

# docker run –rm guacamole/guacamole /opt/guacamole/bin/initdb.sh –mysql > initdb.sql

해당 스크립트를 통해, MySQL에 Guacamole 관련 테이블과 정보를 만든다.

 

Gacamole을 MySQL에 연결

아래와 같이 Guacamole서버를 Link를 통해 DB 접근을 설정하거나,

# docker run –name [DOCKER_NAME] –link [GUACD_NAME]:guacd –link [MYSQL_NAME]:mysql -d -p 8080:8080 guacamole/guacamole

환경 설정 파일을 통해 직접 설정해 줄 수도 있다.

  1. MYSQL_HOSTNAME: 인증에 사용할 데이터베이스 호스트 네임 (필수)
  2. MYSQL_PORT: 대상 포트, 입력되지 않으면 기본값이 사용됨 (3306)

# docker run –name [DOCKER_NAME] –link [GUACD_NAME] -e MYSQL_HOSTNAME=[IP] -e MYSQL_PORT=[PORT] -d -p 8080:8080 guacamole/guacamole

 

위 명령에서는 추가되지 않았지만, 필수적으로 입력해야 하는 정보는 아래와 같다.

  1. MYSQL_DATABASE: 대상 DB
  2. MYSQL_USER: 접속 User 이름
  3. MYSQL_PASSWORD: 접속 패스워드

혹은 선택적으로 아래 변수를 사용할 수도 있다.

  1. MYSQL_ABSOLUTE_MAX_CONNECTIONS: 최대 연결 수, 기본값 0 (무제한)
  2. MYSQL_DEFAULT_MAX_CONNECTIONS: 연결 하나에 허용할 최대 동시 연결 수, 기본값 0 (무제한)
  3. MYSQL_DEFAULT_MAX_GROUP_CONNECTIONS: 연결 그룹 하나에 허용할 최대 동시 연결 수, 기본값 0 (무제한)
  4. MYSQL_DEFAULT_MAX_CONNECTIONS_PER_USER: 단일 사용자가 Guacamole 연결에 대해 유지할 수 있는 최대 동시 연결 수, 기본값 0 (무제한)
  5. MYSQL_DEFAULT_MAX_GROUP_CONNECTIONS_PER_USER: 한 사용자가 모든 연경 그룹에 유지할 수 있는 최대 동시 연결 수, 기본값 0 (무제한)
  6. MYSQL_AUTO_CREATE_ACCOUNTS: MySQL 데이터베이스에 없는 계정이 다른 모듈을 통해 성공적으로 인증되면 자동으로 생성되는지 여부. “true”로 설정하면 계정이 자동으로 생성. 그렇지 않으면 기본적으로 계정이 자동으로 생성되지 않으며 MySQL 데이터베이스 확장 프로그램 내에서 다른 모듈로 인증된 사용자에게 권한을 할당하려면 수동으로 만들어야 함.

기타 자세한 세부 설정

기타 자세한 세부 설정은 아래 링크를 참조

https://guacamole.apache.org/doc/gug/guacamole-docker.html

 

접속 주소 변경 방법

Docker 이미지를 이용하여 설치를 진행하게 되면, 기본적으로 접속 주소가, https://[Address]/guacamole/ 주소로 설정된다.

Nginx Proxy를 통한 설정

아래와 같이 redirect 문구를 통한 주소 변경 요청 설정

rewrite ^/$ /guacamole redirect;

 

접속 정보 입력

Windows Desktop

Guacamole을 통해 Windows Desktop 원격 접속을 설정하려면 아래와 같이 입력하여 설정하면 된다.

  1. 계정 정보 Drop Down 버튼을 이용하여 설정으로 이동
  2. 연결 탭으로 이동
  3. 새 연결 버튼을 이용하여 연결 정보 입력 창으로 이동하여, 아래와 같이 입력한다.
    1. 연결 편집
      1. 이름 : 연결 정보에 대한 이름을 입력함
      2. 위치 : ROOT 위치에 입력되도록 설정 (혹은 해당 위치를 적절히 변경)
      3. 프로토콜 : RDP 를 설정
    2. 매개변수
      1. 네트워크
        1. 호스트 이름 : 접속할 연결 호스트 이름을 입력. Guacd가 내부 아이피 대역에 설치되어 있다면, 호스트 이름으로 내부 아이피를 입력하여 연결 정보를 설정할 수 있다.)
        2. 포트 : 접속 포트로, RDP의 경우는 3389로 입력
      2. 인증
        1. 사용자 이름: 사용자의 이름을 입력한다. 만약 Microsoft Account를 사용하고 있을 경우, 이메일을 포함한 전체 계정 정보를 입력한다.
        2. 패스워드 (옵션): 사용중인 패스워드를 입력하고, 입력하지 않으면 접속 시 다시 한번 묻게 된다.
        3. 도메인: “MicrosoftAccount”를 입력한다.
        4. 안전 모드: NLA (네트워크 수준 인증)을 선택
        5. 서버 인증서 무시: 체크 (활성화)
      3. Wake-on-LAN (WoL) – 현재 설정해도 동작하지 않음.

        만약 WoL을 통해 접속 전에, PC 전원을 켜서 접속 가능 상태로 설정하려면 아래 설정을 추가한다. (다만, 실행되는 guacd 서비스가 대상 PC와 같은 서브넷으로 묶여야 한다. 자세한 사항은 아래 “Wake on LAN 설정 방법” 참고

        1. WoL 패킷 전송: 체크 (활성화)
        2. 원격 호스트 MAC 어드레스: 대상 PC의 맥 주소로 입력되지 않으면 해당 기능이 동작하지 않는다.
        3. WoL 패킷의 브로드케스트 주소: 기본값은 255.255.255.255 이며, 같은 서브 넷 범위의 모든 대상으로 패킷을 보낸다.
        4. 호스 부트 대기 시간: WoL을 전송한 후, 접속을 대기할 시간

Wake on LAN (WoL) 설정 방법

Docker의 Bridge 네트워크는, 내부의 Container들을 가상의 서브넷으로 묶어, NAT 기반으로 네트워크를 제공하는 방식이다. 따라서, 해당 네트워크 상에서 아무리 WoL로 매직 패킷(WoL 신호를 보내 PC를 깨우는데 사용하는 패킷)을 브로드캐스팅 한다고 하더라고, 해당 패킷은 대상에 미치지 못한다.

네트워크 설정

위와 같은 문제를 해결하기 위해서는, 깨울 PC가 존재하는 서브넷 상에서 매직패킷을 브로드캐스팅 할 수 있는 조건을 만들어 줘야 한다.

host 네트워크 사용

Docker는 bridge 네트워크 이외에 host 네트워크를 제공하고 있다. host네트워크는, Host PC의 네트워크 인터페이스를 직접 사용하도록 설정할 수 있다.

  1. Web Interface를 제공하는 guacd 서비스의 네트워크를 bridge에서 host로 변경한다. 혹은 재 생성한다. 그리고, Guacamole서비스의 접속 정보를 다음과 같이 변경한다.
  2. 네트워크 링크 guacd를 제거한다.
  3. guacamole 서비스의 환경 설정 파일을 다음과 같이 변경한다.
    1. GUACD_HOSTNAME=[HOST 서버 주소]
    2. GUACD_PORT=4822

     

    # docker run –name guacamole-guacamole … -e GUACD_HOSTNAME=[HOST 서버 주소] -e GUACD_PORT=4822 … -d –p 8080:8080 guacamole/guacamole

     

위와 같이 설정하면, 서비스를 제공하는 guacd 서버가 Host 네트워크 서브넷으로 설정되어 대상 서버로 Magic Packet을 보낼 수 있는 구조가 된다.

Docker 환경에서의 Kubernetes 시스템 구축

Kubernetes 관리 도구

Kubernetes를 관리하는 도구 중 대표적인 것은 다음과 같이 존재한다.

  1. kubectl: 쿠버네티스 API 서버와 상호작용하여 클러스터의 상태를 조회하거나 리소스를 관리하는데 사용하는 도구
    1. Pod, Service, Deployment, ReplicaSet 등 다양한 리소스를 생성, 조회, 업데이트, 삭제할 수 있음
  2. kubeadm: 쿠버네티스 클러스터를 설정하고 관리하는 도구
    1. 쿠버네티스 클러스터의 초기화 및 설정
    2. 마스터 노드 및 워커 노드의 설정
    3. 클러스터 업그레이드 및 유지 관리
  3. kubelet: 쿠버네티스 클러스터에서 각 노드에 설치되어 실행되는 에이전트
    1. 노드에서 실행되는 모든 Pod와 컨테이너의 상태를 지속적으로 관리
    2. 컨테이너 런타임 (Docker, containerd, cri-o등)과 상호 작용하여 컨테이너를 실행하고 관리
    3. 노드의 리소스를 모니터링 하고, 필요한 경우 리소스 제한을 적용

Kubernetes 관련 도구 설치

필수로 설치를 해야 하는 도구는 kubectl, kubeadm, kubelet이다. 이 도구들은, 각각 적절한 위치 (Host PC, WSL System)에 설치를 진행한다. (설치 위치는 아래 상세 설명을 참고)

kubectl, kubeadm, kubelet

해당 도구는 API 서버와 상호작용하여 클러스터의 상태를 조회하거나, 리소스를 관리하는데 사용하는 도구이므로, 설치 위치는 Host PC, WSL System 어디든 상관없다.

하지만, WSL 상태에서 Docker를 설치하였고, Windows (Host PC)와 WSL System에서 동일한 실행 환경을 구성하려면, WSL System에 설치한 후, Host PC에서는 wsl 명령을 통해 접근하는 것이 효율적인 것이다.

Linux 상의 설치

참고 내용 : https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/

자세한 설치는 위 내용을 참고하고, Ubuntu 기준으로 설치는 아래와 같이 진행하면 된다.

  1. 저장소 등록에 필요한 패키지 설치

    # sudo apt-get update

    # sudo apt-get install -y apt-transport-https ca-certificates curl gnupg

     

  2. 저장소 접근에 필요한 공개 서명 키 등록

    # curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg –dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

    # sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg # allow unprivileged APT programs to read this keyring

     

  3. 저장소 추가

    # echo ‘deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /’ | sudo tee /etc/apt/sources.list.d/kubernetes.list

    # sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list # helps tools such as command-not-found to work correctly

     

  4. 업데이트 후, 패키지 설치

    # sudo apt-get update

    # sudo apt-get install -y kubelet kubeadm kubectl

    # sudo apt-mark hold kubelet kubeadm kubectl

     

  5. (선택사항) kubelet 서비스 활성화

    # sudo systemctl enable –now kubelet

CGroup 사용 설정

기본 Containerd 설정은 SystemdCgroup 설정이 비활성화 되어 있다. 아래 명령을 통해 해당 옵션을 활성화 시켜 준다.

Containerd 의 SystemdCgroup을 활성화 하는 이유는 주로 리소스 관리와 시스템 안정성을 향상시키기 위함에 있다.

CGroup은 Linux의 중요한 기능으로, 컨테이너가 사용하는 시스템 자원을 효율적으로 관리하고, 여러 프로세스나 서비스 간의 자원의 격리 기능을 제공한다.

CGroup을 활성화 하면, QoS를 통해 Pod나 컨테이너 리소스 품질을 관리하는 Kubernetes 시스템이 리소스 요청을 보다 정확히 관리하고, 우선순위에 따라 컨테이너가 필요한 리소스를 효과적으로 할당할 수 있도록 할 수 있다.

  1. 기본 설정 파일을 추출하여 기본 설정 파일에 덮어씀 (설정 파일에는 기본값을 쓰는 설정들은 제외되어 있으므로, 모두 추출)

    # containerd config default | sudo tee /etc/containerd/config.toml

     

  2. SystemdCgroup 설정을 활성화 하여 적용

    # sudo sed -I ‘s/SystemdCgroup = false/SystemdCgroup = true/’ /etc/containerd/config.toml

     

  3. 컨테이너 재 시작

    # systemctl restart containerd

 

Kubernetes 노드 구성

Master 노드 구성

아래 명령을 이용하여, 마스터 노드를 구성한다. network 구성도 같이 설정한다.

# sudo kubeadm init –pod-network-cidr=10.244.0.0/16

아래와 같은 명령과 같이 종료되면서, 노드가 정상적으로 생성된다.

 

Your Kubernetes control-plane has initialized successfully! 

 
 

To start using your cluster, you need to run the following as a regular user: 

 
 

  mkdir -p $HOME/.kube 

  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config 

  sudo chown $(id -u):$(id -g) $HOME/.kube/config 

 
 

Alternatively, if you are the root user, you can run: 

 
 

  export KUBECONFIG=/etc/kubernetes/admin.conf 

 
 

You should now deploy a pod network to the cluster. 

Run “kubectl apply -f [podnetwork].yaml” with one of the options listed at: 

  https://kubernetes.io/docs/concepts/cluster-administration/addons/ 

 
 

Then you can join any number of worker nodes by running the following on each as root: 

 
 

kubeadm join 172.31.202.166:6443 –token f2j7v0.3gbrthjw4w1035is \ 

        –discovery-token-ca-cert-hash sha256:8c278d276ee5dca56e97b76a79fede37a05004d23c0ea7c8cefc88e301fd8748 

 

위 마지막 구문이 Worker노드들이 Worker 노드들을 추가하는 명령이다.

또한 설정이 완료된 후, 설명 문구에 따라 환경 설정파일을 추가해 줘야 한다.

 

# mkdir -p $HOME/.kube
# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# sudo chown $(id -u):$(id -g) $HOME/.kube/config

 

마스터 노드는, Kubernetes의 중앙 제어 지점으로 클러스터와 외부 간의 통신을 담당하며 모든 Kuberentes리소스에 대한 요청을 처리한다. 또한 클러스터의 상태를 관리하고, 사용자나 시스템이 보낸 요청을 처리한다. 마스터 노드에서는 애플리케이션이 직접 실행되지 않고, 위와 같은 상태와 관리 기능만을 수행한다.

워커 노드는, 실제 애플리케이션 컨테이너가 실행되는 노드이다. 이 노드는 Pod를 포함하는 컨테이너를 실행하고, 클러스터 내에서 애플리케이션이 실행될 수 있도록 하는 역할을 수행한다.

두가지 노드들은 부하 분산 및 가용성을 위하여 각각 최소 3대의 실행을 권장한다.

 

Node 설정 Reset 방법

설정이 잘못되었으면, Reset하고 다시 시도해보자.

# kubeadm reset
# rm $HOME/.kube/config
# rm –rf /etc/cni/net.d

Master Node 추가 방법

노드 생성 시 출력된 토큰을 이용하여 Master Node를 추가할 수 있다.

# kubeadm join <your-control-plane-ip>:6443 –token <token> –discovery-token-ca-cert-hash sha256:<hash> –control-plane

kubelet, kube-proxy 상태 확인

Node에 추가되면, 자동으로 kubelet과 kube-proxy 서비스가 시작된다. 해당 서비스 상태를 보고, Node 추가를 확인할 수 있다.

# systemctl status kubelet
# systemctl status kube-proxy

마지막으로 노드 상태를 확인하여 최종적으로 등록된 노드를 확인한다.

# kubectl get pods –n kube-system

 

Worker Node 추가 방법

Init 시 출력되는 마지막 명령어를 활용한다. Master Node추가 때와는 달리 –control-plane 인자를 제외하고 실행하면 된다.

# kubeadm join <your-control-plane-ip>:6443 –token <token> –discovery-token-ca-cert-hash sha256:<hash>

 

인증키를 통한 Node 보안

TLS인증서를 활용하여, Node에 접근을 인가할 수 있다.

TLS인증서를 생성한 후, init 시에, –upload-certs 인자를 통해 해당 키를 제공하고, join시 –certificate-key 인자를 통해 제공하여, 인가되지 않은 노드의 접근을 차단할 수 있다.

 

Master Node에서 Worker Node의 기능 설정

부하 분산 및 가용성을 위해 3대씩 역할을 분리하여 노드 구성하는 것이 좋지만, 개발 환경이거나 싱글 서버 상태로 테스트를 위해 구성을 할 때는, 굳이 역할을 분리하지 않고, 마스터 노드에서 워커 노드의 역할을 수행하도록 설정하는 것이 효과적이다.

아래 명령을 통해 마스터 노드에 워커 노드의 역할을 추가할 수 있다. (taint 제거)

# kubectl taint nodes –all node-role.kubernetes.io/master- (kubernetes 1.21 미만 버전)

or

# kubectl taint nodes –all node-role.kubernetes.io/control-plane- (kubernetes 1.21 이후 버전)

설정된 노드 상태를 다음 명령어로 확인한다.

# kubectl describe node <node-name>

 

원복

아래 명령으로 더 이상 Worker노드의 기능을 수행하지 않도록 원복 할 수도 있다.

# kubectl taint nodes –all node-role.kubernetes.io/congrol-plain=:NoSchedule

 

Join 명령 재 출력

만약 join 명령을 분실하여 토큰 값과 키 값을 알 수 없다면, 아래 명령을 통해 재 출력할 수 있다.

# kubeadm token create –print-join-command

 

CNI (Container Network Interface) 설정

CNI는 Kubernetes와 같은 컨테이너 오케스트레이션 시스템에서 컨테이너 네트워크 연결을 관리하는 표준 인터페이스다. CNI는 여러 컨테이너 간의 네트워크 통신을 가능하게 하고, 네트워크 플러그인 시스템을 통해 네트워크 설정을 처리한다.

CNI의 기본 개념

컨테이너 네트워크 연결

CNI는 각 컨테이너에 대한 네트워크 연결을 설정하고, 네트워크 인터페이스를 생성한다. 이를 통해 컨테이너가 외부 네트워크와 통신할 수 있게 해준다.

플러그인 기반 아키텍처

CNI는 플러그인 시스템을 기반으로 설계되어 있다. 따라서 네트워크 플러그인 (Calico, Flannel, Weave등)을 선택하여 다양한 네트워크 모델과 기능을 구현할 수 있다.

각 플러그인은 CNI 사양을 따르며, 네트워크 연결을 설정하는데 필요한 작업을 처리한다.

CNI플러그인

여러 CNI 플러그인이 있으며, 이들은 네트워크 모델에 따라 다르다. 예를 들어, Calico는 네트워크 보안 및 라우팅 기능을 제공하는 플러그인 이고, Flannel은 간단한 네트워크 관리 및 IP 주소 할당을 담당한다.

CNI플러그인은 Kubernetes 클러스터의 노트에 설치되며, 클러스터 내의 Pod와 컨테이너 간의 네트워크를 설정한다.

CNI 구성 파일

CNI 플러그인은 JSON형식의 구성 파일을 통해 설정된다. 이 파일은 플러그인이 어떻게 네트워크를 구성할지, 어떤 네트워크 모델을 사용할지 등을 정의한다.

CNI 플러그인 예시

Calico

Calico는 고급 네트워크 정책을 지원하며, IP 라우팅 및 네트워크 보안을 제공한다. 보안 및 네트워크 정책을 Kubernetes 네이티브 네트워크 정책과 통합할 수 있다.

Flannel

Flannel은 간단한 오버레이 네트워크 플러그인으로, 각 Pod에 IP 주소를 할당하고, Pod 간에 통신할 수 있도록 한다.

Weave

Weave는 클러스터 간에 네트워크를 연결하는 오버레이 네트워크 솔루션으로, 분산 환경에서 클러스터 간의 네트워크 연결을 쉽게 설정할 수 있다.

Cilium

Cilium은 eBPF(Extended Berkeley Packet Filter)를 기반으로 하여 고급 네트워크 보안 및 정책 기능을 제공한다. Kubernetes와 통합되어 네트워크 성능과 보안을 강화한다.

CNI의 작동 방식

Pod 생성 시

Kubernetes는 새로운 Pod이 생성되면 해당 Pod에 네트워크를 할당하기 위해 CNI를 호출한다.

CNI 플러그인 실행

CNI 플러그인은 네트워크 인터페이스를 생성하고, Pod에 IP 주소를 할당한다. 이때 플러그인은 Kubernetes의 CNI 구성 파일을 사용하여 어떤 네트워크를 사용할지 결정한다.

네트워크 설정

CNI 플러그인은 설정된 네트워크에 맞춰 Pod 간 통신을 가능하게 하거나, 네트워크 정책을 적용한다.

Pod 종료 시

Pod가 종료되면 CNI 플러그인은 해당 Pod의 네트워크 인터페이스를 삭제하고, 관련 리소스를 정리한다.

설정

해당 Kubernetes환경에서는 Calico를 사용하여 설정을 진행한다. 플러그인은 아래 명령을 통해 설치가 가능하다. CNI 플러그인은 Master Node에 설치하여 자동으로 Worker Node에 전파되게 하거나, 개별 Worker Node에 설치할 수 있다.

이렇게 CNI 설정까지 마치면 Worker노드는 “Ready” 상태가 되고, Pod가 Worker노드에서 생성되어 실행될 수 있는 상태가 된다.

Calico 설치

# kubectl apply –f https://docs.projectcalico.org/manifests/calico.yaml

아래 명령을 통해 설치 상태 확인한다. clico-node-*, calico-kube-*, clico-cni-* 관련 pod 들이 정상적으로 수행되고 있는지 확인한다.

# kubectl get pods –n cube-system

Containerd 를 통해 실행상태를 확인해 볼 수도 있다.

# crictl ps | grep calico/node

Calico 삭제

# kubectl delete –f https://docs.projectcalico.org/manifests/calico.yaml

flannel 설치 (권장 – 설정이 간편)

혹은 flannel을 설치해도 무방하다.

# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

설치된 이후, 아래 설정 파일을 수정하여 적절한 네트워크 구성을 설정하면 된다.

/etc/cni/net.d/10-flannel.conf

설정이 적용되는 데는 잠시 시간이 필요 하다… 만약 정상적으로 동작하지 않는다면 시스템을 재 시작 해보자.

 

클러스터와 CNI플러그인의 통합

CNI 플러그인이 Kubernetes에서 정상 동작 하도록 하려면, kubelet이 CNI를 인식하고 클러스터 내에서 네트워크를 설정할 수 있도록 해야 한다.

kubelet설정

kubelet이 CNI 플러그인을 인식하도록 –network-plugin-cni 옵션을 활성화 해야한다. 보통 해당 설정은 Kubernetes 설정에서 자동으로 처리하게 되지만, 직접 설정을 수정하려면, kubelet을 실행할 때, 해당 옵션을 추가해야 한다.

kubelet –network-plugin-cni

네트워크 정책 설정

Calico나 Cilium을 사용하게 되면, Kubenetes 네트워크 정책을 지원하게 된다. 이때는 적절하게 네트워크 정책을 수정해 주면 된다.

CNI플러그인 적용 확인

Pod상태를 통한 확인: 아래 명령을 이용하여 네트워크 정보를 확인할 수 있다.

# kubectl get pods -o wide

CNI플러그인 로그 확인

아래 명령을 통해서, CNI플러그인의 로그를 직접 확인할 수도 있다.

# kubectl logs -n kube-system <flannel-pod-name>

 

외부 접근 설정

이제 생각해 볼 것은, 내부에 생성된 Deployment나 Pod를 외부로 어떻게 연결시킬 것인가 이다.

Cluster IP (외부 접속 불가)

Cluster IP 타입은 기본적으로 외부 접근이 불가능 하고, 클러스터 내부에서만 접근이 가능한 상태이다.

NodePort (특정 포트로 외부 접속 가능) – 권장

첫번째로 생각해 볼 수 있는 것은 NodePort 서비스를 생성하여 외부로 노출 시키는 방법이다. NodePort 서비스는, 외부에서 특정 포트를 통해 Pod에 접근할 수 있도록 하는 가장 간단한 방법이다.

다음과 같은 명령어로 간단하게 서비스를 생성할 수 있고, 이렇게 설정된 포트를 통해 외부에서, 해당 Pod 나 deployment에 직접 접속 할 수 있게 된다.

# kubectl expose deployment [대상 Deployment 이름] –type=NodePort –name=[생성될 서비스 이름] –port=[노출시킬 포트] –target-port=[Deployment의 대상 포트]

이렇게 노출된 Deployment에 Pod들(1개 이상의 Pod 복제 본이 존재할 경우)은 서비스의 동작 방식에 따라 적절하게 Load Balancing 되어 서비스된다.

LoadBalancing (외부 접근 가능)

클라우드 환경에서 외부 로드 밸런서를 자동으로 프로비저닝 하고, 이를 통해 외부에서 클러스터 내 서비스로 트래픽을 배분하는 방식이다. 별도로, Load Balancing 장비를 구축하여야 한다.

서비스를 위한 운영 환경이 아니라면, 굳이 LB를 사용해서 외부 아이피를 직접 받는 것 보다는, NodePort를 사용하여 외부에 맵핑된 Port로 서비스를 제공하는 것이 좀 더 효율적일 것이다.

LB 설정

Kubernetes시스템에는 기본적으로 LoadBalancing을 수행하는 기능이 없다. 따라서, 해당 기능을 수행하는 모듈을 설치해 주어야 한다.

여기서는 MetalLB를 사용하여 LB를 구축하려고 한다. MetalLB를 설치하기 위해서는 Helm명령을 설치해야 한다.

Helm 명령 설치

  1. Keyring추가

    # curl https://baltocdn.com/helm/signing.asc | gpg –dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null

     

  2. 저장소 추가

    # echo “deb [arch=$(dpkg –print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main” | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list

     

  3. Helm 명령 설치

    # sudo apt update
    # sudo apt install helm

     

LoadBalancing을 위한 MetalLB 설치

  1. Helm 저장소를 추가한다.

    # helm repo add metallb https://metallb.github.io/metallb

     

  2. 설정 파일을 생성한 후,

    # vi Config.yaml

    apiVersion: v1

    kind: ConfigMap

    metadata:

    namespace: metallb-system

    name: metallb-config

    data:

    config: |

    address-pools:

    – name: default

    protocol: layer2

    addresses:

    – 10.245.0.1-10.245.255.255

     

  3. 해당 설정 파일을 이용하여 설치

    # heml install metallb metallb/metallb -n metallb-system –create-namespace -f config.yaml

     

Kubernetes 관리 도구 (OS GUI – Lens)

Kubernetes 관리 도구를 이용하면 Kubernetes 의 상세 정보와 관리를 좀 더 편리하게 운영할 수 있다.

무료 오픈 소스 기반의 Lens는 Window나 Mac, Linux 등에 설치되어, 여러 클러스터를 동시에 관리하고, 실시간 로그 스트리밍, 리소스 모니터링, 대시보드 등을 제공하는 기능을 갖추고 있다. 다만, 멀티 클러스터 관리, 팀 협업 기능 등의 일부 고급 기능을 사용하려면 프리미엄 기능을 활용해야 하는 단점도 존재한다.

다운로드 및 설치

아래 링크로 이동하여 OS에 맞는 설치 파일을 다운로드 하여 설치를 진행한다.

https://docs.k8slens.dev/getting-started/install-lens/

 

설정 파일 추출

kubectl 실행 환경에서, 해당 노드로 접속할 수 있는 정보를 가진 파일을 아래 명령을 통해 생성한다.

# kubectl config view –minify –raw

출력되는 내용을 config파일로 저장 한다.

 

추출된 파일 Import

Lens 어플리케이션으로 돌아와, 설정 파일을 이용하여 Kubernetes Cluster 접속 정보를 생성한다.

 

Metric 정보 활성화

Lens에서는 Metric정보에 쉽게 접근하기 위한 도구를 제공해 준다.

추가된 Kubernetes Cluster에서 오른쪽 버튼으로 Settings 창으로 이동한 후, “Lens Metrics” 로 이동하여, Metrics 관련 스택들을 모두 활성화 한 후, Apply를 눌러준다. 위 과정을 통해 Lens는 관리 대성의 클러스터에 Metric 정보를 수집할 수 있는 Pod들을 자동으로 설치하고 데이터를 수집하여 보여준다.

 

Kebernetes 관리 도구 (CLI – k9s)

커맨드 라인에서 구현된 GUI를 통해 관리 기능을 제공하는 k9s는 WSL에 해당 파일을 설치한 후, 실행하면 자동으로 설정된 클러스터를 감지하여 관리 기능을 제공해 준다.

brew를 통해 설치를 권장하지만, 아래 저장소에서 배포되는 패키지 파일을 통해 설치도 가능하다. (brew로 설치하려면, brew 명령 설치하는 것이 k9s설치하는 것보다 어려움)

# dpkg -i [package_file.deb]

 

CGroup V2 설정

CGroup 은 Linux 커널 기능중의 하나로, 시스템의 프로세스나 태스크 그룹을 리소스 별로 제한하고 관리할 수 있는 기능이다. 이는 Container 기반의 시스템을 구축하는데 있어 핵심적인 기능이다.

Cgroup 은 V1과 V2로 두가지 버전이 존재하며 버전 별로 기능이 조금 다르다.

CGroup V1

  • CGroup V1은 여러 종류의 리소스(예: CPU, 메모리, I/O 등)를 각 리소스 별로 별도의 계층으로 관리한다.
  • 각 리소스 종류가 독립적인 서브시스템(controller)으로 존재하며, 리소스 관리를 위한 인터페이스가 다르다.
  • 분리된 controller를 사용하여 리소스를 관리하기 때문에 설정이 복잡할 수 있다.

CGroup V2

  1. CGroup V2는 V1에서 개선된 버전으로, 단일 계층 모델을 제공한다. 즉, 모든 리소스를 하나의 계층에서 관리하게 된다.
  2. 모든 리소스가 하나의 control group 안에 들어가며, 이를 통해 리소스 관리와 설정이 더 직관적이고 통합적이다.
  3. 통합된 인터페이스를 제공하여 설정 및 관리를 더 간단하게 만들어 준다.
  4. 또한, 메모리 관리와 cpu 사용 제한 등에서 더 정교한 기능을 제공한다.
  5. CGroup V2는 커널 4.5 이상부터 사용 가능하며, 리소스 관리에서 보다 높은 효율성과 안정성을 제공한다.

WSL 상에서의 확인 방법

Docker를 사용중이라면, Docker 명령을 통해 CGroup 버전을 확인할 수 있다.

# docker info | grep Cgroup

Kubernetes Cluster System 은 Docker의 Cgroup 버전을 그대로 상속 받아 사용하게 된다.

또한 최근에는 Kubernetes 시스템에서도 CGroup V1을 사용하고 있으면, 업데이트 하라는 경고 메시지를 출력하고 있으므로, 이를 통해 버전을 확인하는 것도 방법일 것이다.

 

CGroup V2 사용 설정

WSL – Config 설정

Config 설정 파일을 수정하여 CGroupV2를 활성화 할 수 있다. /etc/wsl.conf

[boot]
system=true

WSL 재시작

C:\> wsl –shutdown
C:\> wsl

설정 확인

# mount | grep cgroup2

cgroup2 내용이 확인되면 정상 설정된 것이다.

 

Docker 설정

/etc/docker/daemon.json을 열고, 다음과 같이 설정함

{
“exec-opts”: [“native.cgroupdriver=systemd”]
}

 

이후 서비스 재 시작

# sudo service docker restart

 

Kubectl 설정

/etc/system/system/kubelet.service.d/10-kubeadm.conf 파일을 열고, 다음 구문을 추가하거나 수정함

Environment=”KUBELET_CGROUP_DRIVER=systemd”

 

Kubernetes 재 시작

# sudo systemctl daemon-reload
# sudo systemctl restart kubelet

 

테스트

테스트 파드 생성

# kubectl run nginx-pod-01 –image=nginx –restart=Never
# kubectl run nginx-pod-02 –image=nginx –restart=Never

 

파드 상태 확인

# kubectl get pods

 

파드의 상세 상태 확인

# kubectl describe pod nginx-pod-01 / 02

 

테스트

내부 통신 테스트

파드에 접근

# kubectl exec -it nginx-pod-01 — /bin/bash

Ping을 통한 접근 확인 (Pod 내부)

# ping <nginx-pod-01 ip>

Pod 주소 확인 (외부)

# apt install iputils-ping
# kubectl get pod -o wide

 

파드 로그 확인

# kubectl logs nginx-pod-01 / 02

 

확인 후 삭제

# kubectl delete pod nginx-pod-01
# kubectl delete pod nginx-pod-02

Spring – 프로젝트 분할

Spring 프로젝트를 분할하여 라이브러리 한 후, 불러오는 방법, 그리고, 외부 라이브러리 저장소 (Nexus)등으로 올린 후, 불러와서 사용하는 법에 대하여 다루어 본다.

Maven 저장소 타입

Java 프로젝트는 Meven저장소를 이용하여 자바 프로젝트의 의존성과 아티팩트 배포를 관리한다. 저장소는 크게 다음과 같은 저장소로 나눌 수 있다.

  1. 로컬 저장소 (Local Repository)
  • 개발자가 자신의 시스템에 보유한 Maven 저장소
  • 기본적으로 Maven은 ~/.m2/repository 또는 C:\Users\<username>\.m2\repository 경로에 로컬 저장소를 생성함
  • 프로젝트 빌드를 실행할 때, Maven은 먼저 로컬 저장소에서 의존성 라이브러리를 찾음
  1. 중앙 저장소 (Central Repository)
  • Maven의 기본 저장소로 Apache에서 관리하는 중앙 서버
  • 전 세계 개발자들이 만든 수많은 오픈 소스 라이브러리, 플러그인, 아티팩트 들이 저장되어 있음
  • Maven의 기본 설정에 포함되어 있으며, https://repo.maven.apache.orb/maven2/ 주소로도 접근 가능함
  1. 원격 저장소 (Remote Repository)
  • Apache에서 관리하는 중앙 저장소 외에 다른 서버에서 호스팅 되는 Maven 저장소로 기업 내부에서 사용하는 개인 저장소나, 다른 공개 저장소들이 이에 해당함
  • Nexus나 Artifactory 같은 시스템을 이용해 자체적으로 Maven 저장소를 운영
  • 프로젝트에서 필요한 의존성이 로컬 저장소에 없을 경우, 해당 서버에서 의존성을 다운로드 함

 

Maven 저장소의 기본 역할

Maven 저장소의 기본 역할은 다음과 같다.

  1. 의존성 관리 : 프로젝트에서 필요한 라이브러리, 플러그인 등의 아티팩트를 쉽게 관리할 수 있음, 라이브러리는 groupId, artifactId, version으로 고유하게 식별됨
  2. 아티팩트 배포 : Maven 저장소에 라이브러리를 업로드 하여 다른 프로젝트에서도 사용할 수 있도록 함
  3. 버전 관리 : 러이브러리의 여러 버전이 한 저장소에 같이 존재할 수 있으며, Maven은 필요에 맞는 버전을 자동으로 다운로드 하여 의존성을 주입할 수 있음

 

빌드 자동화와 의존성 관리 도구

Maven

Maven은 빌드를 설정할 때 pom.xml파일을 사용하며, 이 파일은 선언적 방식으로 의존성, 플러그인, 빌드 설정 등을 명시한다.

Maven은 다음과 같은 특징을 가지고 있다.

  1. Maven은 XML 기반의 선언적 빌드 도구이다.
  2. pom.xml 파일에 프로젝트의 의존성, 플러그인, 빌드 설정 등을 정의한다.
  3. 프로젝트 모델을 엄격하게 정의하며, 표준화된 디렉터리 구조와 생명 주기를 따른다.
  4. 의존성 관리는 중앙 저장소(Maven Central)에서 제공하며, XML 설정으로 의존성을 선언한다.
  5. 의존성을 groupId, artifactId, version의 형식으로 정의한다.
  6. 단 방향 빌드를 사용하기 때문에, 빌드 과정이 선형적으로 진행되며, 이로 인해 빌드 속도가 상대적으로 느릴 수 있다.

코드 예시

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.10</version>
</dependency>
</dependencies>

 

Gradle

Gradle은 build.gradle 또는 build.gradle.kts 파일을 사용하며, Groovy 또는 Kotlin DSL을 사용하여 빌드 과정을 명령형으로 구성할 수 있다. 이는 더 유연하고 프로그램적으로 제어할 수 있는 장점을 가진다.

Gradle은 다음과 같은 특징을 가지고 있다.

  1. Gradle은 **DSL(Domain-Specific Language)**을 사용하는 빌드 도구이다. 기본적으로 Groovy 또는 Kotlin DSL을 사용해 build.gradle 또는 build.gradle.kts 파일을 작성한다.
  2. 동적이고 유연한 빌드 시스템으로, XML 기반의 선언적 방식과 달리 스크립트 기반의 명령형 빌드 설정을 사용한다.
  3. Gradle은 Maven보다 더 유연하고 확장성이 뛰어나며, 성능도 우수하다는 평가를 받는다. Gradle은 병렬 처리나 캐싱을 통해 빌드 속도를 최적화할 수 있다.
  4. Gradle은 Maven과 동일한 방식으로 의존성을 관리하지만, 더 유연한 방식으로 추가적인 저장소나 의존성 전략을 설정할 수 있다.
  5. 병렬 빌드와 캐시를 활용하여 빌드 성능을 크게 개선할 수 있다. Incremental Build와 Build Caching을 지원하여 동일한 작업을 반복 할 때 속도 개선 효과를 얻을 수 있다.

 

코드 예시

dependencies {
implementation ‘org.springframework:spring-core:5.3.10’
}

 

도구 비교

항목

Maven

Gradle

설정파일

pom.xml (XML)

build.gradle (Groovy/Kotlin DSL)

의존성 관리

중앙 저장소 (Maven Central) 사용

중앙 저장소와 다양한 커스텀 저장소 지원

성능

상대적으로 느림

명렬 빌드, 캐싱, 성능 최적화

유연성

표준화된 방식, 덜 유연함

높은 유연성, 커스터마이징 가능

빌드 모델

선언적 방식 (표준화된 생명주기)

명령형 방식 (스크립트 기반)

플러그인 시스템

표준화된 플러그인 사용

강력하고 유연한 플러그인 시스템

커스터마이징

제한적

자유롭게 커스터마이즈 가능

주요 사용 사례

표준화된 대규모 프로젝트

성능 최적화가 필요한 프로젝트, Android 개발 등

 

프로젝트 분할

Gradle을 이용하여 프로젝트 빌드 설정을 진행하며, 기존에 개발된 하나의 프로젝트를 기반으로 소스를 분리하고, 프로젝트를 분리하여 의존성을 설정하는 방법으로 프로젝트를 분리하려 한다.

분할 목표 프로젝트 구조

분할된 프로젝트는 루트 프로젝트서브 프로젝트로 나누어 볼 수 있다.

  1. 루트 프로젝트는 전체 프로젝트의 루트 디렉토리에 존재하는 프로젝트를 의미하며, 모든 서브 프로젝트의 설정을 포함한다.
  2. 서브 프로젝트는 루트 프로젝트 아래에 위치한 개별적인 모듈을 말하며, 별도의 build.gradle파일을 가지며, 여기에서 서브 프로젝트의 빌드 설정 및 의존성을 관리한다.

기본 구조는 다음과 같다.

my-multi-project/

├── settings.gradle // 프로젝트 설정
├── build.gradle // 루트 프로젝트의 빌드 설정

├── module1/ // 서브 프로젝트 1
│ └── build.gradle

├── module2/ // 서브 프로젝트 2
│ └── build.gradle

└── module3/ // 서브 프로젝트 3
└── build.gradle

 

 

 

settings.gradle 파일 설정

Settings.gradle파일은 Gradle이 멀티 프로젝트를 인식하고, 각 서브 프로젝트를 포함하도록 지정하는 파일이다. 이 파일에서 모든 서브 프로젝트를 정의한다.

코드 예시

// 루트 프로젝트의 이름
rootProject.name = ‘my-multi-project’

// 서브 프로젝트 지정
include ‘module1’, ‘module2’, ‘module3’

 

루트 프로젝트의 build.gradle 설정

루트 프로젝트의 build.gradle에서는 공통 설정이나 모든 서브 프로젝트에 적용될 설정을 정의할 수 있다. 예를 들어, 모든 서브 프로젝트에 공통 의존성을 추가하거나 플러그인을 적용할 수 있다.

코드 예시

// 루트 프로젝트를 포함한 모든 프로젝트에 적용될 설정
allprojects {
repositories {
mavenCentral() // 모든 서브 프로젝트에서 Maven 중앙 저장소를 사용
}

dependencies {
// 모든 프로젝트에서 공통으로 사용할 의존성 설정 (예: logging 라이브러리)
implementation ‘org.slf4j:slf4j-api:1.7.32’
}
}

subprojects {
// 서브 프로젝트들에 대해서만 적용되는 설정
apply plugin: ‘java’

// Java 플러그인 적용 후 공통 설정
sourceCompatibility = ‘1.8’
targetCompatibility = ‘1.8’

// 서브 프로젝트에서만 사용하는 추가 의존성
dependencies {
implementation ‘org.springframework:spring-context:5.3.10’
}
}

 

서브 프로젝트의 build.gradle 설정

각 서브 프로젝트는 독립적인 build.gradle 파일을 가지며, 해당 파일에서는 해당 모듈에서만 필요한 빌드 설정을 추가한다.

코드 예시

apply plugin: ‘java’

dependencies {
// 이 모듈에만 필요한 의존성 추가
implementation project(‘:module2’) // 다른 서브 프로젝트를 의존성으로 추가
}

 

IntelliJ에서의 프로젝트 설정

IntelliJ에서는,

  1. 루트 프로젝트를 “New Project”로 생성
  2. 적절한 위치에, “New -> Module” 메뉴를 통해 서브 프로젝트 생성
  3. 서브 프로젝트 정리 시에, “gradle” 폴더와, “build.gradle” 파일은 제거하지 말 것

빌드 방법

위와 같은 방식으로 분할된 프로젝트를 빌드할 때는 다음과 같은 명령으로 다양한 형태로 빌드를 수행할 수 있다.

  1. 루트 프로젝트에서 전체 빌드할 때,

    ./gradlew build

  2. 특정 서브 프로젝트만 빌드할 때,

    ./gradlew :bodule1:build

     

라이브러리 배포 설정

이렇게 분할된 서브 프로젝트는, 위에서 설명한 Maven 중앙 저장소나, 로컬 저장소, 혹은 사내 커스텀 저장소에 올려 배포할 수 있다.

해당 설정은 서브 프로젝트의 성격에 따라 루트 프로젝트나 서브 프로젝트에 설정을 추가할 수 있다.

로컬 저장소에 배포

코드 예시

apply plugin: ‘java’
apply plugin: ‘maven-publish’

publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
url = uri(“file://${buildDir}/repo”) // 로컬 저장소로 배포
}
}
}

 

사설 저장소에 배포

다음과 같은 형태로, https://repo.repository.com/repository/maven-releases 주소를 가지는 사설 저장소에, user1 / password 계정 정보로 배포하는 설정은 다은과 같이 설정할 수 있다.

코드 예시

gradle.build

apply plugin: ‘java’
apply plugin: ‘maven-publish’

repositories {
mavenCentral()
}

dependencies {
implementation ‘org.springframework:spring-context:5.3.10’
}

// 배포 설정
publishing {
publications {
mavenJava(MavenPublication) {
from components.java // Java 컴포넌트(라이브러리)를 배포
}
}

repositories {
maven {
name = “MyPrivateRepo” // 저장소 이름 (임의 설정 가능)
url = uri(“https://repo.repository.com/repository/maven-releases/”) // 사설 저장소 URL

credentials {
username = project.findProperty(“repoUser”) ?: “user01” // 사용자명 (프로젝트 속성 또는 기본값)
password = project.findProperty(“repoPassword”) ?: “password” // 비밀번호 (프로젝트 속성 또는 기본값)
}
}
}
}

 

repoUser, repoPassword 는 다음과 같이 외부로 추출하여 보안을 강화할 수도 있다.

gradle.properties

repoUser=user1
repoPassword=password

 

버전 설정 방법

아래와 같이 버전을 Project Property로 받아올 수 있게 변경한 후, 빌드 명령 뒤에 –Pversion 형태로 인자를 전달하면 된다.

 

[build.gradle]

version = project.hasProperty(‘version’) ? project.version : ‘0.0.1’

 

C:\> .\gradlew.bat build -Pversion=”[VERSION]”
C:\> .\gradlew.bat publish -Pversion=”[VERSION]”

 

배포 방법

이렇게 분할되고, 배포될 저장소 설정 까지 마무리 되었으면, 아래 명령을 통해 배포를 수행할 수 있다.

./gradlew publish

 

버전 및 배포 파일에 대한 설명 추가 방법

배포 시, 해당 프로젝트의 정보를 추가하려면, 프로젝트에 다음과 같이 설정하면 된다.

publishing {
publication {
mavenJava(MavenPublication) {

// 1. Group ID 설정
groupId = ‘com.company.lib’

// 2. Artifact ID 설정
artifactId = ‘module1’

// 3. Version 설정
version = ‘0.0.1’

// 4. Artifact 파일 정의
artifact(“$buildDir/build/libs/module.jar”)

// 5. pom 설정
pom {
// 5-1. 프로젝트 이름 설명
name.set(“Module 1”)

// 5-2. 프로젝트 상세 설명
description.set(“Common Module”)

// 5-3. URL 설명
url.set(https://common.campany.com/module1)

// 5-4. Licnese설명
licenses {
license {
name.set(“The Apache License, Version 2.0”)
url.set(http://www.apache.org/licenses/LICENSE-2.0)
}
}

// 5-5. 개발자 정보
developers {
developer {
id.set(“johnkim”)
name.set(“John Kim”)
email.set(john@company.com)
}
}

// 5-6. 저장소 정보
scm {
developerConnection.set(“scm:git:ssh://git@gitlab.company.com/common/module1.git”)
url.set(https://gitlab.company.com/common/module1.git)
}

// 5-7. POM 정보 생성
pom.withXml pomfileManualGenerator

}
}
}
}

Apache – Kafka를 통한 이벤트 브로커 시스템 구축 및 활용

Apache Kafka란?

분산 스트리밍 플랫폼으로, 주로 대규모의 실시간 데이터 스트림을 처리하는 데 사용됩니다. Kafka는 메시지 큐 시스템, 로그 처리 시스템, 그리고 실시간 데이터 파이프라인 등 다양한 용도로 사용될 수 있음

LinkedIn에서 개발되었고, 이후, Apache Software Foundation에 의해 관리되고 있음

Apache Kafka의 주요 특징

분산 시스템: Kafka는 분산형 아키텍처를 기반으로 하며, 수많은 프로듀서(producer), 소비자(consumer), 그리고 브로커(broker)들이 협력하여 데이터를 처리함. 이 분산 구조 덕분에 높은 확장성과 내결함성(fault tolerance)을 제공함

  1. 고속 데이터 처리: Kafka는 매우 빠른 데이터 전송 속도를 자랑하며, 초당 수백만 개의 메시지를 처리할 수 있음. 이는 대규모의 실시간 데이터 처리 시스템에서 중요한 성능 요소임.
  2. 내결함성(Fault Tolerance): Kafka는 데이터의 복제(replication) 기능을 통해 내결함성을 제공함. 각 데이터는 복제본을 여러 노드에 저장할 수 있어, 일부 노드가 장애를 일으켜도 데이터 손실 없이 지속적으로 서비스할 수 있음.
  3. 내구성(Persistence): Kafka는 메시지를 디스크에 저장하여 내구성을 제공. 메시지는 기본적으로 로그 파일 형태로 저장되며, 디스크에 기록된 메시지는 일정 기간 동안 보존됨.
  4. 고가용성(High Availability): Kafka 클러스터는 여러 브로커 노드로 구성될 수 있으며, 각 메시지는 여러 복제본으로 저장되어 장애가 발생해도 서비스가 중단되지 않도록 설계되었음
  5. 실시간 데이터 스트리밍: Kafka는 데이터 스트림을 실시간으로 처리하고 전달할 수 있기 때문에 실시간 데이터 분석, 이벤트 소싱, 모니터링 시스템 등에서 널리 사용됨.

 

Kafka의 구성 요소

  1. Producer (생산자): Kafka에 데이터를 전송하는 애플리케이션이나 시스템을 말합니다. Producer는 데이터를 Kafka의 특정 토픽(Topic)에 전송.
  2. Consumer (소비자): Kafka에서 데이터를 가져오는 애플리케이션을 말함. Consumer는 Kafka의 토픽을 구독하여 데이터를 소비하고 처리함.
  3. Broker (브로커): Kafka 클러스터 내에서 데이터를 저장하고 처리하는 서버를 말함. 하나의 Kafka 클러스터는 여러 개의 브로커로 구성되며, 각 브로커는 데이터를 토픽 단위로 저장함
  4. Topic (토픽): Kafka에서 메시지를 구분하는 카테고리나 채널. Producer는 메시지를 특정 토픽에 보내며, Consumer는 특정 토픽을 구독하여 메시지를 받음.
  5. Partition (파티션): 각 토픽은 여러 개의 파티션으로 나누어질 수 있음. 파티션은 데이터의 병렬 처리를 가능하게 하여 Kafka의 확장성을 제공함. 각 파티션은 별도의 로그 파일로 저장되며, 파티션을 나누는 것은 데이터의 병렬 처리와 로드 밸런싱을 위해 필요함.
  6. Zookeeper: Kafka는 Zookeeper를 사용하여 클러스터의 메타데이터 관리와 브로커 간의 협동 작업을 관리함. Zookeeper는 클러스터의 브로커 상태와 리더 선출 등을 담당함. 그러나 최근에는 Kafka의 자체 메타데이터 관리로 Zookeeper 의존성을 줄여나가는 방향으로 발전하고 있음.

 

Kafka의 주요 용도

  1. 실시간 데이터 파이프라인: Kafka는 실시간 데이터 스트리밍 시스템을 구축하는 데 매우 유용함. 예를 들어, 웹 애플리케이션의 로그 데이터를 실시간으로 수집하고 분석하는 데 사용될 수 있음.
  2. 이벤트 소싱(Event Sourcing): Kafka는 이벤트 기반 아키텍처에서 이벤트를 처리하는 데 매우 적합함. 이벤트 소싱은 애플리케이션 상태를 이벤트 로그로 저장하고 이를 통해 시스템 상태를 추적하는 방식힘.
  3. 로그 수집 및 처리: Kafka는 대규모의 로그 데이터를 실시간으로 수집하고 처리하는 데 효과적임. 여러 서버에서 생성된 로그 데이터를 Kafka에 전달하고, 이를 실시간으로 처리하여 모니터링, 경고 시스템 등을 구축할 수 있음.
  4. 실시간 분석 및 대시보드: Kafka를 사용하여 실시간 데이터 분석 시스템을 구축할 수 있음. 예를 들어, 웹사이트 트래픽, 사용자 행동 데이터, IoT 센서 데이터를 실시간으로 스트리밍하여 분석할 수 있음.
  5. Microservices 통합: Kafka는 마이크로서비스 아키텍처에서 서비스 간의 통합을 위한 메시징 시스템으로 사용됨. Kafka를 통해 각 서비스가 이벤트 기반으로 통신하고 데이터를 교환할 수 있음.
  6. 데이터 스트리밍: Kafka는 실시간으로 데이터 스트리밍을 처리하는 데 적합함. 예를 들어, 금융 거래 시스템이나 사용자 인터랙션 데이터를 실시간으로 처리하고 다른 시스템에 전달할 수 있음.

 

Kafka의 장점

  1. 높은 성능: Kafka는 초당 수백만 건의 메시지를 처리할 수 있을 정도로 빠른 성능을 제공함.
  2. 확장성: Kafka는 클러스터로 확장할 수 있어, 사용량이 증가하면 클러스터의 크기를 확장하여 성능을 유지할 수 있음.
  3. 내구성 및 신뢰성: Kafka는 데이터를 디스크에 기록하고 복제하여 내구성과 신뢰성을 보장함.
  4. 실시간 처리: 실시간 스트리밍 처리에 적합하여 빠르게 처리해야 하는 데이터에 유용함.
  5. 유연한 소비자 모델: 여러 소비자가 동시에 하나의 토픽을 구독할 수 있어, 다양한 애플리케이션과 서비스를 통합할 수 있음.

Kafka의 단점

  1. 운영 복잡성: Kafka 클러스터를 설정하고 관리하는 것이 다소 복잡할 수 있음. 특히 Zookeeper와의 통합과 클러스터 크기가 커질수록 관리가 어려워질 수 있음.
  2. 메시지 지연: 높은 부하 상황에서는 메시지 전송 지연이 발생할 수 있음.
  3. 자원 소모: Kafka는 메모리와 디스크 공간을 많이 사용하기 때문에, 클러스터 운영 시 자원 관리를 신경 써야 함.

Kafka 서버 구축

Compose 파일을 통한 구동

Docker를 통한 서비스 구축

아래 Docker Compose 파일을 이용하여 다양한 인프라 환경에 맞춰 서버를 간단하게 설치할 수 있다.

https://github.com/conduktor/kafka-stack-docker-compose

# git clone https://github.com/conduktor/kafka-stack-docker-compose.git

위 방법을 통해, Compose 파일을 다운로드 하여, 자신에 맞는 환경의 Kafka를 설치하면 된다.

아래와 같이 zk-single-kafka-single.yml 파일을 통해 단일 인스턴스 형태의 kafka를 설치한다.

# docker compose –f zk-single-kafka-single.yml up –d

위와 같이 설정하게 되면, 기본적인 설정과 같이 구동해야 할 서비스들도 같이 구동된다.

 

실행중인 Process 확인

실행중인 서비스에 대한 프로세스 확인

# docker compose –f zk-single-kafka-single.yml ps

 

Process 시작/중단

서비스를 일시 중단 하거나, 중단된 서비스를 다시 시작

# docker compose –f zk-single-kafka-single.yml start/stop

 

Process 삭제

# docker-compose –f zk-single-kafka-single.yml down

 

Docker 이미지를 통한 직접 구축

그렇지 않고, 직접 Docker Image를 내려 받아 직접 Docker 컨테이너를 구축할 수 도 있다.

다만, 이렇게 Docker 이미지를 이용하여 설치할 경우, 기본적으로 필요한 Docker 명령 도구들은 같이 설치되지 않으므로, 별도로 설치하거나, 외부에서 접속하는 방법을 따라야 한다.

기본 이미지를 통한 구동

# docker run -p 9092:9092 -d –restart=unless-stopped –name kafka apache/kafka:latest

 

GrallVM으로 빌드된 Native 이미지 구동

# docker run -p 9092:9092 -d –restart=unless-stopped –name kafka apache/kafka-native:latest

 

Command Line Tools

Kafka 명령 테스트를 위해서는 Command Line Tool 을 이용하여 수행할 수 있다.

만약 Docker Image를 통해 직접 설치했을 경우는 이미지 내부에 Command Line 명령들이 존재하지 않기 때문에, 별도로 설치해 줘야 한다.

외부에서 명령을 설치한 후, 접속 정보를 통해 접속할 수 있다.

이벤트 저장할 Topic 생성

Topic 생성

# kafka-topics.sh –create –topic [Topic 이름] –bootstrap-server [서버주소]

생성된 Topic 확인

# kafka-topic.sh –describe –topic [Topic 이름] –bootstrap-server [서버주소]

생성된 전체 Topic 리스트 확인

# kafka-topic.sh –bootstrap-server [서버주소] –list

생성된 Topic 정보 확인

# kafka-topics.sh –describe –topic [Topic 이름] –bootstrap-server [서버주소]

이벤트 송수신

이벤트 송신 (Producer)

# kafka-console-producer.sh –topic [Topic 이름] –bootstrap-server [서버주소]

이벤트 수신 (Consumer)

# kafka-console-consumer.sh –topic [Topic 이름] –bootstra-server [서버주소]

Producer의 동작 방식

Procedure는 앞서 설명한 바와 같이 이벤트를 생성하는 명령이다.

Producer는 Consumer가 이벤트를 가져갈 수 있도록 Partition에 데이터를 입력하게 된다. 이때, 파티션을 분배하는 방식은 몇가지 방식이 있다.

키 기반 파티셔닝 (Key-based Partitioning)

메시지에 키가 포함된 경우, 해당 키를 Hash한 후 그 결과값을 이용하여 파티션을 선정함. 이 방식은 동일한 키의 메시지가 항상 같은 파티션에 전송되는 것을 보장하여, 이는 파티션 내 데이터의 일관성을 유지하는데 유리함

메시지 1 (key=user123) => Hash => Partition 0
메시지 2 (key=user123) => Hash => Partition 0
메시지 3 (key=user456) => Hash => Partition 1

Round-robin 기반 파티셔닝

메시지에 키가 없을 경우, Round-robin방식으로 메시지를 순차적으로 파티션에 할당하며, 데이터의 일관성을 깨지지만, 부하 분산에 유리함

메시지 1 => Partition 0
메시지 2 => Partition 1
메시지 2 => Partition 2

Consumer의 동작 방식

Consumer는 Procedure에서 생성한 이벤트를 수신하는 명령이다.

Consumer는 Consumer그룹으로 묶일 수 있다. (특별히 묶이지 않은 Consumer는 단일 Consumer를 가진 Group으로 보면 된다.) 이렇게 묶인 Consumer Group은 대상 Topic의 Partition을 읽어오도록 Consumer에서 요청한다.

순차 처리 방식인 Kafka의 특징은 이 Partition에 의존된다. Partition이 1개라면, 1개의 순차 처리 방식의 명령을 수행하며, N개 라면 N개의 병렬성을 가진 명령 파이프라인이 생성된다고 보면 된다.

Partition과 Consumer의 개수가 동일할 경우

Partition의 개수와 Consumer의 개수가 동일할 경우, Consumer들은 할당된 하나의 Partition만을 처리하게 된다.

Partition 01 => Consumer 01
Partition 02 => Consumer 02
Partition 03 => Consumer 03

Partition이 Consumer 개수보다 많을 경우 (Partition > Consumer)

하나의 Consumer는 1개 이상의 Partition을 할당 받아 처리하게 된다.

Partition 01, 02 => Consumer 01
Partition 03, 04 => Consumer 02
Partition 05 => Consumer 03

Partition 이 Consumer 개수보다 적을 경우, (Partition < Consumer)

Partition을 할당 받지 못한 Consumer은 역할을 수행하지 못하고 대기하게 된다.

Partition 01 => Consumer 01
Partition 02 => Consumer 02
Partition 03 => Consumer 03
대기 => Consumer 04
대기 => Consumer 05

파티션을 나눠서 Topic 생성 방법

파티션을 나눠서 토픽을 생성하는 방법은 다음과 같다.

# kafka-topics.sh –create –bootstrap-server [서버 아이피] –topic [Topic 이름] –partitions [파티션 개수]

복제본 설정

Kafka는 복제본에 대한 설정은 Topic기준으로 설정을 수행해야 한다. 만약 설정하지 않는다면, 기본 복제본은 1개만을 생성하여 브로커 장애 발생 시 데이터 손실이 발생할 수 있다.

복제본은 다음과 같이 설정하여 지정할 수 있으며, 지정된 개수 만큼 적절한 브로커에 자동으로 배치가 된다.

# kafka-topics.sh –create –bootstrap-server [서버주소] –topic [Topic 이름] –partition [파티션 개수] –replication-factor [생성할 복제본 수]

이벤트 수신 방법에 대한 정의

이벤트는 수신 방법 정의에 따라 다른 형태로 수신을 받을 수 있다. 이를테면, 메시지 수신을 Consumer가 수동으로 Commit을 할 때까지 삭제를 지연 시키면, Consumer가 종료되어 수신할 수 없는 상태가 되더라도, 다시 정상화 되어 데이터를 수신 받을 때까지 삭제를 지연시킬 수 있다.

다만 이때도, 서버에 설정된 메시지 보관 시간 까지만 보관되며, 이 시간이 지날 때까지 수신하지 못하면, 삭제되므로, 주의해야 한다.

수신 Timeout 설정

수신 Timeout을 Kafka의 retention.ms을 통해 설정할 수 있다. 이는 토픽을 생성할 때, 인자 값으로 설정할 수 있다.

kafka-topics.sh –create –topic [TOPIC 이름] –bootstrap-server [서버 주소] –config retention.ms= 86400000

Spring boot 상의 구현

의존성 추가

Spring boot에서는 spring.kafka 프로젝트를 통해 kafka를 지원하고 있다. 아래와 같이 build.gradle파일에 의존성을 추가한다.

dependency {
implementation ‘org.springframework.kafka:spring-kafka’
}

사용할 서버와 Topic 설정

YAML 파일을 통한 설정

 

spring:
kafka:
bootstrap-servers: “localhost:9092”
consumer:
group-id: “myGroup”
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

 

KafkaTemplate을 통한 이벤트 송수신

Kafka 빈 생성

프로젝트를 통해 자동으로 생성된 KafkaTemplate을 아래와 같은 코드를 통해 주입 받아 사용 상태를 설정할 수 있다.

@Component
public class KafkaBean {
private final KafkaTemplate<String, String> kafkaTemplate;
public KafkaBean(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}

……
}

송신 메서드 생성

생성된 Bean에서, KafkaTemplate을 통해 송신 설정을 수행한다.

@Component
public class KafkaBean {
public void sendMessage(String message) {
this.kafkaTemplate.send(TOPIC, message);
}

}

수신 메서드 생성

생성된 Bean에서, KafkaTemplate을 통해 수신 설정을 수행한다.

@Component
public class KafkaBean {
@KafkaListener(topics = “someTopic”)
public void processMessage(String content) {
// ……
}

}

특이 조건

기본 동작

기본적으로 Group으로 그룹화 되지 않은 Consumer들은, 자신이 구독하고 있는 TOPIC에 대해서는 모두 한번씩 데이터를 수신하게 된다. 이는 Group 단위로 Offset을 관리하고 있기 때문이다.

 

서버 중 한대만 받도록 설정

Consumer의 Group을 통해 설정할 수 있다. 같은 Group 이름으로 설정된 Consumer들은 해당 그룹에서 대표자 1대에서는 수신이 가능한 상태가 된다.

Kafka-console-consumer.sh

# kafka-console-consumer.sh –topic [TOPIC 이름] –bootstrap-server [서버 주소] –group [그룹 네임]

Spring boot

@KafkaListener(topics = “[Topic]”, groupId = “[Group ID]”)

 

수신 중에, 또 다른 Group 생성 시

Group은 개별 단위로 Offset을 관리하고 있다고 설명했다. 그렇기에 현재까지 발생한 모든 이벤트는, 해당 그룹에서는 새로운 이벤트로 간주된다.

다만, auto-offset-reset 설정에 의해, 새로 수신 받을 메시지의 상태를 지정할 수 있다.

  1. earliest 로 설정했다면, 0번부터 모든 데이터를 재 수신하기를 시도할 것이다.
  2. latest로 설정한다면, 현재 마지막 offset으로 초기화 되며, 기존에 수신된 모든 이벤트 들은 무시되게 된다.

Consumer Group을 설정할 때는 위와 같은 규칙을 이해하고, 적절한 옵션을 설정해야 할 필요가 있다.

 

분배 방식

이벤트 분배 방식은 Partition과 Consumer의 개수에 밀접한 관계를 가지고 있다.

Producer는 기본적으로 Partition 들에게 보내는 이벤트의 Key와 Partition Number을 기초로 설정되어 있는 Partitioner 동작에 맞춰 분배하게 된다.

  1. key, Partition Number 모두 입력되지 않았을 경우.
    설정되어 있는 Partitioner의 기본 동작에 맞춰 분배된다. 기본적으로 Round robin 방식으로 분배가 된다. (아무런 설정이 되어 있지 않을 경우, 여기서 설명한 DefaultPartitioner가 설정되지 않는 것 같다. 이 문제는 뒤에서 설명한다.)
  2. Key가 설정되어 있을 경우
    추가 설정이 되어 있지 않을 경우, Key를 해시 하여 생성된 키 값에 해당하는 파티션에 분배하게 된다. 이는 특정 키가 한 파티션으로 보내어 데이터 일관성을 유지하기 위함이다.
  3. Partition Number가 설정되어 있을 경우
    지정된 파티션 넘버에 요청을 기록한다. 이때, 파티션 넘버가 유효하지 않을 경우, 이벤트 기록 자체가 실패해 버리기 때문에 주의가 필요하다.
  4. Key, Partition Number가 모두 설정되어 있을 경우
    이럴 경우, 우선순위에 따라 Partition Number가 먼저 적용된다.

 

기본 Default Partitioner

지금까지 설명한 바와 같이 아무런 설정이 없고, Key, Partition Number 없이 이벤트를 보낼 경우, Default Partitioner의 동작에 의해 Round robin방식으로 모든 Partition에 순차적으로 메시지를 보내야 한다. 하지만, 직접 해보면 그렇게 동작하지 않는 것을 확인할 수 있다. (버전에 따라 정상 동작 할 수도…)

이는, 설정 상에 기본 Partitioner가 DefaultPartitioner로 설정되지 않는 문제인 것 같다. (확인해봐야 할 문제)

아래 설정으로 DefaultPartitioner가 지정되도록 수정한다.

application.yml

spring.kafka.producer.properties.partitioner.class: org.apache.kafka.clients.producer.internals.DefaultParitioner

Docker 환경 구축 방법 – Windows

Windows 환경에서의 Docker 설치

Docker Desktop은 기업 사용자의 경우, 2022년 02월 01일부터 전면 유료화 되어, 해당 프로그램을 사용하려면, 유료 계정을 생성하여 사용하여야 한다.

다만, Docker engine, CLI, Compose 등은 아직 무료로 사용할 수 있기에, 해당 프로그램들을 사용하면, 유료 환경에 대한 문제를 해결할 수 있다.

Docker에서 제공하는 프로그램들에 대한 설명이다.

 

Docker Engine

Docker Engine은 Docker의 핵심 컴포넌트로, 실제로 컨테이너를 실행하는 역할을 합니다.

Docker는 클라이언트-서버 아키텍처를 기반으로 하며, Docker Engine은 서버 역할을 하는 부분입니다.

Linux 기반에서 컨테이너화된 애플리케이션을 실행하고, Windows와 Mac에서는 Docker Desktop을 통해 제공됩니다.

주요 기능은 다음과 같습니다.

  1. 컨테이너 생성 및 관리: Docker Engine은 Docker 이미지에서 컨테이너를 생성하고 실행할 수 있게 해줍니다.
  2. 이미지 관리: Docker Hub와 같은 레지스트리에서 Docker 이미지를 다운로드하고 업로드합니다.
  3. 컨테이너 실행 및 스케일링: 컨테이너의 시작, 중지, 삭제, 리소스 관리 등과 같은 기본적인 관리 작업을 수행합니다.
  4. 네트워크 및 스토리지: 컨테이너들 간의 네트워크 설정 및 데이터 볼륨을 관리합니다.

 

Docker CLI (Command Line Interface)

Docker CLI는 명령어 인터페이스로, 사용자가 Docker Engine과 상호작용할 수 있도록 해줍니다.

Docker 명령어를 사용하여 이미지를 빌드하거나, 컨테이너를 실행하거나, 다양한 Docker 관리 작업을 수행합니다.

  1. 컨테이너 및 이미지 관리: docker run, docker build, docker pull, docker ps 등의 명령어를 통해 컨테이너와 이미지를 실행하고 관리할 수 있습니다.
  2. 로그 및 상태 모니터링: docker logs를 통해 컨테이너의 로그를 확인하고, docker stats로 컨테이너의 리소스 사용 상태를 모니터링할 수 있습니다.
  3. 네트워크 및 볼륨 관리: docker network, docker volume 명령어를 통해 Docker의 네트워크와 볼륨을 관리할 수 있습니다.

Docker Compose

Docker Compose는 여러 개의 Docker 컨테이너를 정의하고 실행할 수 있도록 돕는 툴입니다.

주로 멀티 컨테이너 애플리케이션을 관리하는 데 사용되며, YAML 파일 형식으로 컨테이너 설정을 구성합니다.

  1. 멀티 컨테이너 애플리케이션 정의: docker-compose.yml 파일을 사용하여 여러 컨테이너의 설정, 네트워크, 볼륨 등을 정의할 수 있습니다.
  2. 컨테이너 간 의존성 관리: 여러 개의 서비스가 상호작용하는 복잡한 애플리케이션에서 서비스 간의 의존성을 관리할 수 있습니다.
  3. 애플리케이션 시작/중지: docker-compose up으로 정의된 모든 컨테이너를 한 번에 시작하고, docker-compose down으로 종료할 수 있습니다.
  4. 편리한 개발 환경 설정: 복잡한 개발 환경을 손쉽게 설정하고, 여러 환경에서 동일한 애플리케이션을 실행할 수 있도록 도와줍니다.

Docker Desktop

Docker Desktop은 Windows와 macOS에서 Docker Engine을 실행할 수 있게 해주는 애플리케이션입니다.

Windows나 macOS에서는 Docker가 기본적으로 Linux에서 동작하기 때문에, Docker Desktop이 필요한 가상화 기술을 제공합니다.

  1. 그래픽 UI: Docker Desktop은 GUI를 제공하여 명령어 기반의 Docker CLI를 보조합니다.
  2. Windows, macOS에서 Docker 사용: 리눅스 환경을 가상화하여, Windows나 macOS에서도 Docker 컨테이너를 실행할 수 있도록 지원합니다.
  3. Docker Compose 및 Kubernetes 통합: Docker Desktop에는 Docker Compose와 간단히 통합할 수 있으며, Kubernetes 클러스터를 로컬에서 실행하여 애플리케이션을 테스트할 수 있습니다.

 

Docker Registry (예: Docker Hub)

Docker Registry는 Docker 이미지의 저장소로, Docker Hub는 가장 많이 사용되는 공개 이미지 레지스트리입니다.

  1. 이미지 저장 및 배포: Docker 이미지를 레지스트리에 업로드하고 다른 사용자와 공유할 수 있습니다.
  2. 공식 및 커스텀 이미지: 공식 Docker 이미지뿐만 아니라, 사용자 정의 이미지를 저장하고 관리할 수 있습니다.

 

Docker Engine 설치 방법

WSL 설치

Docker Engine은 Linux 기반으로 동작한다. 따라서, Windows 환경에서, Docker Engine을 설치하려면, Linux 환경을 구축해 주어야 한다.

Linux 환경을 구축하는 방법은 다음과 같다.

  1. WSL (Windows Subsystem for Linux)를 활용한 방법
  2. VM Machine을 활용한 방밥

WSL을 이용한 방법

WSL을 사용하려면, Windows 10버전 2004 이상 또는 Windows 11을 사용해야 한다.

다음 명령을 통해 WSL 을 설치할 수 있다.

C:\> wsl –install

다만 이렇게 설치를 진행하게 되면, 명령 실행 시점 기준, 기본값으로 설정된 Ubuntu 가 설치되며, 다른 버전을 사용하려면, 아래와 같이 사용 가능한 버전을 확인하고

C:\> wsl –list –online

사용 가능한 버전 중에 원하는 버전을 선택하여 설치할 수 도 있다.

C:\> wsl –install -d Ubuntu-24.04

 

WSL 실행

WSL 은 설치한 후, Command Line 에서 직접 wsl 명령을 사용하여 접근하거나, Windows 11의 경우, “Windows 터미널”에서, 탭 확장을 이용하여 원하는 쉘을 실행하여 접근할 수 있다.

Windows 10에서는 Windows Store를 통해, “Windows 터미널”을 설치하여 사용할 수 있다.

 

WSL 자동 실행 설정

시작 프로그램을 통한 실행

WSL은 기본적으로 Windows 가 시작될 때 같이 시작되지 않는다. 또한, WSL이 유휴 상태일 때, Windows 는 WSL 시스템을 종료 시켜 시스템 자원을 확보하려 한다. 따라서, 아래와 같은 방법으로, 윈도우 시작과 동시에 WSL이 활성화 되고, 지속적으로 활성화된 상태를 유지 시켜 줘야 한다.

  1. WSL 시작을 위한 PowerShell Script 작성
    적당한 위치에 “WSL Startup.ps1” 파일을 생성하고, 아래와 같은 내용으로 스크립트를 작성한다.
    Start-Process “C:\Windows\System32\wsl.exe” -WindowStyle Hidden
  2. Startup 위치에 해당 스크립트를 실행하는 바로가기를 생성한다.
    WinKey + R & shell:startup
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File “D:\DevTools\WSL Startup.ps1”

주의 사항은 이렇게 하더라도, wsl –shutdown 등의 명령으로, WSL시스템을 강제로 종료해 버리면, 백그라운드에서 돌아가는 해당 스크립트도 같이 종료 되기 때문에 시스템 상태 유지가 동작하지 않을 수 있다.

 

WSL Settings

WSL을 설치하면 WSL의 설정을 GUI환경에서 관리할 수 있는 도구가 같이 설치된다. 다만, 상황에 따라 해당 프로그램이 설치되지 않을 수도 있다. WSL Settings 를 찾아보고, 설치되어 있지 않다면, 아래 링크를 참고하여 설치하면 된다.

https://github.com/microsoft/WSL/releases

 

*.docker.internal 관련 설정

Docker Desktop에서는, *.docker.internal 주소를 자동으로 생성해서 제공해 준다. 하지만, docker-engine을 통해 설치할 경우는, 해당 기능을 제공해 주지 않으며, 이는 간단하게 Host PC의 hosts 파일에 아래 내용을 추가하여 기능을 제공해 줄 수 있다.

# Added by Docker Desktop
192.168.0.61 host.docker.internal
192.168.0.61 gateway.docker.internal
# To allow the same kube context to work on the host and the container:
127.0.0.1 kubernetes.docker.internal
# End of section

 

Container의 Host IP를 통한 Container 접속

2개의 독립적인 Container가 존재하고, 다른 컨테이너로 접속하기 위하여 host IP를 통해 접근하게 되면, 정상적으로 접근이 되지 않는 경우가 발생할 수 있다.

이는 다중 NAT 설정의 포트 포워딩 설정으로 인해 발생하는 문제로, 뒤쪽에서 설명하는 포트 포워딩 등의 동작 방식이, 외부에서 접근하는 것과, 내부에서 접근하는 것에서 발생하는 차이로 인한 문제이다.

현재로써는, 해당 문제가 포트 포워딩으로 인해 발생하는 것 까지만 확인된 상태이며, 구체적으로 어떻게 해결해야 하는 것 까지는 확인하지 못했다. 또한, 해당 문제는 단순히, 내부 아이피 나, 컨테이너 HostName으로 호출하면 되는 문제이기에, 추후 다시 한번 확인할 필요가 있을 때 확인해 보기로 한다.

 

주요 설정

WSL Settings를 통해 Docker를 위한 설정을 몇가지 해주어야 한다.

  1. 메모리 및 프로세스
    1. 스왑 크기: 스왑 메모리가 사용되면, 유휴 상태의 Container가 스왑 영역으로 스왑 아웃될 수 있다. 이런 경우, 컨테이너가 느려지거나 비정상 적으로 동작할 수 있다. 간혹 스왑 인/아웃이 일어나면서, CPU가 100% 점유하는 현상도 발생할 수 있으니, 해당 스왑 크기는 0으로 설정하여 스왑 기능을 비활성화 한다.

 

Docker Engine 설치

기존 설치 버전 삭제

Docker Engine은 containerd에 의존한다. 따라서, 기존에 설치되어 있는 containerd.io나, 다른 종속성이 있는지 확인하여, Docker Engine과의 충돌을 피하기 위하여 아래 명령으로 삭제를 진행한다.

# for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

Docker 저장소를 apt에 등록

설치를 진행하기 위해서 Docker 저장소를 apt에 먼저 등록해야 한다.

  1. 일단 저장소 패키지 정보들을 업데이트 하고,
    # sudo apt-get update
  2. 설치에 필요한 패키지들을 설치한다.
    # sudo apt-get install ca-certificates curl
  3. 폴더를 생성하고, 권한을 부여함
    # sudo install -m 0755 -d /etc/apt/keyrings
  4. 키 정보를 받아와 등록한다.
    # sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
  5. 읽기 권한 부여
    # sudo chmod a+r /etc/apt/keyrings/docker.asc
  6. 저장소 정보를 가져와 등록
    # echo “deb [arch=$(dpkg –print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo “$VERSION_CODENAME”) stable” | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  7. 등록된 저장소에서 정보를 가져올 수 있는지 확인하기 위하여 패키지 정보 업데이트 실행
    # sudo apt-get update

Docker Engine 설치

최신버전을 설치하려면 다음과 같이 진행한다.

# sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Docker Engine 설치 확인

Docker Engine이 정상적으로 설치되었는지 확인하기 위하여, 다음과 같이 명령을 실행하여 확인한다.

# sudo docker run hello-world

다음과 비슷한 문장이 보이면, 정상적으로 설치가 완료된 것이다.

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the “hello-world” image from the Docker Hub. (amd64)
3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

Docker Engine 제거

만약에, 설치된 Docker Engine을 제거하려면, 다음 명령으로 제거할 수 있다.

# sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

호스트의 이미지, 컨테이너, 볼륨 또는 사용자 지정 구성 파일은 자동으로 제거되지 않기에, 다음 명령을 통해 제거를 진행해야 한다.

# sudo rm -rf /var/lib/docker

# sudo rm -rf /var/lib/containerd

 

Host PC 의 드라이브 마운트

wsl상에 Docker를 설치하게 되면, 독립적인 공간을 생성하여 관리되기에, 설정파일은 외부의 Host PC의 저장공간에 관리해야 할 필요성이 있다. 따라서, Host PC의 저장 공간에 접근하는 방법을 알아 두거나, 적당한 위치에 Link를 생성해 두면, Docker Container를 관리하는데 좀 더 편리할 수 있다.

WSL은 기본적으로 /mnt의 하위에 자동으로 드라이브 명으로 마운트 된다.

C: à /mnt/c

D: à /mnt/d

마운트 디렉토리를 이용하여 적당한 위치에 Symbolic Link를 생성한다.

예를 들어, D:\Docker 폴더를 만들고, 해당 폴더 하위에 Volume 파일을 관리한다면, 해당 폴더를 WSL시스템에 /var/lib/docker-config 이름으로 Symbolic Link를 만들어 둔다면 좀더 쉽고 직관적으로 이미지 저장 폴더를 관리할 수 있다.

# ln -s /mnt/d/docker /var/lib/docker-volumes

이에 따른 권한 설정 문제는 아래 “Host File System 에 대한 권한 설정 활성화”에서 다룬다.

 

Host File System (/mnt/c)에 대한 권한 설정 활성화 (WSL 2)

기본적으로 WSL은 자동 혹은 수동으로 마운트 된, Host – File System에 대한 권한 설정을 할 수 없다. 이는 Windows File System (NTFS)와 Linux File System (Ext4)의 권한 관리의 차이로 발생하는 문제이며, 마운트 된 HOST 시스템의 파일이나 폴더를 WSL 시스템이나, 이를 Volume으로 가져온 컨테이너 레벨에서 권한이나 사용자를 변경하더라도, 정상적으로 변경이 되지 않는 문제가 발생한다.

이로 인하여, Container의 Volume 관리에 어려운 점이 발생한다. 이를 테면, GitLab과 같이, 파일이나, 경로의 권한을 확인하여, 원하는 상태가 아닐 경우, (Mode 700 등) 시스템을 동작 시키지 않는 시스템의 경우는 아예 실행조차 되지 않는 문제가 발생할 수 있다.

이를 해결하기 위해서는, WSL 설정에 automout 부분에 아래와 같이 옵션을 추가하여 권한을 설정할 수 있게 설정해 줘야 한다. (WSL 내부 파일 임에 주의)

대상 파일: /etc/wsl.conf

[automount]
options=metadata

위와 같이 설정하게 되면, 마운트 된 Host PC의 File System에 대한 권한을 설정할 수 있다.

MetaData 방식은 이런 윈도우와 Linux간의 파일 권한 관리 차이점을 해결하기 위한 기술로, WSL2에서 사용하는 권한 정보를 Windows 파일시스템에 MetaData형태로 기록하고, 이를 WSL에서 읽어오거나 가져와서 사용할 수 있도록 해주는 기능이다.

 

TCP를 통한 원격 접속 설정

daemon.json 설정

Docker를 TCP를 이용하여 외부로 노출 시키려면 먼저 Docker Daemon 설정을 수정해야 한다.

  1. daemon.json 파일 수정
    /etc/docker 디렉토리에 daemon.json 파일을 생성하여, 아래 문장을 기록한다.
    {“hosts”: [“tcp://0.0.0.0:2375”, “unix:///var/run/docker.sock”]}
  2. /lib/system/system/docker.service 파일에서, 실행 구문 중 fd:// 문장을 제거한다.
    ExecStart=/usr/bin/dockerd -H fd:// –containerd=/run/containerd/containerd.sock
    è
    ExecStart=/usr/bin/dockerd –containerd=/run/containerd/containerd.sock
  3. 서비스를 재시작 한다.
    # systemctl daemon-reload
    # systemctl restart docker.service

위와 같은 과정을 통해, 외부에서 접속할 수 있도록 설정할 수 있다. 다만, 이렇게 설정 하더라도, localhost나, 127.0.0.1로 접속은 되지 않도, WSL에 지정된 내부 아이피를 통해 접속을 해야 정상 접속이 가능하다.

Web 기반 Docker 시각화 도구 설치

Docker Desktop 과 유사한 웹 기반 Docker 시각화 도구를 이용하면, 좀 더 편리하게 Docker 환경을 관리할 수 있다.

Portainer는 시각화를 통해 원격 혹은 로컬 Container들의 상태를 확인할 수 있는 UI를 제공한다.

아래 명령을 통해 설치한 후, 9000 번 포트를 이용하여 웹 브라우저를 통해 콘솔에 접근하면 된다.

# docker run -d –name portainer -p 8000:8000 -p 9000:9000 -p 9443:9443 –restart always portainer/portainer-ce:latest

비밀번호는 설치 초기에는 12자리로 고정되어 있지만, 최초 비밀번호 설정 후, 접근하여 비밀번호 길이를 조절할 수 있다.

 

WSL의 외부 공개 설정

WSL 상에 Docker를 설치하게 되면, WSL또한 NAT 상의 네트워크 상에서 동작하기 때문에, 외부 IP나, host PC에서 localhost를 통해 접속을 시도하게 되면, 접속이 되지 않는 현상이 발생한다. 이는 호스트 PC에서 WSL의 포트로 포트 포워딩이 되어 있지 않아, 정상적인 접속이 이루어지지 않아서 발생하는 현상이다.

아래 명령을 통해 host PC의 포트를 WSL 내부로 매핑해주면 외부 IP를 통해서도 정상적인 서비스가 가능하다.

포트 포워딩 설정

관리자 모드로 PowerShell을 띄운 후, 아래 명령을 참고하여 포트 포워딩 설정을 진행한다.

C:\> netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=[대상포트] connectaddress=[WSL내부 아이피] connectport=[내부포트]

 

포트 포워딩 리스트

참고로, 현재 설정된 리스트는 아래 명령으로 확인 가능하다.

C:\> netsh interface portproxy show all

 

포트 포워딩 삭제

C:\> netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=[대상 포트]

 

Host에서 Docker 명령 사용하기

WSL명령을 통한 실행

실행 방법

WSL에 설치된 docker 명령을 실행하여, docker에 접근하려면, 명령 앞에 wsl을 입력하면 가능하다.

C:\> wsl docker container ls

C:\> wsl docker compose up/down

편의 기능 설정

Path가 설정된 디렉토리에 docker.bat를 만들어 명령어를 단축 시키는 것도 좋은 방법이 될 수 있다.

아래와 같은 간단한 명령 구문으로 docker.bat를 만들어 주고, Path 설정을 통해 실행 가능 상태로 만들어 주면 실행 할 때 마다 wsl docker … 와 같은 형태로 실행하지 않고, Docker Desktop 과 동일한 형태로 docker … 명령을 수행하여 Docker를 제어할 수 있다.

@echo off
wsl docker %*

 

Docker CLI를 통한 실행

wsl을 입력하는 것이 싫다면, Docker CLI를 host에 설치한 후, 접속 경로를 설정하여 실행 환경을 생성할 수 있다.

  1. 바이너리 파일 설치
    1. Docker-CLI: WSL에 설치된 버전과 동일한 Windows Docker 바이너리를 다운로드 한다. (현재 기준으로 독립된 CLI파일만 다운로드 하거나 설치파일을 제공하지 않기 때문에, 아래 링크를 통해 Docker 전체 바이너리 파일을 다운로드 한다.)
      https://download.docker.com/win/static/stable/x86_64/
    2. Docker-Compose: Docker-CLI가 설치된 동일한 위치에 아래 링크에서 시스템 플랫폼에 맞는 파일을 다운로드 한다. (파일명을 필히 docker-compose.exe 로 변경한다. 아니면, 명령 수행 때마다 플랫폼 명까지 다 쳐도 무방하다.)
      https://github.com/docker/compose/releases
  2. 해당 파일을 적절한 위치에 압축해제 하고, PATH설정을 통해 실행 가능한 상태로 설정한다.
  3. 위 “TCP를 통한 원격 접속 설정”을 통해, WSL에서 원격 접속 설정을 수행한다.
  4. Windows의 “고급 시스템 설정”을 통해 Docker Host 경로를 설정한다.

    DOCKER_HOST: tcp://localhost:2375

     

위 과정을 통해, 설정이 완료되었다면, 명령이 정상적으로 수행 가능한지 확인한다.

C:\> docker container ls

C:\> docker compose up

문제점

위와 같이 Docker CLI와 Docker-Compose 바이너리 파일을 직접 다운로드 받아 설치하여 실행할 경우, Volume 설정 등에서, 경로 매칭 오류가 날 수 있다. 이는, Docker 명령을 실행한 위치와, Docker Daemon이 실행되는 위치의 차이 때문에 발생하는 것으로, 현재 경로(예를 들면, C:\Projects\example)를 WSL에서는 인식하지 못하는(/mnt/c/Projects/example) 문제로 인해 발생한 것이다.

WSL을 통해 설치된 환경에서는 Host에서는 되도록 “WSL명령을 통한 실행” 방법을 사용하는 것을 추천한다.

Spring Boot – CORS 설정

Spring MVC상의 CORS 설정

Bean 생성을 통한 설정

/**
* CORS
관련
설정
정보
클래스

*/
@Configuration
public class CorsConfig {
/**
* CORS
설정
*
* @return CORS
설정

*/
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(@SuppressWarnings(“null”) @NonNull CorsRegistry registry) {
registry
.addMapping(“/**”)
.allowedMethods(“GET”, “POST”, “PUT”, “PATCH”, “DELETE”)
.allowedOrigins(“*”);
}
};
}
}

 

Spring Webflux 상의 CORS 설정

Interface 상속을 통한 설정

/**
* CORS
관련 설정 정보 클래스

*/
@Configuration
public class CorsConfig implements WebFluxConfigurer {
/**
* CORS
설정
* @param registry CORS
설정

*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.allowedOrigins("*");
}
}

 

Spring boot 상의 추적 로그 관리 방법

Spring 추적 라이브러리

Spring 추적 라이브러리는, 기존에는 개발자가 직접 만들어 사용하거나, Spring Cloud Sleuth를 사용하여 추적 로그를 수집하고, 분석하였다. 하지만, 2022년 11월 Spring Cloud 에서 해당 프로젝트를 분리하여, Micrometer Tracing 프로젝트를 만들었다. 이는 Spring Cloud Sleuth의 독립적인 복사본이며, Spring Cloud의 라이브러리 종속성을 제거하여, 완전히 분리되어 사용될 수 있는 구조를 가진 추적 라이브러리로 만들어 졌다.

설치

다음 Gradle 문장을 통해 Micrometer-Tracing를 사용하도록 설정할 수 있다.

implementation platform(‘io.micrometer:micrometer-tracing-bom:latest.release’)
implementation ‘io.micrometer:micrometer-tracing’
(tracing 부분은, 아래 Tracer를 추가하게 되면 전이적으로 추가됨)

이후, 필요한 추적 브리지에 따라 다음과 같이 추가로 설정을 진행한다.

Micrometer-Tracing에서는 유연성과 확장성, 호환성 등의 이유로 아래와 같은 두가지의 Tracer를 제공하며, 목적과 필요에 따라 선택적으로 사용하면 된다.

Tracer

Tracer는 추적 로그를 수집하는데, 다음과 같은 기능을 제공한다.

  1. 스팬 생성
    1. 스팬(Span): Tracer는 특정 작업이나 요청을 나타내는 스팬을 생성한다. 각 스팬은 시작 시간, 종료 시간, 이름, 메타데이터(태그 등)를 포함하여 요청의 처리 과정을 추적한다.
  2. 스팬 관리
    1. 시작 및 종료: Tracer는 스팬을 시작하고 종료하는 메서드를 제공하여, 특정 작업의 결과 시간을 기록
    2. 중첩 관계 관리: 여러 스팬을 중첩하여 계증 구조를 만들 수 있고, 이를 통해, 복잡한 호출 체인을 시각화 하고 분석할 수 있음
  3. 컨텍스트 전파
    1. Request Context: Tracer는 요청 간의 컨텍스트를 전파할 수 있는 기능을 제공, 이를 통해 분산 시스템에서 여러 서비스 간의 호출을 연결할 수 있음
    2. Http 헤더 전파: Tracer는 HTTP 요청 및 응답에서 스팬 ID와 같은 정보를 포함하여 컨텍스트를 전파할 수 있음
  4. 메타데이터 추가
    1. 태그와 애노테이션: Tracer는 스팬에 추가 정보를 포함할 수 있도록 태그나 애노테이션을 추가할 수 있는 기능을 제공 (에러메시지, 응답 코드 등을 기록할 수 있음)
  5. 데이터 내보내기
    1. 외부 백엔드 Jaeger, Zipkin등으로 내보내는 기능을 제공하여 실시간 모니터링 및 분석을 가능하게 함

Brave Tracer

  1. 간단하고 가벼운 라이브러리: Brave는 Zipkin에 기반한 경량화된 트레이싱 라이브러리로, 간단한 사용 사례에 적합
  2. Zipkin과의 통합: Zipkin과의 연동이 용이하며, 간단한 설정으로 빠르게 사용할 수 있음
  3. 전통적인 사용 사례: 기존의 Zipkin 사용자가 많은 경우, 쉽게 통합할 수 있음

implementation ‘io.micrometer:micrometer-tracing-bridge-brave’

Open-Telemetry Tracer

  1. 표준화된 프레임워크 : OpenTelemetry는 분산 트레이싱, 메트릭, 로그를 위한 표준화된 프레임워크로, 다양한 언어와 플랫폼에서 사용할 수 있는 통합 솔루션임
  2. 확장성 : 여러 백엔드와 쉽게 연동할 수 있으며, 사용자가 원하는 방식으로 데이터 수집 및 전송을 커스터마이즈 할 수 있음
  3. 정밀한 매트릭과 트레이싱: OpenTelemetry는 보다 정교한 트레이싱과 메트릭 수집 기능을 제공하므로, 복잡한 요구 사항이 있는 경우 성능 모니터링이 더 효과적일 수 있음
  4. 활발한 커뮤니티: 오픈 소스 프로젝트로, 큰 커뮤니티와 활발한 개발이 이루어 지고 있음

implementation ‘io.micrometer:micrometer-tracing-bridge-otel’

Reporter

Micrometer는 Wavefront나 Zipkin 등의 외부 시각화 도구를 지원하며, 이런 도구들에 리포팅을 위한 도구를 제공한다.

Wavefront Reporter

implementation ‘io.micrometer:micrometer-tracing-reporter-wavefront’

Brave Zipkin Reporter

implementation ‘io.zipkin.reporter2:zipkin-reporter-brave’

Open Telemetry Zipkin Reporter

implementation ‘io.opentelemetry:opentelemetry-exporter-zipkin’

Zipkin을 통해 span을 전송하기 위한 OpenZipkin URL 발신자 종속성

implementation ‘io.zipkin.reporter2:zipkin-sender-urlconnection’

Spring boot 설정

설정된 Report URL은 다음과 같은 형태로 application.yml(properties)에 추가하여 설정한다.

management.zipkin.tracing.endpoint: [URL]/api/v2/spans

 

시각화를 위한 설정 – Zipkin

사용 라이브러리 및 도구

Zipkin을 사용하여, 추적성 데이터를 시각화 할 수 있으며, 대상 모듈에서는 zipkin 라이브러리 의존성 추가와 zipkin서버 정보를 입력하여 데이터를 수집할 수 있다.

Zipkin 서버 설치

docker를 이용한 설치

docker run -d -p 9411:9411 openzipkin/zipkin

설정 파일을 위한 설정

Zipkin은 추적 정보를 DB로 관리한다. 따라서, 다음과 같은 설정을 추가하여 외부 DB를 사용하도록 설정하여 관리 데이터를 분리할 수 있다.

environment:
    – STORAGE_TYPE=mysql
    – MYSQL_DB=zipkin
    – MYSQL_USER=zipkin
    – MYSQL_PASS=zipkin
    – MYSQL_HOST=zipkin-mysql
    – MYSQL_TCP_PORT=3306

서버 확인

http://localhost:9411/

 

네트워크를 통한 호출 설정

Micrometer는 네트워크를 통한 타 서버 호출 시, 자동으로 TraceId와 SpanId를 입력하여, 추적할 수 있는 정보를 전달한다.

다만, 지원되는 함수는, RestTemplateBuilder, RestClient.Builder, WebClient.Builder를 지원하기 때문에, 해당 Builder를 주입 받아, Client를 생성하는 형태로 구현을 해야만, 정확한 추적 정보를 대상 서버에 전달할 수 있다.

RestClient.Builder

Trace정보를 입력하기 위하여 자동으로 설정된 코드는, Builder를 통해 넘어오기 때문에, 필히 Builder를 주입받아, Client를 생성하도록 구현한다.

public class Test {
private final RestClient client;
public Test(RestClient.Builder builder) {
client = builder.baseUrl(…).build();
}
}

Portainer를 통한 Docker 시각화

Portainer를 통해 얻을 수 있는 장점

  1. 시각화: Portainer는 시각화를 통해 원격, 혹은 로컬 Container들의 상태를 확인할 수 있는 UI를 제공한다. 이를 통해 직관적인 작업을 지원한다.
  2. 관리 편의성: Docker는 설정을 변경하기 위해서는 삭제 후, 재 설치과정을 거쳐야 하며, Docker에서 제공하는 Interface로 처리하기 위해서는, 복잡한 과정을 거쳐야 한다. Portainer를 통해서는, 간단히 설정 정보를 변경하는 인터페이스를 통해 직관적이고, 편리하게 변경 작업을 수행할 수 있다.

설치

Docker를 통한 설치

  1. 사용 이미지: portainer/portainer-ce:latest
  2. 포트
    1. 8000:8000
    2. 9000:9000
    3. 9443:9443

대상 Docker API에 대한 설정

  1. Docker Desktop에서 대상 시스템 설정
    1. API 인터페이스에 대한 접속 설정

      Docker Desktop에서 TCP 접속을 통해, 외부에서 접속 가능하도록 설정함

      Settings > General

      1. Expose daemon on tcp://localhost:2375 without TLS

    2. Docker 상에서의 접속 host 설정

      Docker 내부의 Container에서 접속을 진행하면, host설정을 통해 접속 host설정을 진행하면, 편리하게 접속 설정을 수행할 수 있다.

      다음 단계를 통해 내부에서 접속할 수 있도록 host설정을 진행한다.

      1. Use the WSL 2 based engine
      2. Add the *.docker.internal names to the hosts’ /etc/hosts file (Requires password)

         

Portainer 대상API 설정

Docker 내부에서 host Docker 설정

  1. Name: 시스템 설정 이름
  2. Environment URL: API 접속 정보

    위에서 설정한 정보를 기반으로 접속 정보를 입력 host.docker.internal:2375