본문 바로가기
MSA (Micro Service Architecture)/Legacy to Domain-Driven Platform

[MSA 말고 Modular Monolith] 4편 — 개발 서버 세팅: Ubuntu + Docker + k3s 설치

by kellis 2026. 7. 2.

 


이 시리즈는 글쓴이 본인이 수행한 레거시 서비스를 리뉴얼 전환한 과정에서 수행한 의사 결정 사항들을 정리한 글입니다. 
실제 운영 중인 PHP 레거시 에듀테크 플랫폼을 Python + React + K3S 환경으로 전환하는 과정에서 
아키텍처 설계와 인프라 구성에 대한 내용이 포함되어 있습니다. 

 

목차

 

 

 


 

 

이제 종이 위의 설계를 실제로 돌릴 환경을 만들 차례이다. 

 

첫 목표는 개발서버에 컨테이너 오케스트레이션 환경을 세우는 것이다. 구체적으로 Ubuntu 위에 Docker와 k3s를 올린다고 말할 수 있겠다. 

 

 


 

 

1. 개발 서버 환경

개발 서버는 운영서버의 웹서비스 서버와 DB 서버가 한대로 합쳐져 있다고 볼 수 있다. 따라서 개발서버의 컨테이너 환경은 운영에도 동일하게 올라간다는 뜻이다. 

 

개발 서버의 사양은 다음과 같다

OS                    Ubuntu 24.04.4 LTS
커널                   6.8.0-117-generic (x86_64)
CPU/메모리       메모리 15Gi
디스크               100GB (SSD)
유형                   VPS (가상 서버)

 

 

신규서버이기 때문에 메모리와 가용 디스크는 넉넉하다. 

$ free -h && df -h /
               total        used        free      shared  buff/cache   available
Mem:            15Gi       650Mi        12Gi       2.4Mi       3.1Gi        14Gi
Swap:          3.8Gi          0B       3.8Gi
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda2       100G   11G   90G  11%

 

 

 

왜 단일 노드인가

 

앞서 말했듯, 개발 서버는 운영계의 두 서버를 하나의 노드에 합쳐서 구성한다. 

운영계
app01 k3s (stateless 애플리케이션 전부)
db01 MySQL + Berkeley DB (stateful)

 

물리적으로 나누는 것은 운영계에서의 중점 내용이고, 개발계에서는 전체 구조가 제대로 도는지 검증하는 것이 목적이기 때문이다. 그러므로 개발 서버는 단일노드 k3s로, 애플리케이션과 데이터를 한 서버에서 모두 돌린다. 

 

k3s는 단일 노드에서도 완전한 기능의 클러스터로 동작하기 때문에, 이 검증이 그대로 운영계 다중 노드 구성으로 이어질 수 있다. 

 

 


 

 

2. Docker와 K3S

설치에 앞서 짚고 넘어가야 하는 것은 개념 정리이다. Docker와 K3S는 하는 일이 다르다. 

 

왜 Docker를 설치했는가

Docker를 설치한 이유 자체는 간단하다. Service C, D를 리팩토링 없이 컨테이너로 이관하기로 했으니, 이 PHP 레거시를 컨테이너 이미지로 만들기 위해서 Docker가 필요했다. 

 

역할은 이렇게 나뉜다

Docker     이미지 빌드 도구
                  - Service C/D PHP 컨테이너 이미지 빌드
                  - platform-api (FastAPI) 이미지 빌드
                  - React 프론트엔드 이미지 빌드

k3s           컨테이너 실행·관리
                  - 빌드된 이미지들을 실제로 배포·운영
                  - 헬스체크, 롤링 업데이트, 서비스 디스커버리

 

컨테이너 이미지로 만들어진 서비스 C, D 역시 배포 관리 및 모니터링 등의 일원화를 위해 K3S로 올려서 관리한다. 

 

Docker는 이미지를 만드는 도구, K3S는 그 이미지를 돌리는 도구다. 그러므로 이 서버에서는 docker-compose는 사용하지 않는다.

 

 

K3S는 Docker를 런타임으로 쓰지 않는다.

"Docker를 깔았으니 k3s가 Docker를 컨테이너로 돌리겠지?" 라는 생각은 잘못되었다. 

 

먼저, 컨테이너 런타임 (container runtime) 의 개념을 짚고 넘어가자. 컨테이너 런타임은 이미지를 실제로 실행해서 컨테이너로 돌리는 하위 엔진이다. 흔히 Docker를 "컨테이너를 돌리는 것"으로 알지만, 정확하게는 Docker도 내부적으로 containerd라는 런타임에게 실행을 맡긴다. 

Docker는 이미지 빌드, CLI, 네트워크 관리 같은 상위 기능을 담당하고, 실제 컨테이너 실행과 같은 일은 containerd가 한다. 

 

쿠버네티스 역시 이 containerd를 런타임으로 사용한다. 과거에는 쿠버네티스가 Docker를 거쳐 컨테이너를 실행했으나, 지금은 Docker라는 중간 단계 없이 containerd를 직접 호출한다. 이는 k3s 역시 마찬가지이다. 

 

k3s는 Containerd를 자체 내장하고 있다. 컨테이너를 실제로 실행하는 런타임은 이 내장 containerd인 것이지, 우리가 설치한 Docker가 아니다. Docker와 k3s는 같은 서버에 공존하고 있기는 하지만, 컨테이너의 실행은 각자 하게 된다. 

Docker     → 자체 containerd로 이미지 빌드/테스트
k3s           → 내장 containerd로 Pod 실행

 

 


 

 

3. Docker 설치

Docker를 설치할 때 유의해야 할 부분이 있다. 

 

sudo apt install docker.io로 설치하면 안 된다

 

Ubuntu 기본 저장소의 Docker는 버전이 오래 되었기 때문에 Docker 공식 apt 저장소를 등록해서 최신 stable을 받는게 권장 방식이다.

 

 

3-1. 기존 충돌 패키지 제거

먼저 이미 설치된 Docker 관련 패키지가 있으면 충돌하므로 정리한다. ( 신규 서버라면 대부분 설치되어있지 않다 )

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

 

 

3-2. Docker 공식 GPG 키 등록

저장소의 패키지 서명을 검증하기 위한 공식 GPG 키를 받는다.

# 사전 패키지 설치
sudo apt update
sudo apt install -y ca-certificates curl

# 키를 저장할 디렉터리 생성
sudo install -m 0755 -d /etc/apt/keyrings

# Docker 공식 GPG 키 다운로드
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

 

키가 /etc/apt/keyrings/docker.asc에 저장된다. 뒤에서 저장소를 등록할 때 이 키로 서명을 검증한다.

 

 

3-3. Docker apt 저장소 등록

이제 공식 Docker 저장소를 apt 소스에 추가한다. 

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

sudo apt update

 

등록된 저장소를 확인하면 아래와 같이 출력된다

( 확인을 위한 명령어들은 결과 데이터와 구분을 위해 명령어 앞에 $ 표시를 표기했다.

코드를 복붙해서 사용한다면 $를 복사하지 않게 유의하자 )

$ cat /etc/apt/sources.list.d/docker.list
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable

 

Ubuntu 24.04 의 코드네임인 noble과 stable 채널이 잡힌 것을 확인할 수 있다.

 

3-4. Docker 설치

이제 Docker 엔진과 관련 구성 요소를 설치한다.

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

 

각 패키지의 역할은 이러하다 

docker-ce Docker 엔진 (본체)
docker-ce-cli docker 명령어 CLI
containerd.io 컨테이너 런타임 (Docker가 쓰는 containerd)
docker-buildx-plugin 멀티 플랫폼 이미지 빌드 도구
docker-compose-plugin docker compose 명령 

 

여기서 containerd.io가 함께 깔리는데, 이게 Docker가 쓰는 containerd다. 앞서 말했듯 k3s는 자기만의 containerd를 따로 내장하고 있어서, 이 둘은 별개로 동작한다.

 

 

3-5.  설치 확인

설치 후에는 제대로 잘 설치가 되었는지 확인해 보는 습관을 들이는 것이 좋다.

$ docker --version
Docker version 29.5.1, build 2518b52

 

설치된 패키지 목록으로 구성 요소를 다시 확인할 수 있다.

$ apt list --installed 2>/dev/null | grep -i docker
docker-buildx-plugin/noble,now 0.34.0-1~ubuntu.24.04~noble amd64 [installed]
docker-ce-cli/noble,now 5:29.5.1-1~ubuntu.24.04~noble amd64 [installed]
docker-ce/noble,now 5:29.5.1-1~ubuntu.24.04~noble amd64 [installed]
docker-compose-plugin/noble,now 5.1.3-1~ubuntu.24.04~noble amd64 [installed]

 

서비스가 켜져 있는지, 부팅시 자동 시작되도록 설정되었는지도 확인한다. 

$ sudo systemctl is-enabled docker && sudo systemctl is-active docker
enabled
active

 

enabled(부팅 자동 시작) + active(현재 실행 중)이면 정상이다.

 

 

3-6.  sudo 없이 docker 쓰기

기본 상태에서는 docker 명령에 sudo가 필요하다. 매번 붙이기 번거로우니 현재 사용자를 docker 그룹에 추가한다.

sudo usermod -aG docker $USER

 

이 명령 후에는 재로그인(또는 newgrp docker)해야 그룹 변경이 적용된다. 적용되면 sudo 없이 docker가 동작한다.

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

 

권한 오류 없이 빈 목록이 나타난다면 성공이다. 아직 실행중인 컨테이너가 없으므로 목록은 비어 있다. 

 

 

 


 

 

4. K3S 설치

k3s를 설치할때는 공식 설치 스크립트 한 줄로 가능하다. 다만 옵션을 하나 붙인다.

curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644

 

 

왜 --write-kubeconfig-mode 644를 붙이는가

옵션 없이 설치하게 되면 kubeconfig 파일 (/etc/rancher/k3s/k3s.yaml) 이 root 소유에 권한 600으로 생성된다. 그러면 일반 사용자가 이 파일을 읽을 수 없으므로, kubectl 명령어를 사용하려면 sudo 권한을 사용해야 한다. 

 

--write-kubeconfig-mode 644를 주면 이 파일을 모든 사용자가 읽을 수 있게 되어(rw-r--r--), sudo 없이 kubectl을 쓸 수 있다.

 

설치 실행

설치를 진행하면 이런 로그가 출력된다. 

[INFO] Finding release for channel stable
[INFO] Using v1.36.2+k3s1 as release
[INFO] Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.36.2%2Bk3s1/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Skipping /usr/local/bin/ctr symlink to k3s, command exists in PATH at /usr/bin/ctr
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO] systemd: Creating service file /etc/systemd/system/k3s.service
[INFO] systemd: Enabling k3s unit
[INFO] systemd: Starting k3s

 

로그에서 유의미한 몇 가지만 설명하자면 아래와 같다. 

v1.36.2+k3s1 stable 채널의 k3s 버전
/usr/local/bin/k3s 단 하나의 바이너리로 설치됨
kubectl → k3s symlink kubectl을 따로 설치 안 해도 k3s가 제공
crictl → k3s symlink 컨테이너 런타임 디버깅용 CLI
Skipping ctr symlink ctr가 이미 PATH에 있어 건너뜀 ← Docker가 깔아둔 것
k3s-uninstall.sh 제거용 스크립트
systemd: Starting k3s systemd 서비스로 등록되어 부팅 시 자동 시작

 

 

 


 

 

5. 설치 확인

5-1. k3s 서비스 상태

$ sudo systemctl status k3s --no-pager
● k3s.service - Lightweight Kubernetes
     Loaded: loaded (/etc/systemd/system/k3s.service; enabled; preset: enabled)
     Active: active (running) since Wed 2026-07-01 16:03:14 KST; 1min 25s ago
   Main PID: 906816 (k3s-server)
      Tasks: 82
     Memory: 1.8G (peak: 1.8G)
     CGroup: /system.slice/k3s.service
             ├─906816 "/usr/local/bin/k3s server"
             ├─906855 "containerd "
             └─ ...

 

active ( running ) 으로 뜨는 것이 정상이다. 여기서 눈여겨 볼 부분은 CGroup 아래에 containerd가 함께 실행되고 있다는 점이다. 앞서 말했듯 k3s 내장 containerd가 실제로 돌고 있는 것을 볼 수 있다. k3s는 메모리 전체가 1.8G를 사용하고 있다. 

 

 

5-2. 노드 상태

$ kubectl get nodes
NAME        STATUS   ROLES           AGE   VERSION
localhost   Ready    control-plane   97s   v1.36.2+k3s1

 

노드가 Teady 상태이고, control-plane 역할을 맡고 있다. 단일 노드가 컨프롤 플레인이자 워커 역할을 겸하는, 개발 서버에서 의도한 구성이다. (설치 직후에는 NotReady 상태로 보일 수 있다. 잠시 기다리면 Ready로 올라온다)

 

 

5-3. 시스템 Pod 확인

$ kubectl get pods -A
NAMESPACE     NAME                                      READY   STATUS      RESTARTS      AGE
kube-system   coredns-5f5694d56b-bxvgq                  1/1     Running     0             101s
kube-system   helm-install-traefik-crd-pnq8s            0/1     Completed   0             96s
kube-system   helm-install-traefik-cshhx                0/1     Completed   2 (84s ago)   96s
kube-system   local-path-provisioner-58d557dc48-snf9p   1/1     Running     0             101s
kube-system   metrics-server-7c86f97b8d-j8cg5           1/1     Running     0             100s
kube-system   svclb-traefik-1a438547-cfqd6              2/2     Running     0             70s
kube-system   traefik-6cd8c7cd89-2jwph                  1/1     Running     0             70s

 

k3s만 설치하면 필요한 기본 구성 요소가 다 실행된다.

coredns 클러스터 내부 DNS (서비스 이름으로 통신 가능하게)
local-path-provisioner 기본 스토리지 프로비저너 (PVC 요청 시 로컬 경로 할당)
metrics-server 리소스 메트릭 수집 (kubectl top 명령 지원)
traefik Ingress 컨트롤러 
svclb-traefik Traefik용 서비스 로드밸런서
helm-install-traefik Traefik을 설치한 Helm Job (Completed)

 

traefik이 이미 Running 상태인 것을 볼 수 있다. 이 내장 Traefik을 Ingress로 활용해, 여러 서비스로 트래픽을 라우팅하게 된다. 별도의 Ingress 컨트롤러를 설치할 필요가 없다는게 k3s의 큰 장점이다. 

 

 

 


 

 

 

6. K3S가 품고 있는 것들

목록에서 여러 구성 요소가 자동으로 뜨는 것을 확인했다. 이것들이 각각 무슨 역할을 하는지 모른다면 앞으로 클러스터를 다룰 때마다 막힐 것이다. 쿠버네티스를 처음 접한다면 이 섹션이 이후 내용들의 바탕이 될 것이다. 

 

 

컨테이너 런타임 ( containerd )

컨테이너를 실제로 실행하는 하위 엔진. kubectl get pods 로 보이는 모든 Pod는 결국 이 containerd 위에서 컨테이너로 돌아간다. 

 

CoreDNS — 클러스터 내부 DNS

컨테이너는 수시로 생겼다 사라지고, 그때마다 내부IP가 변경된다. IP로 호출하게 되면 관리가 불가능하다는 것이다. 그래서 쿠버네티스는 서비스 이름으로 통신한다. 예를 들어 platform-api가 redis를 호출할 때에는 redis라는 이름으로 부르게 된다. 

이 이름을 실제 IP로 바꿔주는 것이 CoreDNS이다. 클러스터 내부의 전화번호부라고 보면 된다. 

 

local-path-provisioner — 기본 스토리지

컨테이너는 기본적으로 휘발성이다. Pod가 재시작되면 안에 쌓인 데이터가 사라진다. DB처럼 데이터를 보존해야 하는 경우엔 별도의 영구 저장 공간이 필요하다. 

쿠버네티스에서는 이 저장 공간을 PV(PersistentVolume)이라고 부르고, "이만큼의 저장 공간을 달라"는 요청을 PVC(PersistantVolumeClaim) 라 한다. 그리고 PVC 요청이 들어왔을때 실제 저장 공간을 만들어 주는 주체가 바로 프로비저너(provisioner)다 

local-path-provisioner 는 k3s 의 기본 프로비저너로, PVC 요청이 오면 노드의 로컬 디스크 경로를 할당해준다. 별도 스토리지 시스템 없이도 영구 볼륨을 쓸 수 있게 해준다. 

 

metrics-server — 리소스 메트릭

각 Pod와 노드가 CPU 및 메모리를 얼마나 쓰는지 수집한다. 이게 있어야 kubectl top pod 같은 명령으로 사용량을 보거나, 부하에 따라 Pod 수를 자동으로 늘리는 오토스케일링(HPA)을 쓸 수 있다. 

 

Traefik — Ingress 컨트롤러 (트래픽 입구)

외부에서 들어오는 HTTP 요청을 받아 클러스터 내부의 알맞을 서비스로 보내주는 관문이다. 쿠버네티스에서는 이 " 외부 트래픽을 내부 서비스로 라우팅하는 규칙" 을 Ingress라 부르고, 그 규칙을 실제로 수행하는 것이 Ingress 컨트롤러이다. Traefik이 바로 그 컨트롤러이다.

 

 

K8S를 이용한다면 이 것들을 하나하나 직접 설치하고 설정해야 한다. 그러나 이번 서버에서는 K3S에서 제공하는 기본설정만으로도 충분하기 때문에 모두 기본 제공되어 설치하자마자 바로 사용가능한 K3S를 사용한다. 

 

 


 

 

7. sudo 없이 kubectl 쓰기 확인

아까 설치할 때 옵션으로 준 --write-kubeconfig-mode 644 명령이 잘 적용되었는지 확인한다. 

$ ls -l /etc/rancher/k3s/k3s.yaml
-rw-r--r-- 1 root root 2945 Jul  1 16:03 /etc/rancher/k3s/k3s.yaml

 

권한이 -rw-r--r--(644)로 되어 있다. 파일 소유자는 root지만, 모든 사용자가 읽기는 가능하다. 그래서 일반 사용자도 이 kubeconfig를 읽어 kubectl을 쓸 수 있다.

$ echo $KUBECONFIG

 

KUBECONFIG 환경변수는 비어 있다. 그럼에도 kubectl get nodes 가 잘 동작한 것은, k3s가 설치될 때 kubectl(k3s 심링크)이 기본 경로인 /etc/rancher/k3s/k3s.yaml을 자동으로 찾도록 되어 있기 때문이다. 권한이 644라 이 파일을 읽을 수 있고, 그래서 환경변수 설정 없이도 kubectl이 바로 동작한다.

참고:
만약 다른 도구(Helm, k9s 등)나 다른 사용자 계정에서 kubectl을 쓰려면, kubeconfig를 홈 디렉터리로 복사해두는 게 편하다.
mkdir -p ~/.kube
cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
export KUBECONFIG=~/.kube/config​

 




 

 

8. 설치 전에 챙기면 좋은 것들

kubeconfig 권한을 고려해 옵션을 추가하여 k3s를 설치한 것처럼, 설치를 하기 전에 미리 체크하면 좋은 사항들을 정리해본다. 

 

  1. kubeconfig 권한 (가장 흔한 함정) --write-kubeconfig-mode 644 없이 설치하면 kubectl마다 sudo가 필요해진다. 설치 시점에 이 옵션을 주는 게 가장 깔끔하다. 이미 설치했다면 sudo chmod 644 /etc/rancher/k3s/k3s.yaml로 사후 조정도 가능하다.
  2. 메모리 k3s 최소 요구 사항은 512MB지만, 실제로 워크로드까지 올리면 1GB로는 빠듯하다. 여러 서비스를 올릴 계획이라면 최소 2GB 이상을 권장한다. 
  3. 방화벽 / 포트 단일 노드에서는 큰 문제가 없지만, 나중에 워커 노드를 추가하거나 외부에서 접근하려면 포트를 열어야 한다. 대표적으로 6443(Kubernetes API), 그리고 Flannel VXLAN을 쓴다면 8472/UDP다. VPS라면 제공업체의 방화벽 설정도 함께 확인해야 한다.
  4.  swap Kubernetes는 전통적으로 swap을 끄는 걸 권장해왔다. k3s는 swap이 켜져 있어도 동작하지만, 예측 가능한 성능을 원한다면 꺼두는 것도 방법이다. (이 글에서는 개발 서버라 swap이 켜진 채로 두고 아무런 조치를 취하지 않았다.)

 

 


 

 

마치며

 

 

이 글에서 수행한 것은 아래와 같다 

 

✓  개발 서버(dev01) 환경 확인 — Ubuntu 24.04, 메모리 15Gi, 단일 노드

✓   Docker와 k3s의 역할 구분 — Docker는 빌드, k3s는 실행

✓  C/D를 k3s에 통합하기로 결정 (compose 미사용)

✓  Docker 설치 — 공식 apt 저장소로 최신 stable (29.5.1)

  k3s v1.36.2 설치 (--write-kubeconfig-mode 644)

노드 Ready, 시스템 Pod 정상 (traefik 내장 확인)

sudo 없이 docker / kubectl 사용 가능

 

 

k3s 한 줄 설치로 컨테이너 오케스트레이션 환경이 준비됐다. coredns, traefik, metrics-server까지 기본 제공되니, 무거운 k8s를 직접 구성하는 것과는 시작점부터 다르다.

 

다음 편에서는 이 위에 올릴 서비스들을 어떻게 격리하고 라우팅할지 다룬다. Namespace로 도메인/환경을 나누고, 내장 Traefik을 Ingress로 구성해 여러 서비스로 트래픽을 흘려보낸다.

 

 

 

 

** 참고자료 

 

 

Docker Engine 공식 설치 문서 (Ubuntu): https://docs.docker.com/engine/install/ubuntu/

 

Install Docker Engine on Ubuntu

Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install Docker Engine on Ubuntu.

docs.docker.com

 

 

Docker 설치 후 설정 (non-root 사용): https://docs.docker.com/engine/install/linux-postinstall/

 

Linux post-installation steps for Docker Engine

Find the recommended Docker Engine post-installation steps for Linux users, including how to run Docker as a non-root user and more.

docs.docker.com

 

 

k3s 공식 문서: https://docs.k3s.io/

 

K3s - Lightweight Kubernetes | K3s

Lightweight Kubernetes. Easy to install, half the memory, all in a binary of less than 100 MB.

docs.k3s.io

 

 

k3s 설치 옵션(Configuration): https://docs.k3s.io/installation/configuration

 

Configuration Options | K3s

This page focuses on the options that are commonly used when setting up K3s for the first time. Refer to the documentation on Advanced Options and Configuration and the server and agent command documentation for more in-depth coverage.

docs.k3s.io

 

 

k3s Requirements (시스템 요구사항): https://docs.k3s.io/installation/requirements

 

Requirements | K3s

K3s is very lightweight, but has some minimum requirements as outlined below.

docs.k3s.io

 

 

댓글