프로덕트

아티클

전체 보기
이창환

이창환

서버 접속이 느려요! K8S 스케쥴러를 만들기까지

(원본은 노션으로 작성되어 있습니다.)


안녕하세요 팀모노리스 CTO 이창환입니다.


수업 시간은 1분 1초가 소중하기 때문에, 저희는 접속시간을 최소한으로 줄이는 것을 신경쓰고 있습니다. 현재 목표는 10~20초 사이인데요. 이 시간 동안 주로 포드(Pod)가 생성됩니다.

1학기 중에 간헐적으로 서버 접속이 느리다는 것을 알게 되었고, 이 문제를 해결하기까지 많은 노력을 기울였습니다. 이 글에서는 우리가 어떤 과정으로 문제를 해결했고, 어떤 결론을 도출했는지 이야기하려고 합니다.

쿠버네티스에 대한 내용이 많고 문제는 결국 스케쥴러를 직접 만들어서 해결했기에 이런 주제에 관심이 있으시다면 재밌을거에요.


문제 파악

한 선생님께서 서버가 늦게 켜진다고 보고를 주셨습니다. 당연히 제가 들어갈때는 빠르게 잘 들어가졌습니다. 그 당시에는 서버 실행 시간을 볼 수 있는 환경이 없어서 문제 파악을 위해서 프로메테우스(Prometheus)와 그라파나(Grafana)를 설치했습니다.

그런 후 결과를 보니, 실제로 30초까지 서버 실행이 늘어지는 현상이 관측되었습니다. 


이 문제를 해결하기 위해서 추가적인 지표를 확인했는데요. 주피터 허브에 남은 접근 기록(Access Log)과 프로메테우스의 로그를 종합적으로 고려했습니다. 예시를 함께 살펴볼까요?


비정상 실행

59:06.506 - 서버 스폰 시작

59:06.595 - GET /hub/spawn-pending/{user}

59:06.??? - kube_pod_created

59.49.??? - kube_pod_container_state_started

00:00.831 - took 54.339 seconds to start


스폰시작 ↔ container start : 43s

container start ↔ 스폰완료 : 11s


정상 실행

59:46.474 - 서버 스폰 시작

59:46.575 - GET /hub/spawn-pending/{user}

59:46.??? - kube_pod_created

59.52.??? - kube_pod_container_state_started

00:01.535 - took 54.339 seconds to start


스폰시작 ↔ container start : 6s

container start ↔ 스폰완료 : 9s


파드가 실행되기 전 pending 상태에 오래 머무르는 것같네요. 하지만 이것만으로는 원인을 단정할 수가 없어서, 수업이 진행중일 때 이벤트를 확인해보았습니다.

이미지를 다운로드하기 위해서 20초 가량이 소모되었네요! 그러니 간헐적으로 1분까지도 실행시간이 걸리는 것이었어요. 🤔 어떻게 해결할 수 있을까요?


문제의 배경

일반적인 쿠버네티스 사용자는 파드 실행이 1분정도 걸리는 것은 이해해줄 수 있는 편이에요. 하지만 팀모노리스에서는 사용자가 접속하는 순간 파드를 띄우기 때문에 파드 실행시간이 곧 유저의 기다림으로 이어집니다.

이런 구조는 주피터 허브에서 사용되는 구조인데요. 커뮤니티의 사용자들도 파드가 실행되는 시간을 줄이기 위해 많은 노력을 해왔습니다. 그 중 하나는 이미지 풀러(Puller)입니다. 이미지 풀러는 노드(Node)가 생성될 때 함께 실행되어 미리 이미지를 다운받습니다. 그러면 파드가 생성될 때, 이미지를 다운로드 받는 시간을 줄일 수 있습니다.


문제

아하, 이미지 풀러에 문제가 있었나봐요. 그래서 파드가 이미지를 다운로드 받아야만 했고, 그만큼 서버 실행이 늦춰진것 같습니다.

하지만 여기서 구체적으로 문제를 설명하자면 굉장히 복잡해집니다. 노드 한 대에 사용자 3명이 할당될 수 있다고 해봅시다. 사용자가 이미 3명이 있을 때, 새 사용자가 오면 다음 그림처럼 새로운 노드를 할당받겠죠.

그런데 이 과정도 상당히 시간이 오래걸립니다. 이미지 다운로드 받는 것은 비교도 안됩니다. 그래서 이 상황도 다른 방식으로 대응하고 있는데요. 바로 더미 사용자를 미리 할당해놓는 것입니다.

다음 그림처럼 녹색 더미 사용자를 미리 할당해두면, 새 사용자가 올 때 더미를 제거하고 사용자를 할당합니다. 그리고 사라진 더미도 잠시 후에 추가해 놓는데요. 이러면 실제 사용자보다 많은 자원을 미리 확보하고 있어서, 사용자가 노드 생성을 기다리는 경우가 많이 줄어듭니다.


이제 이 더미와 앞에서 말한 이미지 풀러가 섞여서 문제를 일으키는데요. 각 노드에 이미지의 여부를 파랑 동그라미로 함께 표현했습니다. 다음 그림처럼 오른쪽 노드가 생성될 때, 이미지 풀러가 이미지를 미리 받아놓습니다. 오른쪽 노드에는 더미만 있는데도요!


자 학생들이 등교할 무렵 실제 사용자는 아무도 접속하지 않은 상태에서 더미가 미리 노드를 할당해놓고 있습니다. 여기에 두 명의 학생이 시간을 두고 들어오면 어떻게 될까요?

먼저 한 명이 도착합니다. 더미 사용자 하나를 제거하고 노드에 자리를 잡습니다. 그리고 더미가 하나 죽었으니, 이어서 더미도 하나 더 생성됩니다. 그러나 노드에 자리가 없기 때문에 새 노드가 할당되고, 더미가 새 노드로 갑니다. 그리고 새 노드에서는 이미지 풀러가 이미지를 받기 시작합니다.

이제 여기서 두 번째 사용자가 도착하면, 의도치 않은 현상이 일어납니다. 우리는 당연히 이미지가 이미 받아져 있는 노드에 가길 바라지만, 쿠버네티스의 스케쥴러는 그렇게 동작하지 않습니다. 왼쪽 노드에는 더미로 인해 진짜 빈 공간은 없기 때문에, 진짜 빈 공간이 있는 오른쪽에 할당해버립니다. 더미는 빈 공간이 없을 때만 삭제하게 되어 있거든요.

그러면 새 사용자는 오른쪽 노드로 할당되고, 이미지가 다운로드되는 것을 기다려야합니다. 이게 바로 우리를 괴롭힌 그 현상이었습니다.


해결

심상치 않아 딱 봐도 어려운 문제네요. 이걸 해결하기 위해서 수 많은 방법을 떠올렸는데요. 사실 이 문제는 팀모노리스만의 문제가 아니고 주피터 허브를 쿠버네티스에서 구동할 때 발생하는 문제라, 이미 오랜기간 알려진 문제였습니다. 하지만… 풀리지 않은 난제이기도 했어요.

결국 이미지가 설치된 노드에만 파드가 할당되도록 하는 쿠버네티스 스케쥴러를 직접 작성하기로 했는데요. 관련된 자료나 예시를 찾는 것은 굉장히 어려웠지만, 생각보다 코드양은 많지 않아 작성된 내용은 꽤나 깔끔했습니다.

https://github.com/team-monolith-product/image-locality-filter

간략한 Go 코드인데요. 핵심은 다음입니다. 저도 이거하면서 Go를 처음 써봤는데, 읽기 어렵지는 않습니다. for 문 안에 있는 if 문에서 이미지의 여부를 검사하고, 이미지가 없다면 image not found 를 전달하며 “스케쥴 불가” 상태를 반환하면 됩니다.


func (pl *ImageLocalityFilter) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {

node := nodeInfo.Node()

if node == nil {

return framework.NewStatus(framework.Error, "node not found")

}


for _, container := range pod.Spec.Containers {

if _, ok := nodeInfo.ImageStates[normalizedImageName(container.Image)]; !ok {

return framework.NewStatus(framework.Unschedulable, "image not found")

}

}


return nil

}


이제 이 스케쥴러를 적용하면 되는데요. 저희는 현재 오픈 소스를 활용하고 있어서 이 과정에서 이슈가 발생했습니다. 바로 AWS EKS 환경에서 원하는대로 스케쥴러를 적용할 수가 없던 문제인데요. 이 문제는 PR를 올려서 빠르게 해결했습니다.

https://github.com/jupyterhub/zero-to-jupyterhub-k8s/pull/2807

https://github.com/jupyterhub/zero-to-jupyterhub-k8s/pull/2809


그리고 마침내 적용을 했고, 원하는대로 스케쥴이 안되는 것까지 확인할 수 있었습니다! 1 image not found를 보세요! 놀랍지 않나요!

이제 예전처럼 서버가 늦게 켜지는 현상이 없어졌을까요? 이 작업이 마무리 됐을 때는 방학이라 사용자가 없어서 확인하지 못했는데요. 8월 초에 교사 연수 과정 중에 확인해볼 수 있었고...

모든 서버가 20초 이내에 안정적으로 켜졌습니다. (🎉)

마무리

글로 적으면서 많은 내용을 생략했는데요. 실제로는 많은 시행착오를 거치며 오랜 시간 골머리를 앓던 문제였습니다. 마침내 해결을 하게 되어 기쁜 마음에 글을 쓰게 되었습니다.

쿠버네티스 스케쥴러는 물론, Go 조차 처음 써보는 와중에 한 경험이라 상당히 재밌었고, 큰 성취감을 느낄 수 있는 작업이었습니다. 또한 커뮤니티에서 함께 공유하고 있던 어려운 문제라서 커뮤니티에 기여하는 뿌듯함도 있었어요. 이 스케쥴러가 모두에게 쓸만한 물건은 아니라고 생각하지만… 누군가에게는 도움이 될 수 있으니 내용을 정리해서 진행할 예정입니다!

긴 글 읽어주셔서 감사합니다 🙂



코들

교실 단위 실시간 코딩 수업을 위한 파이썬 환경, Codle

6
0
이창환

이창환

쿠버네티스에 읽기전용 볼륨 마운트하기

오늘은 팀모노리스의 개발자 류호선님의 개발 로그 공유드립니다. 원본은 저희 회사 사이트(노션)에 있습니다. (여기서 코드가 잘 표현이 안되네요!)


추가로 팀모노리스의 채용설명회가 다음주에 예정되어 있으니 많은 관심 부탁드립니다!



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

쿠버네티스

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

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

새로운 기능: 배포 폴더

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

  1. 실시간 반영
  2. 먼저 선생님이 공간에 자료를 올리면 학생들에게 바로 반영되면 좋겠습니다. 이는 마운트를 통해서 쉽게 해결을 할 수 있는데요, 같은 디렉토리를 선생님과 학생의 파드에 마운트를 하면 됩니다. 마치 하나의 방을 여러 문을 통해 드나들 수 있는 것처럼 말이죠.
  3. 학생들은 수정 불가
  4. 그런데 이 공간에 있는 파일을 학생이 마음대로 수정할 수 있다면 어떻게 될까요? 가령 한 학생이 선생님이 올려둔 수업 자료를 수정해버린다면, 다른 학생들은 선생님이 의도한 학습 효과를 보기 힘들 것입니다. 따라서 우리는 선생님은 수정할 수 있게, 학생들은 읽기만 가능하도록 하고 싶습니다.

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

종합적으로 우리는 같은 디렉토리를 선생님에겐 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가지 방식을 사용합니다.

  1. 정적으로 정의 : 미리 정해진 옵션을 전달하여 생성
  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

6
0
이창환

이창환

팀모노리스의 오픈 소스 데이 (1)

팀모노리스의 금요일은 조금 특별합니다. 공개된 레포지토리에서 개발과업을 진행하는 날이거든요! 저는 오늘 우리가 가지고 있는 프로젝트를 공개하기 위한 작업을 진행했습니다.


공개하려는 기능은 주피터 랩 환경에 구현한 온라인 저지입니다. 온라인 저지를 사용해보신 분이라면 다음 GIF를 보시면 이해되실거에요. 서버로부터 문제를 보여주고, 사용자가 문제를 제출하면 채점해주는 시스템입니다.


하지만 이 프로젝트를 공개할 때 고려해야하는 점이 몇 가지 있습니다.

  • 주피터 커뮤니티의 구성원은 영어를 주로 사용하기에 영어를 지원할 것
  • 문제를 제공하는 서버에 대한 종속성을 제거할 것
  • 다른 개발자가 쉽게 활용하고 참여할 수 있도록 좋은 문서를 제공할 것


첫번째를 만족하기 위해서, 초기에는 한국어로만 개발했다가 국제화 시스템을 활용해서 영어와 한국어 모두 지원하도록 수정했습니다.

두번째가 생각보다 골치아픈 문제였는데요. 아직 개발중인 내용이라 다음 주 금요일에 공유해보도록 하겠습니다.

이번 포스트에서는 세번째를 위해 한 작업에 대해 이야기하겠습니다.


아무리 좋은 프로젝트가 있어도, 그 프로젝트가 잘 설명되어 있지 않다면 아무도 쓰지 않을 것입니다. 어떤 내용인지도 모르는데 소스 코드를 뒤져가며 분석할 수는 없으니까요. 그렇기에 우리는 가장 먼저 README에서 필요한 정보를 획득하는데요. 그래서 저도 오늘은 README를 작성하는 작업을 주로 했습니다.

README를 잘 쓰는 법에 대해서는 많은 글을 찾아 볼 수가 있어요. 그래서 거기에 대해 이야기하기 보단 제가 어떻게 작성했는지에 대해서 사례를 공유해보고 싶습니다.


일단 가장 먼저 진행한것은, 하이라이트 영상(GIF)를 만든 것입니다. ‘온라인 저지’라고 말하면 경험이 있는 사람들은 바로 이해할 수 있겠지만, 익숙하지 않은 사람에게는 글로된 설명이 복잡할 수 있기 때문입니다. 그래서 짧은 영상을 통해 이 프로젝트가 어떤 역할을 하는지 전달했습니다.

영상 편집 경험이 없어서 저 짧은 영상을 시행착오를 겪어가며 만들었는데요. 좋은 방법을 찾아 시도한게 아니고, 제 컴퓨터에 이미 설치된 프로그램을 활용하며 겨우했기에 이렇게는 하지 마시라는 의미에서… 적어보겠습니다.

가장 처음에는 GeForce Experience의 녹화 기능으로 촬영하고, Win10 사진앱으로 편집을 시도했습니다. 그런데 영상 화면 잘라내기(Crop)이 안되서 실패했어요.

그 다음에는 GeForece Experience + Adobe Premiere Rush를 시도했는데, 최종 영상의 종횡비가 몇몇 프리셋으로 고정되어 있어 실패했습니다.

마지막으로는 OBS Studio를 사용해 원하는 크기로 녹화해서 마무리했습니다. 얻은 동영상은 웹 서비스를 활용해서 GIF로 변환했어요. 이 과정을 통해 방금 보신 GIF가 탄생했습니다!


그 다음에는 프로젝트가 추구하는 목적을 서술했습니다. 이 목적은 프로젝트의 개발 동기와 발전 방향을 나타내기에 개인적으로는 아주 중요한 영역이라고 생각합니다. 이 온라인 저지 기능에 대해서는 다음과 같이 서술했습니다.

  • 코드 실행 관리를 위한 추가적인 시스템 구축이 필요없습니다.
  • 주피터 랩에서 문제를 풀고, 결과를 조회할 수 있습니다.
  • 백엔드를 교체할 수 있습니다.

이렇게 서술함으로써 프로젝트에 일관성을 부여할 수 있고, 불필요한 기능 개발에 커뮤니티의 노동력을 낭비하지 않을 수 있습니다.

예를 들자면 다음과 같은 기능 제안은 그 의도가 아주 훌륭하지만 프로젝트의 목적에 부합하지 않습니다.

사용자가 제출한 코드를 격리된 시스템에서 실행하여 코드 실행 시간 지표의 신뢰성을 높입니다.

위 기능이 추가되면서 “추가적인 시스템 구축이 필요없다”라는 핵심적인 강점이 훼손되기 때문입니다.


이외의 영역은 간략한 구조 설명, 시스템 요구 사항, 설치, 기여 방법 등에 대해서 작성했습니다. 또 어떤 내용이 담기면 좋을까요? 의견은 언제나 환영입니다 👋


오늘은 팀모노리스의 특별한 금요일에 대해서 공유해 보았습니다. 멋진 기술에 대한 내용이 아니라서 조금 아쉬워요. 😢 다음에 기회가 된다면 특별한 구현 사례에 대해서도 포스트를 작성해보도록 하겠습니다. 즐거운 주말 되세요!

코들

교실 단위 실시간 코딩 수업을 위한 파이썬 환경, Codle

6
1
이창환

이창환

두 달만에 파이썬 코딩 환경 만들기

안녕하세요. 저는 코들을 개발하고 있는 팀모노리스 CTO 이창환입니다. 여기선 모든 개발을 다 하지만, 경력은 백엔드 개발에 집중되어 있습니다. 팀모노리스 이전에는 버킷플레이스에서 3년간 일했습니다.


코들은 교실 단위의 실시간 수업을 돕는 파이썬 코딩 환경입니다. 코들은 작년 12월부터 개발에 착수해서, 올해 1학기부터 몇몇 학교에서 사용되고 있는데요! 맨바닥부터 프로덕트를 만들기까지 시간이 촉박했던 만큼 어떤 방법으로 개발 기간을 최대한 단축했는지, 그리고 그 과정에서 어떤 시행착오가 있었는지를 공유해보겠습니다. 🎉


개발을 가장 빠르게 하는 방법이 뭘까요? 개발을 안 하면 안 할수록 개발은 빨라집니다. 😊 그래서 팀모노리스는 바퀴의 재발명을 피하고자 최선을 다해 여러 오픈 소스를 활용했습니다.

“교실 단위의 실시간 수업을 돕는 파이썬 코딩 환경”을 개발할 때, 어떤 영역이 가장 많은 공수가 필요할까요? 아마도 “파이썬 코딩 환경”일 것입니다. 그래서 역설적으로 1학기 개학 전까지 MVP를 만들기 위해선 파이썬 코딩 환경은 절대 만들면 안 되는 상황이었습니다.

이때, 주로 VSCODE와 Jupyter사이에서 고민했습니다. 우리는 Jupyter를 사용하기로 했는데요.

  • VSCODE에 웹 개발 환경도 있으나 직접 배포하기까지 큰 시행착오 예상됨.
  • VSCODE는 MS에서 받아 써보기만 했고, 누군가 수정해서 만든걸 본 적이 없음.
  • 교육 기관에서 Jupyter를 사용하는 사례가 많음.
  • 물론 개발자는 VSCODE를 더 많이 사용하지만요.
  • 우리의 고객은 개발자보다는 학생에 가깝습니다.

그래서 우리는 Jupyter를 선택했고, 구체적으로 Jupyter Lab, Jupyter Hub, Zero to Jupyter Hub를 활용해서 개발에 착수했습니다!


기업에서 오픈 소스 도입에서 우려하는 것 중 하나는 유지보수가 어렵다는 것입니다. 그러니까… 만약… 오픈 소스 자체에 버그가 있다면 어떡하죠? 우리 회사에서 만든 거라면 그걸 만드신 분이 아직도 팀에 있을 수 있고, 퇴사하셨더라도(😭) 전화라도 한 번 해볼 수 있을 텐데요!

안타깝게도 우리에게도 그런 일이 일어났습니다. 교실 기능을 만들고서, 우리는 선생님이 학생의 환경에 접속할 수 있길 바랐습니다. 그래서 학생이 작성한 파일도 보고 피드백도 남길 수 있게요.

여러 사용자가 Jupyter를 쓸 수 있도록 하는 서비스는 Jupyter Hub인데요. 그래서 Jupyter Hub의 설정을 통해 선생님이 학생의 서버에 접속할 수 있게 권한을 허가하는 작업이 필요했습니다. 그렇게 신나게 작업을 하고 선생님 계정으로 학생 환경에 성공적으로 접속했습니다! 그리고 동시 편집이 잘 되나 싶어서 학생 계정으로 접속해봤는데… 학생이 자신의 환경에 접속할 수가 없었습니다. 💩

훌륭한 개발자가 되려면 남 탓을 하지 않아야 하기에 당연히 제 설정에 오류가 있을 거라 생각하고 찾아봤으나 차도가 없었습니다. 그리고 스멀스멀 마음속에서 남 탓 귀신이 속삭이고 있었어요. 정신을 차리고 보니 Jupyter Hub 소스 코드를 이 잡듯 뒤지고 있었습니다…

한참이 지나고 나서 마침내 버그를 찾아내었고, PR을 작성했습니다. Jupyter 팀에서 일을 빨리해줘서 3일 만에 병합이 되었습니다. 그리고 새 버전이 배포 되었어요.


이 사례는 어떻게 보면 오픈 소스를 무턱대고 쓸 때의 단점을 말해주는 것 같아요. 하지만 좀 더 따져볼까요?

  • 직접 코딩 환경 만드는 거랑 비교했을 때 여전히 개발이 빠르다. 이런 버그가 100개 더 있어도…
  • 사용 중인 도구에 대한 더 깊은 이해
  • 자동화된 검증 도구, 업무 프로세스에 대한 인사이트
  • 즐거운! 경험
  • 그리고 아주 멋진 Contributor 딱지까지…


대부분 개발자들은 분명 오픈 소스 프로젝트에 참여하는 것이 아주 멋지다고 생각할 거예요. 그런데 생각보다 많은 개발자분이 여기에 어려움을 느끼세요. 그래서 저는 오픈 소스에 대해 열정적인 개발 문화를 만들어야겠다고 다짐했습니다. 어차피 팀모노리스에서는 오픈 소스를 엄청 쓸 거고, 위와 같은 사례도 정말 많이 발생할 거니까요. 

…
…

네. 위 사례는 개발 시작 단 2주 만에 발생한 거고, 실제로 훨씬 더 많이 발생했어요. 이 사례들도 기회가 되면 공유해보도록 하겠습니다. 읽어주셔서 감사합니다. 🙏

코들

교실 단위 실시간 코딩 수업을 위한 파이썬 환경, Codle

8
6

포스트

아직 포스트가 없습니다.