Docker file은 Docker에서 Docker image를 생성하기 위한 용도로 작성하는 파일을 의미합니다.
즉, 만들 Docker image에 대한 정보들을 기술한 스크립트(설정 파일)입니다.
이때, Dockerfile이라고 파일명을 고정해야 합니다.(다른 이름으로 수정하면 Docker가 인식 못합니다.)
# Base image로 openjdk 17을 사용
FROM openjdk:17-jdk-slim
# RUN = 컨테이너에서 실행할 명령어, 컨테이너를 준비하기 위한 명령어
# RUN apt update
# RUN apt intall net-tools
COPY ./build/libs/back-0.0.1-SNAPSHOT.jar /app.jar
# ADD = COPY + 추가 기능(압축 해제, URL)
# ADD
# docker run -dit my_image bash 처럼 특정 이미지에서 마지막에 명령어를
# 입력해서 실행할 때 CMD는 입력한 명령어로 덮어쓰기 돼고, ENTRYPOINT 유지
# ENTRYPOINT pwd
CMD ["java","-jar","/app.jar"]
# ENV = 환경 변수 설정, 문서화 역할, 기본값 설정
# EXPOSE = 포트포워딩 설정, 문서화 역할
Docker File의 지시어
FROM
FROM은 Docker file에서 사용할 Base image를 지정하는 지시어입니다.
예를 들어 우리가 vue.js 프로젝트를 빌드하여 Container로 실행시키고자 한다면 nginx 같은 서버 프로그램이 필요합니다.
이런 프로그램들이 설치된 Base image 위에서 프론트 개발자가 작성한 코드를 실행시켜야 동작하기에 이런 지시어가 필요합니다.
COPY
개발자가 개발한 프로그램을 Image 내부의 한 경로에 저장시킵니다.
즉, image가 container로 빌드될 때 우리의 프로젝트 파일이 어디에 있을지 지정하는 지시어입니다.
RUN
docker image가 container로 빌드될 때 자동으로 실행되는 명령어를 입력하는 지시어입니다.
우리 프로그램이 실행되기 위해서 설치해야할 프로그램이 있다면 이 스크립트 명령어를 통해서 설치해야 합니다.
CMD
container가 실행되고 나서 가장 먼저 실행되는 명령어를 입력하는 지시어입니다.
예를 들어 spring 백엔드 서버를 image로 패키징하여 빌드하고 싶을 때, docker image가 빌드될 때 java -jar ./app.jar 같은 명령어가 필요합니다.
이런 명령어를 입력하는 지시어입니다.
(CMD는 여러번 입력하면 맨 마지막에서 입력된 CMD만 실행됩니다.)
ENTRYPOINT
ENTRYPONIT는 CMD를 조합해서 명령어를 작성할 수 있습니다.
(ENTRYPONIT는 여러번 입력하면 맨 마지막에서 입력된 ENTRYPONIT만 실행됩니다.)
LABEL
LABEL은 Docker Image에 메타 데이터들을 추가합니다.
(key-value 형태로 저장되며, 버전같은 것을 입력할 때 사용합니다.)
ENV
ENV는 image에 필요한 환경 변수를 설정하는 지시어입니다.
(key-value 형태로 저장됩니다.)
EXPOSE
Container가 수신할 포트를 지정합니다.
기본은 TCP로 받으며 [포트 번호]/[프로토콜] 형태로 지정이 가능합니다.
ADD
URL을 통해서 필요한 프로그램을 다운받는 지시어입니다.
USER
Container 내부에서 명령을 실행할 사용자를 지정합니다.
기본은 root계정이며, 이후 명령들이 이 지시어로 지정한 사용자에서 실행됩니다.
WORKDIR
프로그램이 실행될 디렉토리를 지정합니다.
기본 home 디렉토리를 지정한다고 생각하면 될 것 같습니다.
VOLUME
컨테이너 내부 디렉토리를 외부 저장소와 연결(mount)합니다.
이때, 프로그램이 시작되면서 실행되어야 할 명령어 스크립트를 이 볼륨을 이용해서 저장하여 실행시킬 수 있습니다.
Docker는 크게 Image, Container, dockerd, docker client으로 구성되어있습니다.
Dockerd
docker daemon
dockerd는 Docker Daemon의 줄인말로서 Docker Server 프로그램을 말합니다.
한 호스트에서 실행되는 모든 컨테이너는 해당 호스트의 dockerd에 의해 생성, 관리되며 docker client는 이 데몬에 요청을 보내 컨테이너의 상태를 확인하거나 제어합니다.
기본적으로 dockerd와 docker client는 docker.sock 이라는 Unix 소켓 파일을 통해 통신합니다.
(만약, Socket 통신이 아니라 다른 설정을 통해 TCP 통신을 진행한다면 docker.sock이라는 파일을 통하지 않습니다.)
Docker Client
docker daemon 프로그램에 접속하는 프로그램을 말합니다.
간단하게 표현하자면 frontend 프로그램이라고 생각하면 됩니다.
docker desktop같은 프로그램을 말하며, docker daemon에 접속하여 사용자가 container의 상태를 확인하고 제어할 수 있습니다.
Docker Client 명령어 모음(CLI)
컨테이너 실행 및 접근
docker run -d -p 8080:80 --name web nginx
docker exec -it web bash
이미지 빌드 및 실행
docker build -t myapp:1.0 . // 현재 경로에 존재하는 Dockerfile을 이용해서 빌드합니다.
// myapp:1.0 => myapp이라는 이름의 이미지 이름과 1.0이라는 태그를 가진 이미지로 빌드합니다.
docker run -d -p 8080:8080 myapp:1.0
Docker란 Linux의 Container 기술를 활용하여 격리 프로그램을 격리하고, 생성 및 관리하는 프로그램을 말합니다.
Docker 공식 홈페이지에서 정의한 Docker의 기능은 다음과 같습니다.
도커 컨테이너는 일종의 소프트웨어를 소프트웨어의 실행에 필요한 모든 것을 포함하는 완전한 파일 시스템 안에 감싼다. 여기에는 코드, 런타임, 시스템 도구, 시스템 라이브러리 등 서버에 설치되는 무엇이든 아우른다. 이는 실행 중인 환경에 관계 없이 언제나 동일하게 실행될 것을 보증한다.
즉, Docker는 Linux의 시스템을 활용하여 구성되기 때문에 Linux 시스템을 가지고 있어야 합니다.
따라서 Window Os는 WSL이라는 프로그램을 활용해서 Linux를 설치해야 하고, Mac OS는 Linux 시스템과 같은 계열의 OS이므로 따로 추가 설치할 필요가 없습니다.
WSL이란? WSL(Window Subsystem for Linux)의 약자로 윈도우 10, 11에서 네이티브로 Linux를 가동하기 위한 프로그램을 말합니다.
Container란?
Linux에서 Container는 코드의 라이브러리 및 종속성과 함께 애플리케잉션 코드를 포함하는 스프트웨어의 실행 가능한 단위로서, 코드를 다양한 에코 시스템에서 실행할 수 있습니다.
컨테이너는 OS 커널의 기능을 사용하여 프로세스를 격리하고 애플리케이션이 엑세스할 수 있는 CPU, 메모리 및 디스크 공간의 양을 제어하는 운영 체제 가상화의 형태를 활용합니다.
WebSocket 서버를 열기 위해서는 WebSocket Config 파일이 존재해야 합니다.
WebSocketConfig
@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final JwtHandShakeInterceptor jwtHandShakeInterceptor;
private final AuthChannelInterceptor authChannelInterceptor;
@Value("${websocket.allowed-origin}")
private String frontServer; // front URI
@Bean(name = "customMessageBrokerScheduler")
public TaskScheduler messageBrokerTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("ws-heartbeat-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.initialize();
return scheduler;
} // WebSocket을 처리할 Thread 설정
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue") // Topic, Queue Prefix 설정
.setTaskScheduler(messageBrokerTaskScheduler())
.setHeartbeatValue(new long[]{10000, 10000});
// 하트비트 연결 및 10초 주기로 연결 확인
registry.setApplicationDestinationPrefixes("/app"); // Client가 데이터를 보낼 Prefix 설정
registry.setUserDestinationPrefix("/user");
// 사용자별 메세지 라우팅
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat") // WebSocket HandShake를 할 URI
.addInterceptors(jwtHandShakeInterceptor) // jwt를 통해서 인가 담당 기능
.setAllowedOriginPatterns(frontServer) // CORS 해제 설정
.withSockJS(); // SockJS를 통한 WebSocket 설정
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(authChannelInterceptor); // WebSocket에서 인증 정보를 처리할 수 있도록 저장하는 기능을 수행하는 객체
}
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(128 * 1024); // 128KB
registration.setSendBufferSizeLimit(512 * 1024); // 512KB
registration.setSendTimeLimit(1000); // 전송시간 제한 1초
}
}
여기서 추가적인 객체가 2개가 존재합니다.
JwtHandShakeInterceptor, AuthChannelInterceptor인데, 각각 하는 역할이 다릅니다.
JwtHandShakeInterceptor : HTTP HandShake가 일어날 때, 인가 검증
AuthChannelInterceptor : STOMP에서 연결을 하기 위한 CONNECT Frame 통신이 올 때, 인가 검증
특히, AuthChannelInterceptor는 인가를 확인한 후에 사용자 데이터를 기능에 적용할 수 있도록 제공해주는 중요한 객체입니다.