안녕하세요. 저는 코들을 개발하고 있는 백엔드 개발자 류호선입니다. 저는 팀모노리스에서 처음으로 웹개발을 시작했습니다. 프로덕트를 개발하며 팀원들과 같이 문제들을 해결하는 과정에서 정말 많이 배우며 성장하고 있습니다. 이번에 제가 소개할 이야기는 팀모노리스에서 코들을 개발하면서 생겼던 문제와 그 문제를 어떤식으로 해결했는지에 대한 것입니다.

쿠버네티스

저희는 코들을 개발하며 인프라를 구성하기 위해서 쿠버네티스라는 기술을 사용하고 있습니다. 공식 문서에 따르면, 쿠버네티스“컨테이너화된 워크로드와 서비스를 관리하기 위한 이식성이 있고, 확장가능한 오픈소스 플랫폼” 입니다. 이를 이용한다면 확장성 있는 분산 시스템을 쉽게 구성할 수 있는 것입니다.

특히 저희는 파이썬 코딩 환경을 만들기 위해서 JupyterHub를 사용하고 있는데요, 이 또한 Zero to Jupyter Hub 라는 프로젝트를 이용한다면 쿠버네티스를 통해 쉽게 구성할 수 있습니다. 이런 강력한 도구가 있었기에 적은 개발비용으로도 이런 멋진 프로덕트를 만들어 낼 수 있는 것 같습니다.

새로운 기능: 배포 폴더

저희가 이번에 추가하고자 했던 기능은 바로 배포 폴더입니다. 배포 폴더란 간단히 말해서 선생님이 학생들에게 수업 자료를 전달할 수 있는 공간입니다. 선생님이 배포폴더에 수업자료 파일을 넣어두면 학생들도 배포폴더를 통해서 이 파일에 접근할 수 있는 것입니다. 그렇다면 이 기능에는 어떤 조건들이 필요할까요?

  1. 실시간 반영

    먼저 선생님이 공간에 자료를 올리면 학생들에게 바로 반영되면 좋겠습니다. 이는 마운트를 통해서 쉽게 해결을 할 수 있는데요, 같은 디렉토리를 선생님과 학생의 파드에 마운트를 하면 됩니다. 마치 하나의 방을 여러 문을 통해 드나들 수 있는 것처럼 말이죠.

  2. 학생들은 수정 불가

    그런데 이 공간에 있는 파일을 학생이 마음대로 수정할 수 있다면 어떻게 될까요? 가령 한 학생이 선생님이 올려둔 수업 자료를 수정해버린다면, 다른 학생들은 선생님이 의도한 학습 효과를 보기 힘들 것입니다. 따라서 우리는 선생님은 수정할 수 있게, 학생들은 읽기만 가능하도록 하고 싶습니다.

선생님과 학생이 같은 디렉토리를 마운트하는 구조

선생님과 학생이 같은 디렉토리를 마운트하는 구조

종합적으로 우리는 같은 디렉토리를 선생님에겐 writable하게, 학생들에겐 read-only로 마운트를 시키고 싶습니다. 그리고 저희는 이미 writable한 볼륨을 파드에 마운트해서 사용하고 있기 때문에, 우리의 과제는 read-only 볼륨을 마운트 하는 것이 될 것입니다.

직면한 문제

이를 위해서 간단하게 기존에 파드의 볼륨으로 사용하고 있는 PVC를 이용하여 새로운 read-only 볼륨을 정의하고 이를 kubespawner에 넘겨주어서 동적으로 마운트 하기로 하였습니다. 이를 코드로 구현하면 다음과 같습니다. (kubespawner에 대한 설명은 뒤에 자세히 나옵니다)

def read_only_volume(spawner):
    spawner.volumes.append(
        {
            "name": "read-only",
            "persistentVolumeClaim": {
                "claimName": ... ,
                "readOnly": True,
            }
        }
    )
    return spawner.volumes

def mount_student_volume(spawner):
    ...
    for 교실과 이에 속한 학생들에 대해서:
        ...
        spawner.volume_mounts.append(
            {
                "mountPath": ... ,
                "name": "read-only",
                "subPath": ... ,
            }
        )
		...
    return spawner.volume_mounts

...

# 위에서 정의한 콜백 함수들을 넘겨주어서 동적으로 정의합니다.
for profile in c.KubeSpawner.profile_list:
		profile['kubespawner_override']['volumes'] = read_only_volume
    profile['kubespawner_override']['volume_mounts'] = mount_student_volume

이를 개발환경에서 시험하는데, 이 때 문제가 생겼습니다. 바로 서버가 스폰이 되지 않는 현상이 발생하였는데요, 그 이유는 에러 메시지에서 찾을 수 있었습니다.

Untitled

<aside> ⚠️ [Warning] Unable to attach or mount volumes: …

</aside>

볼륨이 제대로 마운트되지 않는 문제가 생겼습니다. 우리는 어떻게든 목표를 달성하기 위해서 이를 파헤치기 시작했습니다. 마운트가 되지 않는 이유를 밝혀내기 위해 여러 가설을 세우고 이를 검증하기 위해서 실험들을 진행하였습니다.

1. readOnly: true로 마운트가 불가능하다

이를 검증하기 위해서 저희는 새로 정의한 볼륨을 writable하게 수정하였습니다. 이를 위해선 단순히 볼륨의 정의에서 readOnly: False로 설정하면 됩니다.