군만두의 IT 개발 일지

[스터디13] 09. 웹서비스 배포하기 본문

학습일지

[스터디13] 09. 웹서비스 배포하기

mandus 2026. 3. 8. 03:31

목차

    9장. 웹서비스 배포하기

    9.1 컨테이너화란 무엇일까?

    서로 다른 시스템 간 종속성(Java 버전, 웹 서버, OS 등), 구성 또는 파일의 불일치로 인해 발생하는 문제를 해결하기 위해 애플리케이션을 컨테이너화한다. 컨테이너화를 하면 애플리케이션은 필요한 모든 의존성 및 파일과 함께 번들링되어 모든 환경에서 동일하게 동작한다.

     

    컨테이너는 호스트 운영체제의 라이브러리, 바이너리뿐만 아니라 커널도 공유하므로 아주 가볍다. 관련 개념인 가상화(virtualization)는 하드웨어를 분할하여 가상 머신(virtual machine)을 만드는 프로세스로, 컨테이너와는 다르다.

    • 가상머신: 호스트 시스템 위에서 생성, 무겁고 수 GB에 달함, 이식성 낮음
    • 컨테이너: 하드웨어와 OS 위에서 격리된 프로세스로 실행, 몇 MB ~ 1GB, 이식성 높음

    9.2 도커(Docker) 이미지 빌드하기

    도커란 무엇인가?

    2013년에 런칭한 도커는 시장을 리딩하는 컨테이너 플랫폼이자 오픈 소스 프로젝트다. 도커는 리눅스 커널의 cgroup네임스페이스를 사용해 의존성을 애플리케이션과 함께 패키징할 수 있도록 제공한다. 각 컨테이너는 고유한 사용자 네임스페이스를 가진다.

    • 프로세스 식별자(PID): 프로세스 격리
    • 네트워크(NET): 네트워크 인터페이스 관리
    • 프로세스 간 통신(IPC): IPC 리소스 접근 관리
    • 마운트 포인트(MNT): 파일시스템 마운트 포인트 관리
    • 유닉스 시간 공유(UTS): 커널과 버전 식별자 격리

    도커 아키텍처에 대한 이해

    도커는 클라이언트-서버 아키텍처를 채택한다. 도커 클라이언트(CLI)는 사용자가 명령을 입력하는 인터페이스이며, 도커 데몬은 컨테이너의 빌드, 실행, 배포를 처리한다. 클라이언트와 데몬은 소켓이나 RESTful API를 통해 통신한다.

     

    도커의 기본 구성 요소

    • 도커 이미지: 읽기 전용 템플릿. 도커 컨테이너를 만드는 데 사용됨
    • 도커 컨테이너: 도커 이미지로부터 생성되며 자체 프로세스, 파일시스템, 네트워킹 스택을 가짐
    • 도커 레지스트리: 도커 이미지를 업로드하거나 다운로드할 수 있는 이미지 리포지토리 (예: Docker Hub)

    도커 컨테이너의 생명주기

    1. 컨테이너 생성: docker create
    2. 컨테이너 실행: docker run
    3. 컨테이너 일시 중지(선택사항): docker pause
    4. 컨테이너 일시 중지 해제(선택사항): docker unpause
    5. 컨테이너 시작: docker start
    6. 컨테이너 중지: docker stop
    7. 컨테이너 재시작: docker restart
    8. 컨테이너 강제 종료: docker kill
    9. 컨테이너 제거: docker rm — 중지된 컨테이너를 대상으로 실행해야 함

    9.3 액추에이터(Actuator) 의존성 추가를 통해 이미지 빌드하기

    프로덕션 수준의 기능을 제공하는 스프링 부트 액추에이터 의존성을 추가한다. 이 장에서는 /actuator/health 엔드포인트를 사용해 도커 컨테이너 내부 서비스의 상태를 파악한다.

     

    1. build.gradle에 의존성 추가

    runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator'

    2. Constants.java에 URL 상수 추가

    public static final String ACTUATOR_URL_PREFIX = "/actuator/**";

    3. SecurityConfig.java 보안 설정 업데이트

    // 앞부분 생략
    req.requestMatchers(toH2Console()).permitAll()
        .requestMatchers(new AntPathRequestMatcher(
            ACTUATOR_URL_PREFIX)).permitAll()
        .requestMatchers(new AntPathRequestMatcher(
            TOKEN_URL, HttpMethod.POST.name())).permitAll()
    // 뒷부분 생략

    위 설정으로 인증 여부와 관계없이 액추에이터 엔드포인트에 접근할 수 있게 됐다.

    9.4 스프링 부트 플러그인 태스크 설정하기

    스프링 부트 그래들 플러그인은 도커 이미지 빌드를 위해 bootBuildImage 명령어를 제공한다. .jar 파일 빌드에만 사용할 수 있으며, 스프링 부트의 bootBuildImagePaketo 빌드팩을 사용한다. Paketo는 LTS 버전의 자바 릴리스만 지원하며, Java 17은 LTS이므로 지원이 중단되지 않는다.

    // build.gradle
    bootBuildImage {
        imageName = "192.168.1.2:5000/${project.name}:${project.version}"
    }
    environment = ["BP_JVM_VERSION" : "17"]
    
    // settings.gradle
    rootProject.name = 'packt-modern-api-development-chapter09'

    9.5 도커 레지스트리의 설정

    개발 단계에서는 로컬 도커 레지스트리를 구성하여 쿠버네티스 환경에 배포하는 것이 이상적이다. 로컬 레지스트리는 TLS를 구성하지 않아 안전하지 않으므로 도커 설정을 변경해야 한다.

     

    daemon.json에 안전하지 않은 레지스트리 추가하기

    1. Docker app > Settings > Docker Engine 메뉴를 찾는다.
    2. JSON 파일에 insecure-registries 항목을 추가한다.
    {
        "features": { "buildkit": true },
        "insecure-registries": [ "192.168.1.2:5000" ]
    }
    1. 도커를 재실행한다.

    9.6 이미지를 빌드하는 그래들 태스크 실행

    # 로컬 도커 레지스트리 구동
    $ docker run -d -p 5000:5000 -e REGISTRY_STORAGE_DELETE_ENABLED=true \
    --restart=always --name registry registry:2
    
    # 이미지 빌드
    $ ./gradlew clean build
    $ ./gradlew bootBuildImage
    
    # 로컬 컨테이너 실행 (테스트)
    $ docker run -p 8080:8080 192.168.1.2:5000/packt-modern-api-development-chapter09:0.0.1-SNAPSHOT
    
    # 이미지 태그 설정 및 레지스트리 푸시
    $ docker tag 192.168.1.2:5000/packt-modern-api-development-chapter09:0.0.1-SNAPSHOT \
    192.168.1.2:5000/packt-modern-api-development-chapter09:0.0.1-SNAPSHOT
    $ docker push 192.168.1.2:5000/packt-modern-api-development-chapter09:0.0.1-SNAPSHOT
    
    # 레지스트리 이미지 확인
    $ curl -X GET http://192.168.80.1:5000/v2/_catalog
    {"repositories":["packt-modern-api-development-chapter09"]}
    
    $ curl -X GET http://192.168.80.1:5000/v2/packt-modern-api-development-chapter09/tags/list
    {"name":"packt-modern-api-development-chapter09","tags":["0.0.1-SNAPSHOT"]}

    9.7 쿠버네티스에 애플리케이션 배포하기

    도커 컴포즈도 여러 컨테이너를 관리하는 기능을 제공하지만, 쿠버네티스를 사용하는 것이 좋다. 쿠버네티스를 사용하면 컨테이너 관리뿐만 아니라 배포된 컨테이너를 동적으로 확장할 수도 있다. 미니큐브(Minikube)는 로컬에서 학습 및 개발 목적으로 단일 노드의 쿠버네티스 클러스터를 실행하는 도구다.

    미니큐브 설정 (로컬 레지스트리 연동)

    미니큐브는 기본적으로 원격 도커 허브를 사용하기 때문에 로컬 레지스트리를 사용하려면 ~/.minikube/machines/minikube/config.jsonInsecureRegistry 항목에 로컬 레지스트리 정보를 추가해야 한다.

    "InsecureRegistry": [
        "10.96.0.0/12",
        "192.168.1.2:5000"
    ],
    # 미니큐브 재시작
    $ minikube start --insecure-registry="192.168.80.1:5000"
    
    # 쿠버네티스 동작 확인
    $ kubectl get po -A
    
    # 미니큐브 대시보드 실행
    $ minikube dashboard

    네임스페이스(namespace)는 쿠버네티스 클러스터의 리소스를 사용자나 프로젝트 간에 나눌 수 있게 해주는 쿠버네티스의 특수 객체다. 쿠버네티스 클러스터는 기본적으로 "default" 네임스페이스를 사용한다.

    deployment.yaml 생성

    쿠버네티스는 YAML 설정을 통해 객체를 생성한다. 샘플 앱을 배포하려면 deployment(파드 생성) 및 service(서비스 노출) 객체가 필요하다.

    $ kubectl create deployment chapter09 \
    --image=192.168.1.2:5000/packt-modern-api-development-chapter09:0.0.1-SNAPSHOT \
    --dry-run=client -o=yaml > deployment.yaml
    
    $ echo --- >> deployment.yaml
    
    $ kubectl create service clusterip chapter09 --tcp=8080:8080 \
    --dry-run=client -o=yaml >> deployment.yaml
    코드: /Chapter09/k8s/deployment.yaml
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: chapter09
      name: chapter09
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: chapter09
      strategy: {}
      template:
        metadata:
          labels:
            app: chapter09
        spec:
          containers:
          - image: 192.168.1.2:5000/packt-modern-api-development-chapter09:0.0.1-SNAPSHOT
            name: packt-modern-api-development-chapter09
            resources: {}
    status: {}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: chapter09
      name: chapter09
    spec:
      ports:
      - name: 8080-8080
        port: 8080
        protocol: TCP
        targetPort: 8080
      selector:
        app: chapter09
      type: ClusterIP
    status:
      loadBalancer: {}

    쿠버네티스에 배포 및 확인

    # 배포 명령 실행
    $ kubectl apply -f k8s/deployment.yaml
    deployment.apps/chapter09 created
    service/chapter09 created
    
    # 파드와 서비스 상태 확인
    $ kubectl get all

    파드(Pod)는 쿠버네티스의 가장 작은 배포 단위로 하나 이상의 컨테이너가 포함된다. 쿠버네티스가 파드를 관리하다가 파드가 다운되면 다른 파드로 대체하기 때문에 IP 등의 구성 정보는 언제든 변경될 수 있다. Service 객체는 파드의 IP 주소를 외부에 노출하거나 파드에 대한 맵핑 정보를 관리하는 추상화 계층을 제공한다.

    SSH 터널링으로 외부에서 접근하기

    쿠버네티스 내에서 실행 중인 애플리케이션에 직접 접근할 방법은 없으므로 포트 포워딩(SSH 터널링)을 사용해야 한다.

    $ kubectl port-forward service/chapter09 8080:8080
    Forwarding from 127.0.0.1:8080 -> 8080
    
    # 새 터미널에서 확인
    $ curl localhost:8080/actuator/health
    {"status":"UP","groups":["liveness","readiness"]}

    쿠버네티스 클러스터에 애플리케이션이 성공적으로 배포된 것을 확인했다. 이제 포스트맨을 사용해 모든 REST 엔드포인트에 쿼리를 해 볼 수 있다.

     

    이 글은 『스프링 6와 스프링 부트 3로 배우는 모던 API 개발』 책의 내용을 바탕으로 작성되었습니다.
    Comments