도커 네트워크와 docker-compose를 이용한 2개의 컨테이너 연동
- Docker: 컨테이너 기반의 가상화 도구
- Image
- Container
- Volume
- Network
- 이미지를 만드는 방법
- 현재 컨테이너를 commit 이라는 이미지로 만들기
- Dockerfile에 적어서 만들기
- 이미지를 올려놓는 방법
- Docker Hub (퍼블릭하게 저장)
- Private Image Registry (로컬에서 이미지 저장소를 직접 운영)
- Network, docker-compose, docker-swarm, kubernetes ⇒ 컨테이너 2개 이상: 컨테이너 오케스트레이션
python application, redis db 한 컴퓨터에 설치 → 하나가 고장나면 다른 하나도 멈춤 → 무중단 x
python application과 redis db를 각각 다른 컴퓨터에 설치해서 사용하기 시작 → 자원의 낭비와 확장과 축소 힘듦
한 컴퓨터에 가상환경을 여러 개 두고 각 가상환경에 python application과 redis db를 설치 → 자원의 낭비 문제 해결 but 로드밸런싱 힘듦 문제
구조 장점 단점 적합 상황
한 서버에 모든 구성요소 설치 | 단순, 저비용 | 장애 시 전체 다운 | 개인 실습, 소규모 |
서로 다른 컴퓨터에 배포 | 장애 격리, 확장성 일부 확보 | 자원 낭비, 운영 복잡 | 중규모 서비스 |
하나의 서버에 여러 가상환경 구성 | 자원 효율, 관리 유연성 | 확장성/고가용성 한계 | 테스트, CI 환경 |
클러스터/Kubernetes 사용 | 확장성, 무중단, 자동 복구 | 복잡한 설정, 초기 러닝 커브 | 운영 서비스, 대규모 |
- docker-compose는 로드밸런싱까지는 못 함, 수동으로 해줘야 한다.
- Conatainer는 Local Machine에 종속되지만 다른 Container와 격리되어 있다. (다른 컴퓨터처럼 취급된다.)
- 네트워크가 왜 필요한지
- 어제 실습 환경 review
docker engine에다 redis(IP)를 만들었고 로컬에 python application(포트)을 만들었다.
docker engine은 기본적으로 다 막혀있어서 python app이 redis에 접근하기 위해서는 docker engine이 열어줘야 한다. by 포트포워딩 → redis의 ip:port(6379), 컴퓨터의 ip:port(6379) (어차피 도커 엔진과 컨테이너는 컴퓨터의 ip 아니까 포트만 적어줘도 된다.)
Redis IP:6379 ↔ localhost:6379 로 소통했었다.
근데 python application은 로컬에 있으니까 항상 그 위치에 가서 python app.py를 돌려야 한다. ⇒ 번거롭다. → Dockerfile로 이미지 만들어서 컨테이너로 실행
But 컨테이너도 자신이 컴퓨터라고 생각한다. 따라서 python application에서 localhost라고 쓰면 자기 컨테이너로 들어간다. redis로 들어가는 게 아니라.
⇒ 해결 방법: 어차피 내부에 있으니까 내부 네트워크로 연결해주자 (네트워크를 만들어준다 = 너랑 나랑은 같은 곳이야. (밖에서 subneting으로 해주던 것과 똑같다.))
컨테이너끼리 통신
Python의 플라스크로 웹 페이지를 출력하고 redis의 인메모리 데이터베이스에서 웹 페이지 액세스 카운트를 캐시해서 페이지에 출력하려고 한다.
- 레디스 컨테이너 생성 방법
docker run --name myredis -d -p 6379:6379 redis
# 확인
docker ps
- 파이썬 flask 애플리케이션 생성(가상 환경을 만들어서 수행: 모든 패키지가 requirements에 기록되는 것을 방지) (현재 위치: kyla@master:~/docker/web$)
python3 -m venv ./myvenv
# 활성화
source myvenv/bin/activate
- 필요한 패키지 설치
pip install flask
pip install redis
- 파이썬 패키지 의존성을 파일로 전송
pip freeze > requirements.txt
- 플라스크를 이용한 웹 페이지 생성 - vi app.py
import time
import redis
from flask import Flask
app = Flask(__name__)
def web_hit_cnt():
with redis.StrictRedis(host='localhost', port=6379) as conn:
return conn.incr("hits")
@app.route("/")
def hello():
cnt = web_hit_cnt()
return "<p>Web Access Count: {} times</p>".format(cnt)
if __name__ == "__main__":
# 0.0.0.0 : 모든 대역의 사람들이 들어올 수 있게
app.run(host="0.0.0.0", port=9000, debug=True)
- 실행
python app.py
# 포트포워딩 했는데도 안되면 ufw allow 9000 해주기
- python 애플리케이션을 이미지로 만들 수 있는 Dockerfile 생성 - vi Dockerfile
FROM python:3.10-slim
ENV LIBRARY_PATH=/lib:/usr/lib # 환경 변수 만드는 거라 이 3개 없어도 됨
ENV FLASK_APP=py_app
ENV FLASK_ENV=development
EXPOSE 9000 # 9000번 포트를 외부로 노출할 거야
WORKDIR /py_app
COPY . . # 현재 디렉토리에 있는 모든 자료를 workdir로 복사한다. (python은 이렇게 해줘야 한다.)
RUN pip install -r requirements.txt
# 실행하는 명령
ENTRYPOINT ["python"] # 주 명령어
CMD ["app.py"] # 매개변수
- 이미지 빌드
docker build -t flaskapp .
- 컨테이너 실행
docker run -dit -p 9000:9000 --name=flaskapp flaskapp
→ 컨테이너 실행 후 웹 브라우저에서 접속을 하면 에러가 발생
이유: 플라스크 애플리케이션이 컴퓨터에 직접 설치된 경우는 redis가 포트포워딩이 되어 있다면 localhost로 접속이 가능하지만 플라스크 애플리케이션이 도커 컨테이너가 되면 localhost는 컨테이너에 부여된 IP가 되므로 redis에 접속할 수 없어서 에러가 된다.
해결방법:
- app.py의 접속 IP를 수정해서 이미지를 다시 만들고 컨테이너로 배포
- 기존 플라스크 컨테이너는 중지하고 이미지와 함께 삭제 (redis 컨테이너는 중지하고 삭제할 필요 없음)
docker stop flaskapp
docker rm flaskapp
docker rmi flaskapp
- redis 접속 IP를 확인 (컨테이너의 IP 확인 가능)
docker inspect network bridge # "IPv4Address": "172.17.0.2/16"
- app.py 의 소스 코드에서 redis 접속 위치를 수정 (localhost에서 172.17.0.2로)
- 이미지를 다시 생성
docker build -t flaskapp .
- 컨테이너를 생성해서 접속
docker run -dit -p 9000:9000 --name=flaskapp flaskapp
- 확인
docker ps
→ 이번엔 제대로 나오는 것을 확인할 수 있다.
- 정리
docker stop flaskapp
docker rm flaskapp
docker rmi flaskapp
docker stop myredis
docker rm myredis
docker rmi redis
✅ 핵심 요약
상황 접근 방식 이유
로컬 Python → Docker Redis | localhost:6379 | Redis 포트를 호스트에 포워딩함 |
Docker Python → Docker Redis (다른 컨테이너) | ❌ localhost (오류 발생)✅ myredis | 컨테이너끼리는 localhost가 아님→ Docker 내부 네트워크에서 컨테이너 이름으로 접근 |
컨테이너끼리 소통 | Docker 네트워크 공유 + 컨테이너 이름 사용 | DNS처럼 작동하는 브리지 네트워크 기능 활용 |
도커 네트워크와 docker-compose를 이용한 2개의 컨테이너 연동
위의 작업을 docker-compose로 해보기
- 현재 디렉토리에 docker-compose.yml 파일 만들기
→ 각각을 쓰레드로 만들기 때문에 순서가 보장되지 않음 ⇒ 의존성으로 순서를 써줘서 flask는 Redis 컨테이너가 "시작 명령"을 받은 후에 실행되도록 해야 한다.version: '3' services: redis: # 컨테이너 이름 image: redis:latest ports: - 6379:6379 restart: always flask: build: . # 현재 디렉토리에 있는 Dockerfile을 기반으로 이미지 빌드를 하겠다 ports: - 9000:9000 restart: always depends_on: - redis
- app.py에서 ip를 적는게 아니라 컨테이너 이름을 적어주기
→ redis 접속 코드를 서비스 이름으로 접속하도록 수정
- docker-compose 실행
docker-compose up -d
- 컨테이너 잘 띄워졌는지 확인
docker ps
→ redis가 먼저 띄워진거 확인할 수 있다.
명령어
- build: Dockerfile을 이용한 빌드 또는 재빌드
- config: 내용 확인
- create: 서비스를 생성
- up: 컨테이너 서비스 생성하고 시작
- down: 서비스 중지
- events: 이벤트 정보 수신
- exec: 실행 중인 컨테이너에 명령을 실행
- logs: 로그 정보 출력
- restart: 서비스 재시작
- rm: 서비스 제거
- scale: 컨테이너 서비스에 대한 컨테이너 수 설정
- start: 서비스 시작
- stop: 서비스 중지
옵션
- d
- --build: 컨테이너를 실행하기 전에 이미지를 빌드
- --force-recreate: 변경된 내용이 없어도 컨테이너를 재생성
- --scale SERVICE=NUM: 컨테이너의 수를 변경
- --abort-on-container-exit: 컨테이너가 하나라도 종료되면 모든 컨테이너를 종료
- --remove-orphans: 컴포즈 파일에 정의되지 않은 서비스의 컨테이너를 종료
컨테이너 생성 (깨끗하기 위해 compose 디렉토리를 만들고 그 안에서 작업)
- vi docker-compose.yml
version: '3.8'
services:
server_web: # 컨테이너이름
image: httpd:2 # 아파치 웹 서버 가져오고 싶으면
server_db:
image: redis:latest
- 컨테이너 실행
docker-compose up -d
- 컨테이너 확인
docker ps
- 컨테이너 중지
docker-compose down
scale up
docker-compose up --scale server_db=3 --scale server_web=3 -d
→ 이미지는 있으니까 금방 만든다.
⇒ 일반 컨테이너로 생성했다고 하면 중지시키려면 명령어 6번 써야 한다. But, 지금은 docker-compose down 만 하면 된다.
docker-compose [-f 파일경로] down [옵션]
docker-compose 삭제
- rmi 종류: 이미지도 같이 삭제하는 것으로 all을 설정하면 모든 이미지가 삭제되고 local로 지정하면 커스텀 태그가 없는 이미지만 삭제
- -v: 기재된 볼륨 삭제
- --remove-orphans: 컴포즈 파일에 정의되지 않은 서비스의 컨테이너도 삭제
docker-compose logs -f
로그 확인
docker-compose config
yaml 파일 확인
- volume은 안 만들어 주는데 network는 만들어준다.