Django 프로젝트 K8S 배포하기

Seokbin·2021년 5월 30일
3

Kubernetes

목록 보기
2/3


Django를 이용해 구현한 웹사이트가 어느 정도 완성되었습니다. 평소 구현이 완료 된 코드를 AWS EC2에서 실행시켜 접근 하였지만, 현재 제가 테스트용으로 사용하는 쿠버네티스 클러스터가 있어서 프로젝트를 쿠버네티스에서 배포 하고 싶은 마음이 생겨 시도 해 보고 삽질한 과정을 기록해봅니다. 잘못된 점이나 부족한 점을 지적해주시면 감사하겠습니다.


Overview
Django 프로젝트를 배포하기 위해 쿠버네티스에서 크게 다음과 같은 두 개의 파드가 생성됩니다. Nginx는 웹서버의 역할을 맡아 클라이언트와의 정적파일을 통신하는 역할로 사용됩니다. Django는 Gunicorn 이라는 WSGI를 통해 실행되면서 웹서버(Nginx)와 소통 할 수 있게 합니다. DB서버로는 Mariadb를 사용하였습니다. 다음과 같은 세개의 파드를 이용해 배포를 진행 한 후 서비스를 생성해 외부에서 접근이 가능하도록 진행하겠습니다.

  • Nginx,Django
  • Mariadb

1. Django config 설정
Django 배포를 진행하기 위해 몇 가지 설정을 바꿔야 합니다. settings 파일에서 외부로의 노출이 불필요한 정보는 환경변수를 통해 설정합니다. env파일을 따로 만들어 정보를 한 곳에 모아놓았습니다. 이 파일은 후에 Kubernetes의 secret으로 생성합니다. 또한 Static 파일은 collectstatic 명령어를 실행하였을 때 코드의 최상단에 모이도록 설정해 nginx에서 마운트 할 수 있도록 설정 했습니다.

#settings.py
DEBUG = False
DATABASES = {
    'default': {
        'ENGINE': os.getenv('DATABASE_ENGINE'),
        'NAME': os.getenv('DATABASE_NAME'),
        'USER': os.getenv('DATABASE_USERNAME'),
        'PASSWORD': os.getenv('DATABASE_PASSWORD'),
        'HOST': os.getenv('DATABASE_HOST'),
        'PORT': os.getenv('DATABASE_PORT')
    }
}
STATIC_ROOT = "/code/.static_root"

이후 settings.py와 같은 레벨에 nginx 폴더를 만들고 nginx.conf를 다음과 같이 만들 었습니다. upstream은 Django container 명으로 설정 합니다. server 블록은 해당 url로 들어 왔을 때 매핑되는 룰입니다. 이 설정은 후에 파드에 생성 할 때 변경합니다.

upstream devcloud {
    ip_hash;
    server devcloud:8000;
}

server {
    location / {
        proxy_pass http://devcloud/;
  }

    location /static/ {
        alias /static/;
    }
    listen 80;
    server_name localhost;
}

2.Django image 만들기
위의 설정을 변경 후 Django image를 생성해 파드에 사용합니다. 아래와 같이 간단하게 도커파일을 만듭니다. code라는 워킹 디렉터리를 생성한 후 Django 프로젝트를 실행하는데 필요한 라이브러리를 설치 합니다. 이후 코드를 모두 code 디렉토리로 복사 합니다.

#Dockerfile
FROM python:3
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/

이후 docker build —network host -t [repository:v1] . 명령어로 빌드를 실행하였습니다. —network 옵션의 경우 현재 쿠버네티스 네트워크와 겹쳐서 강제로 지정해 줬습니다. 빌드가 완료되면 아래 화면과 같이 이미지가 생성된 것을 확인 할 수 있습니다.
이미지가 정상적으로 빌드 되었는지 확인하게 위해 접속해 명령어를 수행해봅니다.docker run -it --name devcloud --env-file env devcloud:v2 /bin/bash 명령어를 통해 컨테이너에 접속해 python3 manage.py collectstatic 과 /code/.static_root 디렉터리에 static file이 생기게 됩니다.또한 python3 manage.py runserver를 수행하면 Django가 실행 되는 것을 확인 할 수 있습니다. 정상적으로 실행이 된다면 이후에 이 이미지를 바탕으로 K8S Pod를 생성하겠습니다.


3.Mariadb Pod 생성
우선 DB용 MariaDB Pod를 생성합니다. 파드가 삭제되어도 데이터가 사라지지 않게 DB용 PV와 이를 연결하는 PVC를 생성합니다. 이후 Mariadb의 패스워드 정보가 들어있는 Secret을 생성해 파드에 연결합니다. 전체 매니페스트는 다음과 같습니다.

apiVersion: v1
kind: Pod
metadata:
  name: mariadb
  labels:
    app: mariadb
spec:
  containers:
    - name: mariadb
      image: mariadb
      imagePullPolicy: IfNotPresent
      envFrom:
          - secretRef:
              name: mariadb-secret
      ports:
        - containerPort: 3306
      volumeMounts:
        - mountPath: /var/lib/mysql
          name: mariadb-pv-volume
  restartPolicy: Always
  volumes:
    - name: mariadb-pv-volume
      persistentVolumeClaim:
        claimName: mariadb-pv-claim

또한 Service의 한 종류인 ClusterIP를 생성해 MariaDB와 연결했습니다. Pod의 IP는 재시작될 때마다 바뀌기 때문에 고정된 IP를 사용해야 편합니다. DB는 클러스터 내부의 파드간에만 통신하기 때문에 ClusterIP로 생성했습니다.

mariadb-svc         ClusterIP   10.233.48.91   <none>        3306/TCP       8h 

다음과 같은 cluster IP 가 생겼습니다. 클러스터 내에서 서비스의 IP 주소를 통해 데이터베이스에 접근할 수 있게됩니다.


3.Django Pod 생성
위에서 생성한 DB와 연결해 Django Pod를 생성합니다.아래의 매니페스트를 통해 생성합니다.

apiVersion: v1
kind: Pod
metadata:
  name: devcloud-admin
  labels:
    app: devcloud-admin
spec:
  containers:
    - name: devcloud-django
      image: 211.214.88.173:5000/devcloud:v2
      imagePullPolicy: IfNotPresent
      envFrom:
          - secretRef:
              name: devcloud-admin-secret
          - configMapRef:
              name: devcloud-admin-configmap
      ports:
        - containerPort: 8000
      volumeMounts:
        - mountPath: /code/.static_root
          name: devcloud-admin-volume
      command: ["/bin/bash"]
      args: ["-c", "python3 manage.py makemigrations && python3 manage.py migrate
      && python3 manage.py collectstatic --no-input && gunicorn --bind 0.0.0.0:8000 config.wsgi"]
  imagePullSecrets:
    - name: nexus-secrets
  restartPolicy: Always
  nodeSelector:
    hw: cpu
  volumes:
    - name: devcloud-admin-volume
      hostPath:
        path: /home/seokbin/static-admin

Django에서 사용하는 env파일은 보안 정도에 따라 secret(PW,SECRET KEY 등)과 configmap(DEBUG, ALLOWED HOST등 )으로 분류해서 설정했습니다. kubectl create secret generic devcloud-admin-secret --from-env-file=env 과 같은 커맨드로 env파일을 secret으로 편하게 전환 할 수 있습니다. 이미지는 제일 처음에 만든 이미지를 사용합니다. 저는 그 해당 이미지를 Nexus에 업로드 후 사용합니다. 이미지가 생성 된 후 command에 작성한 명령어를 수행합니다. collectstatic 명령어를 수행한 후 생성되는 스태틱 파일을 nginx에서 접근 할 수 있도록 볼륨을 만들었습니다. 최종적으로 gunicorn을 통해 django를 실행하게 됩니다. Pod를 생성 후 로그를 확인하면 정상적으로 실행 된 것을 볼 수 있습니다.하지만 현재 상태에서는 외부에서 접근이 불가능 하고 로컬에서 접속해도 static파일이 적용이 안되서 css,js 등 적용 되지 않습니다. 이를 해결하기 위해 nginx container를 추가로 생성합니다. 위의 매니페스트에서 다음과 같이 추가합니다.

containers:
- name: devcloud-admin-nginx
      image: nginx
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: /static
          name: nginx-admin-static
        - mountPath: /etc/nginx/conf.d
          name: nginx-admin-config
      ports:
        - containerPort: 80
      --생략---
        volumes:
    - name: devcloud-admin-volume
      hostPath:
        path: /home/seokbin/static-admin
    - name: nginx-admin-static
      hostPath:
        path: /home/seokbin/static-admin
    - name: nginx-admin-config
      hostPath:
        path: /home/seokbin/nginx/admin-config

볼륨을 생성해 Django에서 collecstatic 명령어를 통해 생성한 static 파일을 접근 할 수 있도록 설정하고 nginx 설정 파일을 호스트에서 접근 할 수 있도록 마운트 했습니다. nginx config는 다음과 같습니다. 파드 내의 컨테이너는 같은 IP를 공유하기 때문에 localhost로 접근이 가능합니다. 이를 통해 [Pod IP]:80번으로 접근 할 수 있게 됩니다.

upstream devcloud-admin {
   ip_hash;
   server localhost:8000;
}

server {
   location / {
       proxy_pass http://devcloud-admin/;
 }

   location /static/ {
       alias /static-admin/;
   }
   listen 80;

4.외부로 노출하기
지금까지의 설정으로는 클러스터 내부에서만 웹에 접근이 가능합니다. 외부에서 접근을 허용하기 위한 서비스를 설정해야합니다. 보통 로드밸런서나 ingress를 통해서 외부에서 접근을 가능하도록 하지만 사용 할 수 있는 공용 IP가 존재하지 않아 NodePort로 테스트를 진행하였습니다. 아래의 매니페스트로 NodePort 서비스를 생성하였습니다.

apiVersion: v1
kind: Service
metadata:
  name: devcloud-admin-nodeport
spec:
  selector:
    app: devcloud-admin
  ports:
    - port: 80
  type: NodePort

노드포트를 생성하면 쿠버네티스에서 지정된 범위내의 포트를 랜덤으로 사용합니다. 현재 저는 32720 포트가 열렸고 [서버IP:32720]로 외부에서 접근하면 [PodIP:80]로 접속해 외부에서 접근이 가능하게 됩니다. 전체적인 구조를 도식화 하면 다음과 같습니다.

0개의 댓글