TECHNICAL REPORT · CASE STUDY
교육용 온라인 저지와 안전한 AI 피드백 설계
1. 문제 정의
고교 정보/인공지능 수업에서 파이썬을 처음 배우는 학생이 스스로 문제를 풀고 즉시 채점받을 수 있는 환경이 필요했습니다. 요구사항은 세 가지였습니다.
- 안전성 — 학생이 작성한 임의의 코드를 서버에서 실행해야 하므로, 사고가 나도 호스트와 다른 서비스(운영 중인 LLM 플랫폼·GPU 작업)에 전파되지 않아야 한다.
- 교육성 — 틀렸을 때 정답을 그대로 주면 학습이 되지 않는다. 방향만 짚어주는 피드백이 필요하다.
- 지속성 — 학생이 제출한 뒤에도 자기 코드가 그대로 남아 계속 고칠 수 있어야 한다.
2. 아키텍처 결정 — 왜 별도 서비스인가
결정
기존에 운영 중인 교육용 LLM 플랫폼과 프로세스·포트·도메인을 완전히 분리했습니다. 저지는 독립 포트에서 FastAPI로 돌고, 앞단은 Cloudflare Tunnel이 담당합니다.
근거
저지는 설계상 신뢰할 수 없는 코드를 실행합니다. 같은 프로세스나 같은 서비스에 얹으면, 채점 중 사고가 수백 명이 쓰는 LLM 서비스까지 끌고 내려갈 위험이 있습니다. 분리하면 장애 범위가 저지 안에 갇힙니다.
3. 막힌 부분 — 신뢰할 수 없는 코드를 어떻게 실행할까
막힌 지점
가장 큰 난관은 "학생이 무엇을 제출할지 모른다"였습니다. 무한 루프,
메모리 폭탄, 포크 폭탄, 파일 시스템 쓰기, 외부 네트워크 접속, 다른
프로세스 엿보기 — 전부 가능성 있는 입력입니다. 단순히
subprocess로 파이썬을 실행하면 호스트가 그대로 노출됩니다.
시도한 방법
컨테이너 격리를 택하고, 보호 플래그를 하나씩 실제로 깨뜨려 보며 검증했습니다. "네트워크를 막았다고 주장"하는 것과 "소켓 연결이 실제로 실패하는 것을 확인"하는 것은 다릅니다. 아래는 적용하고 동작을 확인한 격리 계층입니다.
| 위협 | 방어 | 확인 방법 |
|---|---|---|
| 외부 네트워크 접속 | --network none | 소켓 연결 실패 확인 |
| 파일 시스템 변조 | --read-only + tmpfs /box | /etc 쓰기 거부, /box만 쓰기 가능 |
| 메모리 폭탄 | --memory 256m (swap 동일) | 초과 시 OOM 종료 |
| 포크 폭탄 | --pids-limit 64 | 프로세스 수 상한 |
| 권한 상승 | --user 65534 + cap-drop ALL | UID 65534(nobody)로 실행 확인 |
| 무한 루프 | 컨테이너 내 timeout -s KILL + 호스트 timeout | 강제 종료 확인 |
결정
제출 코드와 입력은 호스트 임시 디렉토리에 쓰고 :ro(읽기 전용)로
마운트, 표준출력만 수거해 기대값과 비교합니다. 컨테이너는 종료 시 삭제하고,
동시에 돌아가는 채점 수는 세마포어로 제한해 GPU·LLM 작업과 CPU를 다툼 없이
공유하도록 했습니다.
4. 막힌 부분 — AI 피드백이 정답을 알려주면 안 된다
막힌 지점
"AI가 틀린 코드를 봐주면 좋겠다"는 요구는 자연스럽지만, 그대로 두면 LLM이 정답 코드를 통째로 뱉어 학생이 베끼게 됩니다. 교육적으로는 최악입니다.
시도한 방법
- 시스템 프롬프트로 역할 고정 — "정답 코드를 통째로 주지 말고, 무엇이 틀렸는지 방향과 개념을 짚고 스스로 고치도록 질문하라"는 규칙을 명시했습니다.
- 출력 후처리 안전망 — 모델이 그래도 긴 코드 블록을 쏟아내면, 코드블록이 6줄을 넘을 때 앞부분만 남기고 "나머지는 직접 완성해 보세요"로 잘라내는 후처리를 넣었습니다. 프롬프트만 믿지 않습니다.
- 숨은 테스트 비노출 — 채점용 숨은 입력·정답은 프롬프트에 넣지 않고, 공개 샘플만 전달합니다.
- 항상 응답(폴백) — 로컬 LLM이 없거나 느리면 규칙 기반 힌트로 폴백해, AI 호출 실패가 채점 흐름을 막지 않게 했습니다.
실제 결과 (검증)
"두 정수를 더해야 하는데 input().split()으로 받은 문자열을 그대로
+로 이어 붙인" 전형적 버그 코드를 넣었더니, 모델은 정답 코드를
주지 않고 이렇게 답했습니다(실제 출력):
input().split()으로 받은 값은 숫자가 아니라 문자열(String)이에요. 문자열끼리+연산자를 쓰면 숫자 합이 아니라 앞뒤로 붙는 연결이 일어나죠.a와b가 숫자로 계산되려면 어떤 변환을 해줘야 할까요?int()함수를 활용해 수정해 보세요.
원인(문자열 연결)과 개념(형변환)을 짚고 질문으로 끝맺되, 완성된 정답은 주지 않았습니다. 의도한 교육적 동작과 일치합니다.
5. 데이터 — 처음 배우는 학생을 위한 그림 있는 문제
결정
설명을 글로만 쓰면 입문자가 막힙니다. 수직선, 리스트 인덱스 상자, 막대그래프, 조건 분기 순서도처럼 그림으로 설명하는 게 더 나은 주제에는 문제 설명 안에 인라인 SVG를 직접 그려 넣었습니다. 외부 이미지 파일이 아니라 텍스트로 된 벡터 그림이라, 서버에 바이너리를 두지 않고 버전 관리와 재현이 쉽습니다.
검증
새로 만든 튜토리얼 문항의 참조 해답을 전부 자동 실행해 기대 출력과 비교했습니다. 아래는 측정된 수치입니다.
- 100신규 튜토리얼 문항
- 240신규 테스트케이스 — 전수 통과
- 7그림(SVG) 포함 문항
- 200 / 640전체 문항 / 테스트케이스
참조 해답을 샌드박스로 돌린 표본 채점에서도 전부 정답 판정(AC)이 나왔고, 로그인→문제 조회→오답 제출(AI 힌트 생성)→정답 제출의 전체 흐름을 통합 테스트로 확인했습니다.
6. 지속성 — 제출해도 코드가 사라지지 않게
막힌 지점 → 결정
초기 구현은 문제를 열 때마다 에디터를 템플릿으로 초기화해, 제출 후 코드가 날아갔습니다. 문제별 키로 브라우저 로컬 저장소에 코드를 보관하고, 편집할 때마다 저장·복원하도록 바꿨습니다. 이제 제출·새로고침과 무관하게 학생이 자기 코드를 계속 고칠 수 있습니다. 더불어 처음 배우는 학생을 위해 에디터에 자동완성(파이썬 키워드·내장 함수·코드 내 식별자), 괄호 자동 닫기, 괄호 짝 강조, 현재 줄 강조, 4칸 들여쓰기, 가벼운 문법 린트(괄호/따옴표 짝, 콜론 뒤 들여쓰기 누락), 단축키(자동완성·빠른 제출·주석 토글)를 넣었습니다.
7. 성능 메모 (실측)
로컬 LLM 힌트 생성 지연을 3회 측정했습니다: 약 4.3s · 5.2s · 6.7s. 채점은 샌드박스에서 동기 처리하되 이벤트 루프를 막지 않도록 스레드로 오프로드했고, AI 힌트는 오답일 때만 생성해 정답 제출 경로에는 추가 지연이 없습니다. 저지 핵심 백엔드는 약 1,000줄 규모의 파이썬 (게이트웨이·채점 엔진·AI 피드백·DB·인증)으로 유지했습니다.
8. 배운 점
- 보안은 "설정했다"가 아니라 "깨뜨려 보고 막힌 걸 확인했다"로 말해야 신뢰할 수 있다.
- 교육용 AI의 핵심 설계는 모델 성능이 아니라 무엇을 주지 않을지를 정하는 데 있다.
- 프롬프트 규칙은 1차 방어일 뿐, 출력 후처리 같은 결정론적 안전망을 함께 둬야 한다.
- 외부에 못 나가는 폐쇄적 LLM이라도, OpenAI 호환 인터페이스로 두면 교체·폴백 설계가 단순해진다.
부록 · 실행 환경
이 보고서를 작성·검증한 호스트(실측): HP ZGX Nano G1n AI Station, NVIDIA GB10(Grace-Blackwell) GPU, 약 121 GiB 통합 메모리, Ubuntu 24.04(aarch64). 이 한 대에서 교육용 LLM 채팅·온라인 저지·이 소개 사이트가 각각 분리된 포트로 동시에 돌아가며, 앞단은 Cloudflare Tunnel이 담당합니다.
참고: 운영 전체 구성은 이 Grace-Blackwell 스테이션을 포함한 여러 장비로 이루어져 있으나, 위 수치는 보고서 작성 시점에 이 호스트에서 직접 측정한 값만 적었습니다.