Swarm 클러스터에서 외부 볼륨 (Bind Mount: NFS)을 이용한 Persistent Volume 사용하기
목표: Docker Swarm 클러스터에서 NFS를 활용하여 영속적인 데이터 저장소를 제공하고, CI/CD 도구(Jenkins 또는 GitLab)을 통해 자동화된 배포 환경을 설정하여 개발자가 GitHub 또는 GitLab에 푸시한 코드를 자동으로 배포하는 시스템을 구성합니다.
1. Swarm 클러스터에서 NFS 외부 볼륨 사용
NFS 서버 설정:
- NFS 서버를 설정하여 Swarm 클러스터 노드들 간에 데이터를 공유할 수 있게 합니다.
- NFS 서버를 설치하고, 공유 디렉토리를 설정합니다.
- NFS 서버 설정:
- 공유할 디렉토리 설정: /mnt/nfs_share
- exports 파일 수정:
- /mnt/nfs_share *(rw,sync,no_subtree_check)
- NFS 서버를 실행하고 모든 Swarm 노드에서 NFS 클라이언트를 설치합니다.
- 클라이언트 설치: sudo apt install nfs-common
- NFS 공유 디렉토리 마운트:
- sudo mount -t nfs <NFS_SERVER_IP>:/mnt/nfs_share /mnt/nfs_share
Swarm 서비스에서 외부 볼륨 설정:
- Docker Compose 파일 예시 (Swarm 서비스 배포):
version: '3.7'
services:
app:
image: nginx
deploy:
replicas: 3
volumes:
- nfs-volume:/usr/share/nginx/html
volumes:
nfs-volume:
driver: local
driver_opts:
type: nfs
o: addr=<NFS_SERVER_IP>,nolock,soft,rw
device: ":/mnt/nfs_share"
- nfs-volume을 정의하고 driver_opts를 통해 NFS 서버를 마운트합니다.
배포:
- 위 파일을 사용하여 Swarm 클러스터에 서비스를 배포합니다:
- docker stack deploy -c docker-compose.yml my_stack
2. CI/CD (Jenkins 또는 GitLab CI/CD) 설정
2.1 Jenkins를 이용한 CI/CD
- Jenkins 설치: Jenkins를 Swarm 클러스터의 하나의 노드에 설치하거나 Docker로 실행합니다.
- GitHub/GitLab 연동: GitHub 또는 GitLab 저장소와 Jenkins를 연결하여 소스 코드 변경을 감지하고, 이를 트리거하여 자동 빌드 및 배포합니다.
Jenkins 파이프라인 예시 (Jenkinsfile):
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git '<https://github.com/your/repository.git>'
}
}
stage('Build') {
steps {
sh 'docker build -t your-image:latest .'
}
}
stage('Push') {
steps {
sh 'docker push your-image:latest'
}
}
stage('Deploy') {
steps {
sh 'docker stack deploy -c docker-compose.yml your_stack'
}
}
}
}
- 설명:
- GitHub에서 코드 클론
- Docker 이미지를 빌드
- 이미지를 Docker Registry에 푸시
- Swarm 클러스터에 서비스 배포
2.2 GitLab CI/CD 설정
- GitLab에서 GitLab Runner를 설정하여 CI/CD 파이프라인을 구성합니다.
- GitLab Runner는 Docker 환경에서 컨테이너를 사용하여 파이프라인을 실행할 수 있습니다.
.gitlab-ci.yml 예시:
stages:
- build
- deploy
build:
stage: build
script:
- docker build -t your-image:latest .
- docker push your-image:latest
deploy:
stage: deploy
script:
- docker stack deploy -c docker-compose.yml your_stack
- 설명:
- GitLab에 푸시된 코드로 Docker 이미지를 빌드하고 푸시
- Swarm 클러스터에 자동 배포
3. GitHub/GitLab 저장소 연동 및 자동 배포
- GitHub 연동:
- Webhook: GitHub에 푸시 이벤트를 트리거하여 Jenkins 또는 GitLab CI/CD가 자동으로 실행되도록 설정합니다.
- Jenkins 또는 GitLab Runner는 소스 코드를 클론하고, 이를 빌드하여 Docker 이미지를 생성하고 푸시합니다.
- 푸시된 이미지는 Swarm 클러스터에 배포됩니다.
- GitLab 연동:
- GitLab은 자체 CI/CD를 제공하며, .gitlab-ci.yml 파일을 기반으로 자동 빌드 및 배포가 이루어집니다.
- GitLab에서 푸시된 코드가 GitLab Runner를 통해 Jenkins와 유사한 방식으로 처리됩니다.
4. 자동화된 배포 및 관리
- 자동 배포:
- GitHub/GitLab에 푸시된 코드가 Jenkins 또는 GitLab CI/CD를 통해 자동으로 배포됩니다.
- Swarm 클러스터에서 서비스가 자동으로 업데이트됩니다.
- 영속적 저장소:
- NFS를 활용한 Persistent Volume으로 서비스가 중단되더라도 데이터는 안전하게 유지됩니다.
결론
이 구성은 Swarm 클러스터에서 NFS 기반 Persistent Volume을 사용하여 데이터를 안전하게 유지하고, Jenkins 또는 GitLab CI/CD를 이용해 개발자가 GitHub/GitLab에 푸시한 코드를 자동으로 클러스터에 배포하는 완전한 자동화된 개발/배포 환경을 만듭니다. 이는 코드 변경이 발생하면 즉시 빌드 및 배포가 이루어져 CI/CD 파이프라인을 효율적으로 자동화할 수 있습니다.
복습
고가용성을 위한 Docker Compose 사용 시, yml 파일을 이용해 하나의 서버에서 두 개의 컨테이너를 실행할 수 있습니다. 하지만, 하나의 호스트 포트를 두 개의 컨테이너에 연결할 수 없기 때문에 각 컨테이너는 다른 포트를 사용하여 접속하게 됩니다.
서버를 2개 이상 사용할 경우, 각 서버는 독립된 리소스를 사용할 수 있는 장점이 있지만, IP가 다르더라도 동일한 포트를 사용할 수 있다는 점이 장점입니다. 이때 **NFS(Network File System)**를 사용해 마운트 및 구성을 각각 해야 합니다.
클러스터를 사용하면 여러 서버에서 컨테이너, 네트워크, 볼륨을 일괄적으로 관리할 수 있습니다. 이를 위해 컨테이너 오케스트레이션 툴을 사용하게 되며, 주요 툴로는 **쿠버네티스(Kubernetes)**와 **도커 스웜(Docker Swarm)**이 있습니다.
쿠버네티스
사용 방법에 따라 세 가지로 나눌 수 있습니다:
- 구성형: kubeadm, kubespray 등
- 설치형: Rancher, OpenShift (RedHat 계열)
- 관리형: 클라우드 서비스 제공자(CSP)에서 제공하는 관리형 서비스로, EKS(AWS), GKE(Google Cloud), AKS(Azure), NKS(Nutanix) 등이 있습니다.
이러한 툴을 통해 고가용성 환경을 구축하고 관리할 수 있습니다.
- Pod는 Kubernetes에서 하나 이상의 컨테이너를 포함하며, 이 컨테이너들은 네트워크와 볼륨을 공유합니다.
- 같은 Pod 내의 컨테이너는 밀접하게 연관된 작업을 수행하는 경우가 많습니다.
도커 스웜
**매니저(Manager)**와 워커(Worker) 노드로 구성됩니다. 이들 각각은 토큰을 가지고 있으며, 워커는 매니저에게 조인하여 매니저에 의해 관리됩니다. 도커 스웜은 클러스터 내에서 컨테이너의 네트워크를 일괄적으로 관리합니다.
주요 특징:
- 도커 네트워크:
- 기존의 docker0와 달리, 도커 스웜에서는 docker gwbridge가 생성됩니다.
- docker gwbridge는 로컬 스코프로 동작합니다.
- 인그레스 네트워크가 생성되며, 이는 오버레이(overlay) 드라이버를 사용하여 스웜 스코프로 동작합니다. 즉, 클러스터 내에서만 사용됩니다.
- 네트워크 흐름:
- 외부 사용자는 물리적 인터페이스를 통해 docker gwbridge와 연결됩니다.
- 이때, 연결된 스위치는 인그레스 VIP(Virtual IP)로 트래픽을 전달합니다.
- VIP는 로드밸런서 역할을 하며, 예를 들어 HAProxy가 외부의 공인 IP를 받아 백엔드의 사설 IP로 라운드 로빈 방식으로 트래픽을 전달합니다.
- 인그레스 네트워크 동작:
- 인그레스 네트워크도 위와 같은 방식으로 동작하며, 로드밸런서 역할을 통해 클러스터 내 서비스로 트래픽을 분배합니다.
1. 도커 컨테이너와 도커 서비스
- 도커 서비스에 컨테이너를 추가하려면, docker container run --attachable 옵션을 사용해야 합니다. 이 옵션은 컨테이너를 서비스에 연결 가능하도록 설정합니다.
2. 도커 스택(Docker Stack)
- 도커 스택은 도커 컴포즈와 도커 스웜을 결합한 개념입니다.
- docker stack을 사용하면 yml 파일을 통해 여러 서비스와 네트워크, 볼륨 등을 정의하고, 이를 클러스터에서 관리할 수 있습니다. 즉, 도커 스택은 도커 스웜 클러스터 환경에서 컴포즈처럼 여러 서비스를 한 번에 배포하고 관리하는 방법입니다.
- 도커 스웜에서는 서비스 단위로 애플리케이션을 관리합니다. 이를 통해 **서비스의 확장(scale out)**이나 배포를 보다 효율적으로 관리할 수 있습니다.
- 예를 들어, docker service scale <서비스명>=<갯수> 명령을 사용하여 해당 서비스의 컨테이너 수를 동적으로 조정할 수 있습니다.
- 서비스 단위 관리
도커 스택은 여러 서비스를 포함하며, 각 서비스는 하나 이상의 컨테이너로 구성됩니다.
프로비전 && 디플로이
- 인프라가 구성되었다는 것은 **프로비전(provision)**이 완료되었다는 의미입니다.
- 프로비전은 서버, 네트워크, 스토리지 등 인프라 리소스를 설정하고 준비하는 과정입니다.
- 애플리케이션 설치가 되었다는 것은 **디플로이(deploy)**가 완료되었다는 의미입니다.
- 디플로이는 애플리케이션을 실제 운영 환경에 배포하고 실행하는 과정입니다.
- 프로비전: 인프라 리소스를 준비하고 설정하는 과정.
- 디플로이: 애플리케이션을 배포하여 운영 환경에서 실행되도록 하는 과정.
VLAN (Virtual Local Area Network):
- VLAN은 같은 네트워크 상에서 논리적으로 구분된 네트워크입니다.
- VLAN 내의 장치들은 서로 통신할 수 있지만, 다른 VLAN에 있는 장치들과는 통신할 수 없습니다.
- 스위치 2대가 있을 때, 같은 VLAN에 속한 장치들은 서로 통신할 수 있습니다. 하지만 라우터를 건너지 못합니다.
- VLAN 번호는 12비트를 사용하여 설정되며, 0번부터 4095번까지 사용할 수 있습니다.
VXLAN (Virtual Extensible LAN):
- VXLAN은 VLAN의 확장으로, 더 많은 가상 네트워크를 지원합니다.
- VXLAN은 최대 1600만 개의 가상 네트워크를 사용할 수 있습니다.
- VXLAN은 라우터를 건너 다른 네트워크와도 통신할 수 있기 때문에 VLAN보다 더 확장성이 뛰어납니다.
실습 내용 요약
이 실습에서는 Docker Swarm 환경에서 오버레이 네트워크와 스택 배포를 이용해 서비스를 구성하고, 이를 YAML 파일로 정의하여 Docker Stack으로 배포하는 과정입니다.
1. 오버레이 네트워크 생성
먼저, 오버레이 네트워크를 생성합니다:
docker network create --driver overlay testnet1
- -driver overlay: 오버레이 네트워크 드라이버를 사용하여 클러스터 내에서 네트워크를 생성합니다.
- testnet1: 네트워크 이름
2. 서비스 생성
서비스를 생성하여 컨테이너를 배포합니다:
docker service create --name testsvc1 --replicas 3 --constraint 'node.role == worker' --network testnet1 -p 8001:80 nginx
- -replicas 3: 서비스의 복제본을 3개 생성
- -constraint 'node.role == worker': 작업 노드에서만 서비스 실행
- -network testnet1: testnet1 네트워크에 연결
- p 8001:80: 포트 8001을 80으로 맵핑
- nginx: NGINX 이미지를 사용하여 서비스 배포
3. 서비스 삭제
docker service rm testsvc1
4. 네트워크 삭제
docker network rm testnet1
5. YAML 파일 생성 및 수정
net1.yml 파일을 생성하여 서비스와 네트워크를 정의합니다:
version: '3.8'
services:
nginx:
image: nginx:latest
ports:
- "8002:80"
networks:
- testnet2
deploy:
replicas: 2
placement:
constraints: [node.role != manager]
networks:
testnet2:
driver: overlay
attachable: true
- nginx 서비스:
- nginx:latest 이미지를 사용하고, 포트 8002를 컨테이너의 80번 포트에 매핑
- testnet2 네트워크에 연결
- replicas: 2: 서비스 복제본 2개 실행
- placement.constraints: 매니저 노드 제외, 워커 노드에서만 실행
- testnet2 네트워크:
- 오버레이 네트워크로 생성되며, attachable: true로 설정하여 외부에서 컨테이너가 이 네트워크에 연결할 수 있게 합니다.
6. 스택 배포
docker stack deploy 명령어를 사용하여 스택 배포:
docker stack deploy -c net1.yml nginxstack
7. 스택 상태 확인
- docker stack ls로 현재 배포된 스택 확인:
- NAME SERVICES ORCHESTRATOR nginxstack 1 Swarm
- docker stack ps nginxstack로 서비스 상태 확인:
- nginxstack_nginx: nginx 서비스가 worker2와 worker3에서 실행 중
- ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS rkq80lczhc3i nginxstack_nginx.1 nginx:latest worker3 Running Running 17 seconds ago svks0cgppiq1 nginxstack_nginx.2 nginx:latest worker2 Running Running 17 seconds ago
8. 서비스 상태 확인
- docker service ls로 서비스 확인:
- nginxstack_nginx: 2개의 복제본이 실행 중이며, 포트 8002가 80번 포트에 맵핑됨
- ID NAME MODE REPLICAS IMAGE PORTS cfxxj1siwq5q nginxstack_nginx replicated 2/2 nginx:latest *:8002->80/tcp
실습
NFS 서버를 활용한 Docker에서 영구 볼륨(Persistent Volume) 사용
1. NFS 서버 설정 (Manager)
- 필요한 패키지 설치
- sudo apt install -y nfs-common sudo apt install -y nfs-server
- 공유 디렉토리 생성 및 권한 설정
- sudo mkdir /nginx sudo chmod 777 /nginx
- NFS 서버 설정 파일 수정
- /etc/exports 파일을 수정하여 NFS 공유 디렉토리를 설정합니다.
- /nginx 디렉토리를 211.183.3.0/24 네트워크 대역으로 읽기/쓰기가 가능하도록 설정합니다.
sudo vi /etc/exports
# 파일에 아래 라인 추가:
/nginx 211.183.3.0/24(rw,sync,no_root_squash)
- NFS 서버 재시작
- sudo systemctl restart nfs-server
- NFS 서버 상태 확인
- sudo systemctl status nfs-server
2. 워커 노드에서 NFS 공유 접속 (Worker)
- 필요한 패키지 설치
- sudo apt install -y nfs-common
- NFS 서버 접속 확인
- sudo showmount -e 211.183.3.200 # 결과: /nginx 211.183.3.0/24
- NFS 공유 디렉토리 마운트
- sudo mount -t nfs 211.183.3.200:/nginx /home/user1/mounttest
- 마운트 상태 확인
- sudo mount | grep /nginx # 결과: 211.183.3.200:/nginx on /home/user1/mounttest type nfs4 (rw,...)
- 마운트 해제
- sudo umount /home/user1/mounttest
3. Docker Stack을 통한 NFS 볼륨 사용
- Docker Compose 파일 (nginx.yml) 작성
- NFS 볼륨을 Docker 서비스에 연결하여 nginx 웹 서버를 배포합니다.
version: '3.8'
services:
nginx:
image: nginx:latest
deploy:
replicas: 3
placement:
constraints: [node.role == worker] # 워커 노드에만 배포
volumes:
- nginx_vol:/usr/share/nginx/html # NFS 볼륨을 nginx HTML 디렉토리에 마운트
ports:
- "8888:80" # 포트 매핑
volumes:
nginx_vol:
driver: local
driver_opts:
type: "nfs4"
o: "addr=211.183.3.200,rw" # NFS 서버 주소와 rw 권한
device: ":/nginx" # NFS 서버의 공유 디렉토리
- Docker Stack 배포
- nginxtest라는 이름으로 스택을 배포합니다. NFS 볼륨은 nginx_vol이라는 이름으로 생성됩니다.
- docker stack deploy -c nginx.yml nginxtest
- NFS 서버 내용 확인
- nginx 디렉토리 내에 있는 50x.html과 index.html 파일을 확인합니다.
sudo ls /nginx
- 서비스 상태 확인
- docker service ls # 결과: # ID NAME MODE REPLICAS IMAGE PORTS # td06z2fsyznh nginxtest_nginx replicated 3/3 nginx:latest
- 서비스 세부 정보 확인
- docker service inspect nginxtest_nginx # 출력 예시: # "Mounts": [ # { # "Type": "volume", # "Source": "nginxtest_nginx_vol", # "Target": "/usr/share/nginx/html", # "VolumeOptions": { # "DriverConfig": { # "Name": "local", # "Options": { # "device": ":/nginx", # "o": "addr=211.183.3.200,rw", # "type": "nfs4" # } # } # } # } # ]
- Docker 볼륨 정보 확인
- nginxtest_nginx_vol 볼륨의 정보를 확인합니다.
docker volume inspect nginxtest_nginx_vol
# 출력 예시:
# "Driver": "local",
# "Options": {
# "device": ":/nginx",
# "o": "addr=211.183.3.200,rw",
# "type": "nfs4"
# }
Docker Stack 삭제 및 네트워크 추가 작업
- 기존 Stack 삭제:
- 기존에 배포한 nginxtest 스택을 삭제합니다.
docker stack rm nginxtest
- nginx.yml 파일 수정 (네트워크 추가):
- nginx_net 네트워크를 추가하여 서비스가 해당 네트워크에 연결되도록 수정합니다.
- nginx 서비스에 nginx_net 네트워크를 연결하고, 네트워크 드라이버로 overlay를 사용합니다. 또한, attachable: true 옵션을 설정하여 컨테이너가 해당 네트워크에 연결할 수 있게 합니다.
version: '3.8'
services:
nginx:
image: nginx:latest
deploy:
replicas: 3
placement:
constraints: [ node.role == worker] # 워커 노드에만 배포
volumes:
- nginx_vol:/usr/share/nginx/html # NFS 볼륨을 nginx HTML 디렉토리에 마운트
ports:
- "8888:80" # 포트 매핑
networks:
- nginx_net # 새로운 네트워크 추가
volumes:
nginx_vol:
driver: local
driver_opts:
type: "nfs4"
o: "addr=211.183.3.200,rw"
device: ":/nginx" # NFS 서버의 공유 디렉토리
networks:
nginx_net:
driver: overlay
attachable: true # 네트워크에 외부 컨테이너를 연결할 수 있게 설정
- 수정된 nginx.yml 파일로 Stack 재배포:
- docker stack deploy -c nginx.yml nginxtest
- Portainer 컨테이너 실행:
- nginxtest_nginx_net 네트워크에 연결된 Portainer 컨테이너를 실행합니다. Portainer는 Docker 환경을 관리할 수 있는 웹 UI를 제공합니다.
- 실행 명령어:
docker run -d -p 9000:9000 --network nginxtest_nginx_net --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:2.9.1
- Portainer 접속:
- Portainer 웹 UI에 접속하기 위해 브라우저에서 http://211.183.3.200:9000 주소로 접속합니다.