쿠버네티스에 읽기전용 볼륨 마운트하기
오늘은 팀모노리스의 개발자 류호선님의 개발 로그 공유드립니다. 원본은 저희 회사 사이트(노션)에 있습니다. (여기서 코드가 잘 표현이 안되네요!)
추가로 팀모노리스의 채용설명회가 다음주에 예정되어 있으니 많은 관심 부탁드립니다!
안녕하세요. 저는 코들을 개발하고 있는 백엔드 개발자 류호선입니다. 저는 팀모노리스에서 처음으로 웹개발을 시작했습니다. 프로덕트를 개발하며 팀원들과 같이 문제들을 해결하는 과정에서 정말 많이 배우며 성장하고 있습니다. 이번에 제가 소개할 이야기는 팀모노리스에서 코들을 개발하면서 생겼던 문제와 그 문제를 어떤식으로 해결했는지에 대한 것입니다.
쿠버네티스
저희는 코들을 개발하며 인프라를 구성하기 위해서 쿠버네티스라는 기술을 사용하고 있습니다. 공식 문서에 따르면, 쿠버네티스란 “컨테이너화된 워크로드와 서비스를 관리하기 위한 이식성이 있고, 확장가능한 오픈소스 플랫폼” 입니다. 이를 이용한다면 확장성 있는 분산 시스템을 쉽게 구성할 수 있는 것입니다.
특히 저희는 파이썬 코딩 환경을 만들기 위해서 JupyterHub를 사용하고 있는데요, 이 또한 Zero to Jupyter Hub 라는 프로젝트를 이용한다면 쿠버네티스를 통해 쉽게 구성할 수 있습니다. 이런 강력한 도구가 있었기에 적은 개발비용으로도 이런 멋진 프로덕트를 만들어 낼 수 있는 것 같습니다.
새로운 기능: 배포 폴더
저희가 이번에 추가하고자 했던 기능은 바로 배포 폴더입니다. 배포 폴더란 간단히 말해서 선생님이 학생들에게 수업 자료를 전달할 수 있는 공간입니다. 선생님이 배포폴더에 수업자료 파일을 넣어두면 학생들도 배포폴더를 통해서 이 파일에 접근할 수 있는 것입니다. 그렇다면 이 기능에는 어떤 조건들이 필요할까요?
- 실시간 반영
- 먼저 선생님이 공간에 자료를 올리면 학생들에게 바로 반영되면 좋겠습니다. 이는 마운트를 통해서 쉽게 해결을 할 수 있는데요, 같은 디렉토리를 선생님과 학생의 파드에 마운트를 하면 됩니다. 마치 하나의 방을 여러 문을 통해 드나들 수 있는 것처럼 말이죠.
- 학생들은 수정 불가
- 그런데 이 공간에 있는 파일을 학생이 마음대로 수정할 수 있다면 어떻게 될까요? 가령 한 학생이 선생님이 올려둔 수업 자료를 수정해버린다면, 다른 학생들은 선생님이 의도한 학습 효과를 보기 힘들 것입니다. 따라서 우리는 선생님은 수정할 수 있게, 학생들은 읽기만 가능하도록 하고 싶습니다.
선생님과 학생이 같은 디렉토리를 마운트하는 구조
종합적으로 우리는 같은 디렉토리를 선생님에겐 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
이를 개발환경에서 시험하는데, 이 때 문제가 생겼습니다. 바로 서버가 스폰이 되지 않는 현상이 발생하였는데요, 그 이유는 에러 메시지에서 찾을 수 있었습니다.
⚠️ [Warning] Unable to attach or mount volumes: …
볼륨이 제대로 마운트되지 않는 문제가 생겼습니다. 우리는 어떻게든 목표를 달성하기 위해서 이를 파헤치기 시작했습니다. 마운트가 되지 않는 이유를 밝혀내기 위해 여러 가설을 세우고 이를 검증하기 위해서 실험들을 진행하였습니다.
1. readOnly: true로 마운트가 불가능하다
이를 검증하기 위해서 저희는 새로 정의한 볼륨을 writable하게 수정하였습니다. 이를 위해선 단순히 볼륨의 정의에서 readOnly: False로 설정하면 됩니다.
def read_only_volume(spawner):
spawner.volumes.append(
{
"name": "read-only",
"persistentVolumeClaim": {
"claimName": ... ,
"readOnly": False,
}
}
)
return spawner.volumes
만약 볼륨의 read-only 때문에 마운트가 안된다면 이젠 잘 되어야겠지요. 하지만 결과는 이전과 같이 실패하였습니다.
2. kubespawner에서 동적으로 할당하면 안된다
그 다음으로 의심이 갔던 부분은 kubespawner에서 동적으로 정의된 볼륨이었습니다. kubespawner란 JupyterHub가 단일 사용자의 서버를 쿠버네티스 클러스터에서 생성할 수 있도록 합니다. 저희는 kubespawner에서 볼륨을 정의할 때 2가지 방식을 사용합니다.
- 정적으로 정의 : 미리 정해진 옵션을 전달하여 생성
- 동적으로 정의 : 단일 사용자의 서버가 생성 될 때에 옵션을 전달
저희는 어차피 항상 같은 볼륨을 사용하기 때문에 동적으로 정의하지 않아도 됩니다. 따라서 이를 한번 정적으로 정의를 해보았습니다.
c.KubeSpawner.volumes.append(
{
"name": "read-only",
"persistentVolumeClaim": {
"claimName": ... ,
"readOnly": True,
}
}
)
결과적으론 이 방법 또한 결과의 차이가 없었습니다. 여기까지 오다보니 제 머리속에 서서히 드는 생각이 있었습니다.
3. 동일한 PVC를 여러 볼륨으로 마운트가 불가능하다
그 생각은 바로 “이걸 JupyterHub가 아니라 쿠버네티스에서 제한하고 있지 않을까?” 입니다. 이를 알아보기 위해선 어떤 실험을 해볼 수 있을까요? JupyterHub라는 특수한 상황이 아닌 raw한 쿠버네티스에서 실험을 해보는 것입니다.
이를 위해 저희는 다음과 같은 파드를 새로 정의했습니다.
apiVersion: v1
kind: Pod
metadata:
name: pod-volume-mounts-test
spec:
containers:
- image: alpine
command: ["sh", "-c", 'echo "Hello Container!" && sleep 3600']
name: container-alpine
initContainers:
- name: init-container-alpine
image: alpine
command: ["sh", "-c", 'echo "Hello InitContainer!" && sleep 5']
volumeMounts:
- name: home
mountPath: "/nfs"
subPath: ...
- name: read-only
mountPath: "/read-only"
subPath: ...
# 한 PVC에 대해서 볼륨을 2개를 정의
volumes:
- name: home
persistentVolumeClaim:
claimName: ...
- name: read-only
persistentVolumeClaim:
claimName: ...
readOnly: True
InitContainer에선 메시지를 출력하고 5초 동안 sleep, Container에선 메시지를 출력하고 1시간 동안 sleep하는 파드입니다. 그리고 InitContainer에는 같은 PVC를 사용하는 볼륨 2개를 마운트했습니다. 이 파드를 apply 하면 어떻게 될까요?
결과는 이전과 같았습니다. 아하! 그럼 이젠 다음과 같은 결론을 낼 수 있겠네요.
ℹ️ 동일한 PVC에 대해서 다른 볼륨으로 마운트할 수 없다.
솔루션
이제 해결하는 방법은 간단합니다. 동일한 파일시스템에 대해서 read-only 볼륨을 위한 PV와 PVC를 새롭게 만드는 것입니다.
apiVersion: v1
kind: PersistentVolume
...
spec:
...
nfs:
server: ... # 기존 파일시스템과 동일하게
path: "/"
---
kind: PersistentVolumeClaim
apiVersion: v1
...
spec:
storageClassName: ""
...
위와 같이 정의를 하고 apply를 하여 기존의 python 코드에 새로 정의한 PVC의 이름을 넘겨줍니다. 그렇게 하면 우리가 원하던 기능이 다음과 같이 완성됩니다.
선생님의 코딩환경
학생의 코딩환경
이번 문제같은 경우에는 생각보다 간단한 방법으로 해결되었지만 매번 이렇게 쉽진 않답니다. 그래도 문제를 직면하고 해결하는 과정에서 무언가를 배울 수 있다는 건 엄청 기쁜 일이랍니다!
읽어주셔서 감사합니다. 🙏
교실 단위 실시간 코딩 수업을 위한 파이썬 환경, Codle
댓글
로그인 후 댓글을 남길 수 있습니다.
아직 댓글이 없습니다.