Docker-Compose와 Dockerfile을 활용한 Flask-MySQL 연동 예제

Page content

개요

  • Docker-Compose와 Dockerfile의 주요 기능을 이해한다.
  • 각 파일의 위치와 주요 기능을 이해한다.

전체 프로젝트 파일 디렉터리

  • 본 프로젝트의 전체 코드는 다음과 같다.
  • 실제 코드 작성을 해야하는 곳은 다음과 같다.
    • app.py
    • requirements.txt
    • init.sql
    • docker-compose.yml
    • Dockerfile
docker_kubernetes_flask/
├── app/
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── db/
│   ├── init.sql
│   └── data/ (This will be created by Docker)
├── docker-compose.yml
└── Dockerfile

사전준비

  • 사전에 Docker는 Desktop 설치가 되어 있다고 가정한다.
  • 코드 편집을 위해서는 Visual Studio Code를 활용한다.

Docker가 익숙하지 않은 사람들을 위한 1줄 요약

  • MySQL 설치하고, Python 설치하고, 두개 또 연동해야 하고, CLI 명령어 또 각각 입력하는거 다 자동화 해줄게요!!
  • 즉, 자동화에 익숙해지자!

docker-compose와 Dockerfile 간단 비교

  • docker-compose.yml : python 컨테이너와 mysql 컨테이너를 각각 한꺼번에 구성하도록 스크립트를 작성함
  • Dockerfile : 여기에서는 python 개발환경을 구성함
  • docker-compose.yml에서 Dockerfile을 호출하여 개발환경을 만들도록 지시할 수 있음

전체 코드 흐름 1줄 요약

  • From MySQL to Python Flask

init.sql과 app.py 간단 설명

  • 각 두개의 파일은 사전에 미리 작성을 해둔다.

MySQL : init.sql

  • SQL 코드는 데이터베이스와 테이블을 생성하고, 테이블에 데이터를 삽입하는 작업을 수행.
    • test_db 데이터베이스 생성
    • users 테이블 생성
    • 간단하게 이름 생성
CREATE DATABASE IF NOT EXISTS test_db;

USE test_db;

CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);

INSERT INTO users (name) VALUES ('Evan');
INSERT INTO users (name) VALUES ('Sara');
INSERT INTO users (name) VALUES ('Lotto');

Python : app.py

  • Python 코드는 Flask 웹 애플리케이션을 설정하여 MySQL 데이터베이스에 연결하고, 사용자 데이터를 JSON 형식으로 반환하는 작업을 수행.
from flask import Flask, jsonify
import mysql.connector
import os

app = Flask(__name__)

def get_db_connection():
    connection = mysql.connector.connect(
        host='mysql',
        user='root',
        password='example',
        database='test_db'
    )
    return connection

@app.route('/')
def index():
    connection = get_db_connection()
    cursor = connection.cursor()
    cursor.execute('SELECT * FROM users')
    users = cursor.fetchall()
    cursor.close()
    connection.close()
    users_list = [{"id": user[0], "name": user[1]} for user in users]
    return jsonify(users_list)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
  • 좀더 구체적으로 살펴본다.

step 01 - 라이브러리 불러오기

  • Flask: Flask 웹 애플리케이션 프레임워크를 불러오기
  • jsonify: 데이터를 JSON 형식으로 변환하여 HTTP 응답으로 반환하는 데 사용
  • mysql.connector: MySQL 데이터베이스에 연결하기 위해 사용
from flask import Flask, jsonify
import mysql.connector
import os

step 02 - Flask Application Setup

  • Flask 애플리케이션 인스턴스를 생성. __name__은 현재 모듈의 이름을 전달하여 Flask 애플리케이션을 생성하는 데 사용.
app = Flask(__name__)

step 03 - Database Connection Function

  • get_db_connection 함수는 MySQL 데이터베이스에 연결하고, 연결 객체를 반환.
  • host, user, password, database 매개변수는 데이터베이스에 연결하기 위한 정보
def get_db_connection():
    connection = mysql.connector.connect(
        host='mysql',
        user='root',
        password='example',
        database='test_db'
    )
    return connection

step 04 - Index Route

  • @app.route('/'): 해당 Decorator는 URL (’/’)에 대한 요청을 처리하는 index 함수를 정의.
  • index 함수의 내용은 다음과 같이 구성됨
    • get_db_connection을 호출하여 데이터베이스에 연결.
    • 연결 객체에서 커서를 생성하고, SELECT * FROM users 쿼리를 실행하여 users 테이블의 모든 데이터를 가져오기
    • 데이터를 가져온 후 커서와 연결을 닫기
    • users 데이터를 List Comprehension을 사용하여 딕셔너리 형태로 변환합니다. 각 사용자에 대해 idname 키를 가지는 딕셔너리를 생성.
      • 이 부분은 별도의 HTML 소스코드를 넣지 않기 위해서 진행한 것이니, 해당 자세한 내용을 보기를 원한다면 Flask 웹개발로 더 공부할 것 권장
    • 변환된 리스트를 jsonify를 사용하여 JSON 형식으로 반환.
@app.route('/')
def index():
    connection = get_db_connection()
    cursor = connection.cursor()
    cursor.execute('SELECT * FROM users')
    users = cursor.fetchall()
    cursor.close()
    connection.close()
    users_list = [{"id": user[0], "name": user[1]} for user in users]
    return jsonify(users_list)

step 05 - Running the Application

  • 모듈이 직접 실행될 때만 Flask 애플리케이션을 실행
    • app.run 메소드를 호출하여 애플리케이션을 시작.
    • host='0.0.0.0': 애플리케이션이 모든 네트워크 인터페이스에서 접근 가능하도록 설정.
    • port=5000: 애플리케이션이 5000번 포트에서 실행되도록 설정.
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Python : requirements.txt

  • 주요 라이브러리 설치 위해 필요한 라이브러리 2개만 설치
  • 추후에 독자가 라이브러리 추가 가능
Flask
mysql-connector-python

Docker: Dockerfile

  • Dockerfile은 Python 애플리케이션을 컨테이너화하기 위한 스크립트
  • 다른 파일과 달리 확장자명이 없다는 것에 주의
  • 다양한 옵션에 대해 설명하도록 한다.
FROM python:3.10-slim

WORKDIR /app

COPY app/requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app/ .

EXPOSE 5000

CMD ["python", "app.py"]

step 01 - FROM

FROM python:3.10-slim
  • Base Image를 선택한다. 해당 이미지를 선택하려면 Docker Hub 검색창에서 확인 (Login 필수)
  • 각 Base Image를 선택하면 관련 Tag가 존재하며 여기에서 Tags 확인해서 입력하도록 한다.
  • Docker Official Image 를 선택한다.

Screenshot 2024-07-01 at 8.16.09 AM.png

Step 02 - WORKDIR

WORKDIR /app
  • 생성될 컨테이너의 작업공간을 지정하는 것이다. (독자의 PC 폴더가 아님)
  • WORKDIR는 컨테이너 내부의 작업 디렉토리를 설정하는 명령어.
    • 즉, WORKDIR /app는 컨테이너 내부에 /app 디렉토리를 생성하고 그 디렉터리를 작업 디렉터리로 설정하는 것.
  • 이 디렉터리 내에서 이후의 COPY, RUN, CMD 명령들이 실행됨.

Step 03 - COPY

COPY app/requirements.txt .
  • Local-PC의 app/requirements.txt 파일을 컨테이너의 현재 작업 디렉토리 (/app)로 복사함.
    • 이 때 app 폴더는 Dockerfile과 같은 경로에 있는 것을 의미

Step 04 - RUN

RUN pip install --no-cache-dir -r requirements.txt
  • requirements.txt 파일에 명시된 Python 패키지들을 설치
  • -no-cache-dir 옵션은 pip가 패키지 캐시를 저장하지 않도록 함

Step 05 - COPY

COPY app/ .
  • Local-PC 프로젝트 폴더 내 app/ 디렉터리의 모든 파일을 컨테이너의 작업 디렉터리 (/app)로 복사.
  • . 은 모든 파일을 의미.

Step 06 - EXPOSE

EXPOSE 5000
  • EXPOSE는 Dockerfile 내에서 컨테이너가 지정된 네트워크 포트를 통해 접속할 것임을 Docker에 알리는 역할. 컨테이너가 5000 포트를 외부에 노출하도록 설정. 보통 Flask 같은 웹 애플리케이션에서 사용.
  • EXPOSE는 실제로 포트를 공개하거나 외부와의 연결을 설정하지 않는다. 단지, 그 포트가 컨테이너 내에서 사용된다는 것을 알리는 역할을 한다.

Step 07 - CMD

CMD ["python", "app.py"]
  • CMD ["python", "app.py"]는 Dockerfile에서 컨테이너가 시작될 때 실행할 명령을 정의.
  • 이를 통해 컨테이너가 시작되면 python app.py 명령이 실행되어 Python 애플리케이션이 실행됨.
  • CMD 명령어는 Dockerfile 내에서 단 하나만 사용될 수 있음.

Docker: docker-compose.yml

  • 이 Docker Compose 파일은 Flask 애플리케이션과 MySQL 데이터베이스를 함께 구성하여, 서로 독립적인 컨테이너로 실행되지만 네트워크를 통해 상호작용할 수 있게 함.
  • Flask 애플리케이션은 MySQL 데이터베이스에 연동.
  • MySQL 데이터베이스는 초기화 SQL 스크립트를 사용하여 설정.
  • 호스트의 포트 5000은 Flask 애플리케이션으로 포워딩되어 외부 접근이 가능.
version: '3.8'

services:
  flask:
    build: .
    volumes:
      - ./app:/app
    networks:
      - flask-mysql-network
    ports:
      - "5000:5000"
    depends_on:
      - mysql

  mysql:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - flask-mysql-network
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: test_db

volumes:
  mysql-data:

networks:
  flask-mysql-network:

Step 00 - version

  • 각 버전은 서로 다른 기능을 지원하며, 일부 오래된 버전은 최신 버전에서 제공하는 특정 기능을 지원하지 않을 수 있음.
  • 버전 사용 예시
    • version 1 : Docker Compose의 초기 버전, 최상위 version or services 키가 없는 평면 구조 사용
    • version 2.x : version키와 services 키와 같은 새로운 구문과 기능 도입
      • 서비스 간의 의존성, 네트워크 및 볼륨과 같은 더 복잡한 구성 허용
    • version 3.x : 스웜 모드를 목표로 하는 더 많은 기능을 추가하여 확장 및 분산 애플리케이션을 더 잘 지원. 일반적으로 배포를 관리하기 위한 배포 구성 개념 도입
    • version 3.8 : 3.x 시리즈의 최신 버전

Step 01 - services

  • services는 여러 개의 컨테이너를 정의하고 구성하는 기본 단위임.
  • 각 서비스는 Docker 컨테이너를 정의하며, 해당 컨테이너의 이미지, 실행 명령, 네트워크 설정, 볼륨 마운트 등의 구성을 포함할 수 있습니다.
  • services 의 주요 개념과 구성은 다음과 같음.
    • 서비스 정의 : 각 서비스는 특정한 역할을 하는 하나의 컨테이너 정의
    • 컨테이너 설정 : 서비스 내에서 컨테이너의 이미지, 환경 변수, 포트 매핑, 볼륨 마운트 ,네트워크 설정 등 지정
    • 의존성 설정 : depends_on 옵션 사용하여 서비스 간의 시작 순서 정의할 수 있음. 일반적으로 웹 서비스가 실행되기 전에 DB 서비스가 시작된 후에 실행되도록 설정 가능
    • 네트워크 설정 : 서비스 간의 네트워크 정의하여 서로 통신할 수 있도록 설정
    • 볼륨 설정 : 각 서비스에 필요한 데이터를 영구적으로 저장하기 위해 볼륨을 정의하고 마운트
  • 위 기본 개념을 통해서 다음 코드를 설명해보도록 한다.
services:
  flask:
    build: .
    volumes:
      - ./app:/app
    networks:
      - flask-mysql-network
    ports:
      - "5000:5000"
    depends_on:
      - mysql
      
   mysql:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - flask-mysql-network
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: test_db
  • 위 코드는 크게 2개의 서비스로 구성되어 있다.
    • flask 컨테이너
    • mysql 컨테이너
  • 먼저 flask 컨테이너에 대해 설명하면 다음과 같다.
    • build: .: 현재 디렉토리에서 Dockerfile을 사용하여 이미지를 빌드.
    • volumes: - ./app:/app: 호스트(=Local PC)의 ./app 디렉토리를 컨테이너의 /app 디렉토리에 마운트합니다. 이를 통해 호스트 파일 시스템의 변경 사항이 즉시 컨테이너에 반영됨.
    • networks: - flask-mysql-network: flask-mysql-network라는 네트워크를 사용하여 MySQL 서비스와 통신.
    • ports: - "5000:5000": 호스트의 포트 5000을 컨테이너의 포트 5000에 매핑. 이를 통해 호스트의 5000 포트로 접근하면 Flask 애플리케이션에 접근할 수 있음.
    • depends_on: - mysql: Flask 서비스는 mysql 서비스가 먼저 시작된 후에 시작됨.
services:
  flask:
    build: .
    volumes:
      - ./app:/app
    networks:
      - flask-mysql-network
    ports:
      - "5000:5000"
    depends_on:
      - mysql
  • 이번에는 MySQL 컨테이너에 대해 설명하면 다음과 같다.
    • image: mysql:8.0: MySQL 8.0 이미지를 사용하여 컨테이너를 생성.
    • command: --default-authentication-plugin=mysql_native_password: MySQL 서버를 시작할 때 기본 인증 플러그인을 mysql_native_password로 설정.
    • volumes:
      • mysql-data:/var/lib/mysql: mysql-data 볼륨을 MySQL 데이터베이스 파일이 저장될 컨테이너의 /var/lib/mysql 디렉토리에 마운트합니다.
      • ./db/init.sql:/docker-entrypoint-initdb.d/init.sql: 호스트의 ./db/init.sql 파일을 컨테이너의 /docker-entrypoint-initdb.d/init.sql에 마운트하여 초기화 SQL 스크립트를 실행.
services:
  flask:
    ...
  mysql:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - flask-mysql-network
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: test_db

Step 03 - Volume

  • mysql-data라는 이름의 Docker 볼륨을 정의. 이 볼륨은 MySQL 데이터베이스 데이터를 영구적으로 저장하는 데 사용됨.
  • volume은 컨테이너의 파일 시스템과 호스트의 파일 시스템 또는 Docker가 관리하는 외부 스토리지 사이에 데이터를 공유하는 데 사용.
  • 이는 데이터의 영구 저장을 가능하게 하고, 컨테이너가 재시작되거나 삭제되어도 데이터가 유지되도록 함.
  • 주요 기능은 다음과 같다.
    • 데이터 지속성 : 컨테이너가 삭제되거나 재시작되더라도 데이터는 유지. 데이터베이스나 로그 파일과 같은 중요한 데이터를 영구적으로 저장
    • 개발 편의성 : 소스 코드를 호스트에서 컨테이너로 마운트하여, 개발 중에 소스 코드 변경 사항이 즉시 반영
    • 데이터 공유 : 여러 컨테이너 간에 데이터를 공유, 동일한 볼륨을 사용하는 웹 서버와 데이터베이스 서버가 데이터를 공유
volumes:
  mysql-data:

Step 04 - Networks

  • networks는 여러 컨테이너 간의 네트워크를 정의하고 관리하는 데 사용.
  • 네트워크는 컨테이너 간의 통신을 가능하게 하며, 격리된 네트워크 환경을 제공하여 보안과 네트워크 구성의 유연성을 향상시킴.
  • 주요 기능은 다음과 같다.
    • 컨테이너 간 통신 : 같은 네트워크에 있는 컨테이너들은 서로의 서비스 이름을 호스트 이름으로 사용하여 통신할 수 있음 . (예시 web 서비스는 db 서비스를 db라는 호스트 이름으로 접근)
    • 네트워크 격리 : 각 서비스에 대해 별도의 네트워크를 정의하여 서비스 간의 통신을 제한하고 보안을 강화. 여러 네트워크를 사용하여 서비스 그룹 간의 트래픽을 분리.
    • 유연한 네트워크 설정 : 사용자 정의 네트워크를 생성하고, 네트워크 드라이버를 지정하여 다양한 네트워크 토폴로지를 구성할 수 있음. 브리지 네트워크, 오버레이 네트워크 등 다양한 네트워크 드라이버를 지원.
  • flask-mysql-network라는 사용자 정의 네트워크를 정의하여 Flask와 MySQL 서비스가 서로 통신할 수 있도록 함.
networks:
  flask-mysql-network:

Test

  • 테스트 확인
docker-compose up

Screenshot 2024-07-02 at 12.44.53 PM.png

  • 만약 Mac 사용자 중 Error가 발생이 되면 AirPlay Receiver를 해제 한다.

    Screenshot 2024-07-02 at 12.45.46 PM.png