{ "version": "https://jsonfeed.org/version/1", "title": "LiteHell의 블로그", "home_page_url": "https://blog.litehell.info", "feed_url": "https://blog.litehell.info/feed/json", "description": "LiteHell의 개인블로그입니다. 프로그래밍이나 제 개인적인 일상에 관련된 글들이 올라옵니다.", "icon": "https://gravatar.com/avatar/837266b567b50fd59e72428220bf69b1", "author": { "name": "Yeonjin Shin", "url": "https://litehell.info" }, "items": [ { "id": "retrospective_of_2024", "content_html": "

들어가는 글

\n

올해는 뭔가 많은 일이 있었다.... 되게 바빴지만 동시에 많은 것들을 이루어낸 해이기도 했다. 회고를 쓰면서 지난날들을 되돌아보려 한다.

\n

하반기

\n

하반기에는 인턴십을 하고 학업을 마무리했다.

\n

하계방학 인턴십

\n

중앙대학교 현장실습지원센터를 통해 여러 인턴십에 지원했다. LG CNS 채용 연계형 인턴십과 카카오 채용 연계형 겨울 인턴십, 그리고 주식회사 슈르의 산학연계 인턴십에 지원했다.

\n

LG CNS 채용 전형은 서류, 코딩테스트, 2:2 비대면 면접 순으로 진행됐고, 카카오 채용 전형은 서류, 코딩테스트, 3:1 대면 면접으로 진행됐다. 둘 다 면접까지는 갔으나 아쉽게도 불합격했다. LG CNS는 같이 면접 본 다른 지원자분께서 스펙이 너무나 뛰어났고, 카카오 인턴십은 면접이 서툴러서 잘못 본 것이 원인인 것 같다. 카카오는 대면면접을 볼 때 대기실이 따로 있고, 면접비로 50,000원 상당의 카카오페이포인트와 춘식이 핫팩을 주는 점이 좋았다.

\n

주식회사 슈르는 중앙대학교 현장실습 통합관리 시스템을 통해 지원했다. 전형은 서류, 1:1 비대면 면접 순으로 진행됐다. 면접은 웹과 관련된 기술면접으로만 이루어졌다. 회사가 가산디지털단지역에 있어서 학교 기숙사에서 출퇴근하기 매우 편했고, 사람들이 되게 좋았다. 출퇴근은 자유로운 편이었으며, 휴가 사용은 완전히 자유로웠다. 포괄임금제이지만, 초과근무시 그에 상응하는 보상휴가를 지급해준다.

\n

회사에서는 고인물테스트의 프론트엔드를 개발했다. 그때 회사에 나를 포함하여 인턴이 3명 있었는데, 내가 백엔드를 하면 다른 분께서 프론트 개발하는 데 우여곡절이 많을 것 같아서 그냥 내가 프론트엔드를 맡았다.

\n

처음 회사 직원분께서 사이트가 매우 간단할 것으로 예측하고 그냥 HTML + CSS + Javascript 조합으로 빠르게 만들자고 제안하셨다. 초반 기획서도 그렇게 거창하지 않았기에 알겠다고 하고 HTML + CSS + Javascript 조합으로 만들었다. 그러나 기획서가 가면 갈수록 수정되면서 복잡해졌고, 이에 나는 개발 편의성을 위해 Javascript 코드를 Typescript 코드로 재작성하고 Webpack을 이용한 빌드 시스템을 구축했다.

\n

가장 인상 깊었던 것은 좌우 무한 스크롤링을 구현하라는 요구사항이었다. 처음에는 웹브라우저에서 제공하는 스크롤바를 최대한 활용하려 했는데 사파리에서 자꾸만 버그가 나서 결국 그냥 바퀴를 재발명했다. 마우스랑 터치 이벤트를 받아 이동량을 계산하고 계산한 값에 따라 requestAnimationFrame으로 자식 요소의 위치를 이동시킴으로써 스크롤링을 직접 구현했다. 가장 하기 싫은 방법이었지만 결국 어쩔 수 없었다. 사소한 버그는 있었지만, 나름 그럴싸하게 동작했다. (이에 대해서는 추후 별도의 글로 쓸 예정)

\n

학교 프로젝트에서 디자이너나 기획자랑 협업할 일이 없었는데 회사에서 처음으로 기획자, 디자이너, 실무자와 같이 협업했다. 그 과정에서 슬랙이랑 노션도 적극적으로 써보고 스타트업이 어떻게 돌아가는지에 대해 많은 걸 배울 수 있어 좋은 경험이었다.

\n

2024-1학기

\n

학교 수업

\n

2024-1학기에는 정보보호이론, 네트워크응용설계, 신호및시스템, 데이타베이스시스템, 캡스톤디자인(2)를 수강했다. 임베디드 관련 과목을 듣고 싶었는데 담당 교수님이 안식년인지라 어쩔 수 없이 신호및시스템을 대신 수강하게 됐다.

\n

정보보호이론은 DES, AES, RSA, ElGamel와 같은 암호에 관한 내용을 배웠다, 한 번 제대로 배워보고 싶었던 내용이라서 재밌었다. 네트워크응용설계는 네트워크 레이어 3~7을 Top-down으로 배운다. 데이타베이스시스템은 DB 시스템의 내부구조에 대해 가르친다. 신호및시스템은 푸리에 변환에 대해 맛보기로 가르쳐준다. 모두 다 좋은 내용이어서 만족스러웠다.

\n

바이드럼(캡스톤디자인)

\n

중앙대학교는 캡스톤디자인을 2번 해야 한다. 2023-2학기에는 알고모여를 했었고 (관련 글) 이제 2024-1학기에도 프로젝트를 해야 했다.

\n

팀 인원은 다행스럽게도 2023-2학기의 구성 그대로 가기로 결정돼서 주제만 빠르게 결정되면 됐다. 이때 리듬 게임을 만들 것을 다시 한번 더 제안했다. 말로만 제안하면 또 반대를 받을 게 예상돼서 이번에는 프로토타입을 미리 만들어 팀원들을 설득했다. 프로토타입에 관한 이야기는 이 글을 참고하라.

\n

주제부터 결과물까지 모든 것이 어그로였다. (중앙대에서 졸업작품으로 아케이드 리듬 게임을 하는 용자는 매우 드물다) 그래서 이왕 어그로 끄는 김에 최종 발표도 어그로로 하기로 했다. 한솜미술센터에서 사물놀이복을 빌려서 최종 발표를 했다. 결과는 매우 성공적이었고 웃음을 참지 못하던 조교의 표정을 아직도 잊을 수가 없다.

\n

상반기

\n

상반기에는 본격적인 사회인이 됐다.

\n

첫 정규직 직장

\n

하계방학에 인턴십을 진행한 주식회사 슈르에 정규직으로 입사하게 됐다. 내가 학업에 열중하는 동안 회사는 여러 우여곡절을 겪으며 체계가 더 단단해졌다. 회사에서 개발하는 이커머스 서비스의 백오피스 프론트엔드 개발을 주로 맡았었다. 스타트업 기업에서 근무하면서 스타트업이 어떻게 돌아가는 지를 인턴으로 근무할 때보다 더 자세히 알 수 있었다. 다른 현직 개발자와 협업도 처음으로 해봤지만, 개인적으로 시간에 쫓겨 개발하느라 기술적인 성장을 많이 이루지 못한 것 같다. 그와는 별개로 Firebase를 본격적으로 처음 써봤는데 꽤 편리해서 좋았었다.

\n

되게 열심히 일했다. 학교로 졸업사진 찍으러 가다 회사에 큰 일 터져서 바로 헐레벌떡 회사로 달려가 고쳐도 보고... 여러 추억을 쌓았다.

\n

이직

\n

슈르를 다니다가 더 좋은 직장에서 더 높은 연봉의 오퍼를 받아 이직하게 됐다. 아직 모든 것이 낯설고 앞으로 내가 잘할 수 있는지 두렵지만 어찌 됐든 잘 적응해서 어서 빨리 성취를 이루고 싶다.

\n

자취

\n

직장을 옮기니 집과 직장 사이의 거리가 더 멀어져 결국 자취를 하게 됐다. 수도권 집값 너무 비싸서 볼 때마다 아깝다는 생각이 들지만, 그래도 어쩔 수 없다. 집값도 비싸면서 이상한 집은 얼마나 많은지... 그래도 자취를 하니 직장까지 가기가 너무 편해서 만족스럽다. 특히 자취를 한 번도 안 했다가 이제서야 진정한 프라이버시를 얻게 된 점이 너무 좋다.

\n

현재 근황

\n

개인서버

\n

지인으로부터 안 쓰는 데스크톱 본체를 얻게 됐다. 이 본체에 Proxmox를 설치해 개인 서버를 구축했다. 이제 NAS를 설치하려고 이것저것 알아보고 있는데 TrueNAS는 제대로 쓰려면 하드웨어 패스쓰루를 해야 한다고 해서 내키지 않고, openmediavault는 웹 파일 브라우저 UI가 뭔가 마음이 들지 않아서... 그냥 내가 직접 만들까도 생각하고 있다.

\n

Tor 릴레이

\n

개인적으로 고대역폭 Tor 릴레이를 구축해보고 싶다. 그래서 KINX에 관련 문의를 해봤는데 추후 견적이 어떻게 나오는가에 따라서 안 할 수도 있다.

\n

프로젝트 "나무위키 이야기"

\n

나무위키의 초반기 역사에 대한 책을 쓰고 싶어서 올해 중반쯤에 프로젝트를 결성했는데, 바쁘게 살다 보니 그 새 까먹어서 이제서야 본격적인 시동을 걸게 됐다. 사람들이 슬슬 기억이 안 나기 시작해서 원할하게 될지는 모르겠지만, 어쨌든 잘 이루어졌으면 좋겠다.

\n

쓰지 못한 글들

\n

아직 못 쓴 글들은 다음과 같다. 학생자치후기는 특성상 검열이 많이 될 수 있어서 재미가 없을 수도 있다.

\n\n

아랫글들은 잠깐 쓰다 말았는데 완성 안 하고 그냥 삭제할 수도 있다.

\n\n

아랫글은 쓰다 말았는데 타이밍을 놓쳐서 그냥 삭제할 계획이다.

\n\n

마무리

\n

이제 연말이 얼마 남지 않았다. 다음 해에는 다들 즐거운 일만이 있기를!

\n", "url": "https://blog.litehell.info/post/retrospective_of_2024", "title": "2024년의 회고", "summary": "졸업과 취업", "image": "https://gravatar.com/avatar/837266b567b50fd59e72428220bf69b1", "date_modified": "2024-12-27T10:41:06.311Z" }, { "id": "caucalendar_1", "content_html": "

들어가는 글

\n

나는 캘린더 앱을 적극적으로 활용한다. 시간이나 약속을 머릿속으로만 관리하면 잘 잊어버리기 때문에 캘린더 앱을 적극적으로 활용하고 있다.

\n

그렇게 캘린더 앱을 적극적으로 쓰다가 대학교에 입학했다. 시험기간이나 수강정정기간 같은 것도 캘린더 앱에 뜨면 좋겠는데 이걸 직접 추가하는 건 귀찮았다. 그래서 중앙대학교 학사일정 페이지를 크롤링하는 어플리케이션을 작성했다. 그리고 캘린더 앱과 내 어플리케이션을 연동하는 데에는 iCalendar 파일 포맷을 이용했다.

\n

iCalendar

\n

Google CalendarMS Outlook, 혹은 필자가 이용하는 FastMail에서는 캘린더 기능을 제공한다. 이 캘린더 서비스들은 기본적으로 특정한 iCalendar 주소를 구독하는 기능을 지원한다. 즉, 다시 말해 필자가 구글 캘린더나 아웃룩에 iCalendar 파일 주소를 추가하면, 구글 캘린더나 아웃룩 서버가 주기적으로 iCalendar 주소에 접속해 동기화한다.

\n

iCalendar 파일은 다음과 같은 형식으로 되어있다.

\n
BEGIN:VCALENDAR\nVERSION:2.0\nTIMEZONE-ID:Asia/Seoul\nX-WR-TIMEZONE:Asia/Seoul\nX-WR-CALNAME:중앙대학교 학사일정\nX-WR-CALDESC:calendar.puang.network에서 제공하는 중앙대학교 학사일정\nCALSCALE:GREGORIAN\nPRODID:adamgibbons/ics\nMETHOD:PUBLISH\nX-PUBLISHED-TTL:PT1H\nBEGIN:VTIMEZONE\nTZID:Asia/Seoul\nTZURL:http://tzurl.org/zoneinfo-outlook/Asia/Seoul\nX-LIC-LOCATION:Asia/Seoul\nBEGIN:STANDARD\nTZOFFSETFROM:+0900\nTZOFFSETTO:+0900\nTZNAME:KST\nDTSTART:19700101T000000\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:552361268d864ef42fff1bee5d295e073f7ab2b2@calendar.puang.network\nSUMMARY:신정(공휴일)\nDTSTAMP:20240825T081930Z\nDTSTART;TZID=Asia/Seoul;VALUE=DATE:20220101\nEND:VEVENT\nBEGIN:VEVENT\nUID:eb57cfcaf7345c4ad83d1e7537dd81016db2d8a7@calendar.puang.network\nSUMMARY:2022년 1학기 재입학 원서접수\nDTSTAMP:20240825T081930Z\nDTSTART;TZID=Asia/Seoul;VALUE=DATE:20220103\nDTEND;TZID=Asia/Seoul;VALUE=DATE:20220107\nEND:VEVENT\nEND:VCALENDAR
\n

위와 같은 식으로 iCalendar 아이템(VCALENDAR) 속에 여러 일정(VEVENT)들이 나열되어 있다. iCalendar 형식은 할일(VTODO)이나 일기(VJOURNAL)도 지원하지만 이 글에서는 다루지 않는다.

\n

Koa.js를 이용한 첫 버전

\n

첫 버전은 Koa 프레임워크를 이용하여 간단하게 작성했다. 원래 이전에는 express를 썼었는데, express는 async 함수 핸들러가 바로 지원되지 않아서 약간 귀찮다는 단점이 있었기에 Koa 프레임워크를 이용했다.

\n

이 프로그램에서 중요한 것은 iCalendar 파일을 제공하는 것이다. 따라서 그 외의 요소는 모두 부수적인 것이다. 그렇기에 프론트엔드는 다음과 같이 디자인이 극단적으로 되어있어도 상관없었다. (사진은 첫 커밋 버전의 메인 페이지이다.)

\n

\"첫

\n

다만 그래도 위처럼 만드는 건 좀 심하니 bulma CSS 프레임워크를 이용해 아래와 같이 간단히 꾸몄다.

\n

\"bulma

\n

이때가 2019년 5~6월쯤이였다. 이때의 구조도는 다음과 같다.

\n

\"서비스

\n

당시 가상서버에서는 여러 웹서비스가 구동되고 있었기에, Host를 확인하여 알맞은 웹서비스로 트래픽을 전달해야 했다. 따라서 Nginx로 리버스 프록시가 동작하고 있었다.

\n

위 사진에서 PM2는 프로세스가 꺼지면 다시 켜주는 역할을 한다. 리브레위키의 리버티엔진에서 쓰길래 써봤다.

\n

크롤링 스크립트 분리

\n

초기에는 크롤링을 분리하기 귀찮아서, 그냥 요청이 들어올 때마다 학교 홈페이지에 접속해 학사일정 iCalendar 파일(이하 "ics 파일")을 제공했다. 그랬더니 어느순간 학교에서 서버 ip를 차단했다. 이게 2019년 11월 쯤의 일이였다.

\n

그래서 크롤링하는 코드를 별도의 파일로 분리하고, crontab을 이용해 크롤링 스크립트가 주기적으로 실행되게 했다. 크롤링된 데이터는 Sequelize ORM을 이용해 저장했다.

\n

GitHub Action

\n

학사일정 서비스에 버그가 생겼다고 캘린더 앱에서 잘 보이던 일정이 갑자기 사라지진 않는다. 그래서 동작에 이상이 생겨도 기존에 쓰던 사람들은 티가 잘 안난다.

\n

그래서 동작이 정상적으로 이루어지는 지 주기적으로 확인하기 위해 다음과 같이 GitHub Action을 추가했다.\n푸시나 커밋시가 아닌 특정 주기에 따라 반복되는 GitHub CI로 테스트가 주기적으로 이루어지도록 했다.

\n

따라서 이를 통해 학교 홈페이지의 갑작스런 디자인/API 변경에도 대응할 수 있었다.

\n
name: Build and test\n\non:\n  schedule:\n    - cron: '0 19 * * *'\n  push:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Build\n        run: docker build --target test .
\n

푸시나 커밋시가 아닌 cron으로 GitHub CI를 추가하여 학교 홈페이지의 갑작스런 디자인/API 변경에도 대응할 수 있도록 했다.

\n

도커라이징

\n

개인서버에서 돌아가던 서비스들을 다 도커 컨테이너에 감싸는 작업을 했었다. 그때 학사일정 ics 서비스도 도커 컨테이너로 감쌌다. crontab을 이용해 따로 돌아가던 크롤링 스크립트는 어플리케이션에 다시 집어넣어서, 어플리케이션 실행시 크롤링이 자동으로 주기적으로 실행되도록 수정했다. Docker에서 crontab을 쓰려면 약간 귀찮기 때문이다.

\n

처음에는 node:14 도커 이미지를 기반으로 썼는데 값싼 가상서버에서 쓰기에는 디스크를 너무 많이 차지했다. 그래서 나중에 Alpine Linux 기반 도커를 기반으로 바꿨다. Alpine Linux 기반 이미지를 쓰니 디스크 소비량을 줄일 수 있었다. 이때가 2021년 2~3월인가 그랬을 것이다.

\n

Go 언어로의 재작성

\n

그렇게 Javascript로 작성해서 잘 쓰다가 문득 이런 생각이 들었다. 'Go를 쓰면 더 빠르지 않을까?' Javascript는 인터프리터 언어이고, Go는 컴파일 언어이니, 알고리즘의 효율성이 유사하다는 가정하에 Go가 더 빠를 수도 있지 않을까란 생각이 들었다. 물론 Go 언어를 한 번 써보고 싶은 생각도 없진 않았다.

\n

그래서 Go 언어로 학사일정 서비스를 재작성했다. 크롤러는 다음과 같이 고루틴을 이용하여 비동기적으로 동시에 구동되도록 했다.

\n
package main\n\nimport "time"\n\nfunc crawlWorker() {\n    for {\n        time.Sleep(time.Hour * 1)\n        fetchAllYears()\n    }\n}\n\nfunc setupCrawller() {\n    go crawlWorker()\n}
\n

Docker를 이용한 테스팅

\n

DigiCert CA 인증서와 관련된 문제가 있어 해당 CA 인증서를 추가하여 문제를 해결했었다. 물론 HTTP 요청시 인증서 오류를 모두 무시하도록 하는 방법도 있지만, 그 방법은 보안이 취약해지기에 채택하지 않았다.

\n

다만 이렇게 CA 인증서를 추가하는 식으로 해결할 시에는 go test -v 명령어만으로 테스트를 할 수 없다는 문제점이 있었다. 그래서 Docker를 테스트에도 활용할 수 있도록 다음과 같이 Dockerfile을 수정했다.

\n
FROM golang:alpine AS base\n\nWORKDIR /app\n\n# To avoid tls error from swedu.cau.ac.kr\nCOPY digicert-ca.pem /usr/local/share/ca-certificates/digicert-ca.crt\nRUN cat /usr/local/share/ca-certificates/digicert-ca.crt >> /etc/ssl/certs/ca-certificates.crt\n\nCOPY go.mod go.sum ./\nRUN go mod download && go mod verify\n\nCOPY static ./static\nCOPY *.go ./\n\nFROM base AS deployment\nRUN go build -v -o /app/app\nCMD ["/app/app"]\n\nFROM base As test\n\nRUN go test -v ./...
\n

서버리스

\n

위와 같이 만들어서 굴리다가 추후 AWS Lambda 함수를 이용한 서버리스로 재작성했다. 이에 대해선 다음 글에서 이어서 작성하도록 하겠다.

\n", "url": "https://blog.litehell.info/post/caucalendar_1", "title": "학사일정 ICS 서비스 개발기 (上)", "summary": "Javascript랑 함께 Docker로", "image": "https://blog.litehell.info/img/puang_network/caucalendar_first_commit_html.png", "date_modified": "2024-09-15T13:06:50.914Z" }, { "id": "bidrum_on_rust", "content_html": "

들어가는 글

\n

※ 참고: 이 시리즈의 글은 시간순 작성을 최대한 목표하고 있으나, 글의 짜임새나 가독성을 위해 미리시점이나 과거시점의 이야기가 섞이거나 순서가 일부 달라질 수 있습니다.

\n

내가 만들고 싶은 게임은 아케이드 리듬게임이였다. 마침 내 방에 라즈베리 파이(4B Rev 1.2)가 있어서, 라즈베리 파이로 게임을 구동하고 싶었다. Unity나 Unreal Engine으로 만든 게임이 라즈베리 파이 위에서 돌아갈까? 아마도 돌아가지 않을 것이다. 실제로 돌려본 적은 없지만 라즈베리 파이가 성능이 좋은 편은 아니니까.

\n

그래서 필자는 마침 Rust 프로그래밍 언어를 한 번 써보고 싶은 생각도 있었기에, Rust로 직접 게임을 만들어보기로 결심했다. 게임 엔진을 쓰면 나중에 게임이 유명해졌을 때 라이선스비를 내야 한다는 것도 하나의 이유였다. (좀 과한 김칫국이긴 하지만...) 그래서 유니티니 언리얼이니 하는 게임 엔진을 쓰지 않고 Rust로 기초부터 쌓아올리기 시작했다.

\n

SDL2를 이용한 게임 프로그래밍

\n

Rust에는 rust-sdl2라는 라이브러리 바인딩이 있다. SDL2는 오디오, 키보드, 마우스, 그래픽, 조이스틱을 다를 수 있게 하는 크로스플랫폼 라이브러리이다. 즉, 그래픽이나 마우스 등에 대한 Direct3D/OpenGL 등의 운영체제/플랫폼 종속적인 API를 추상화하고 단일한 인터페이스로 통일하여 크로스 플랫폼으로 개발할 수 있게 하는 라이브러리이다.

\n

따라서 SDL2를 이용하면 Mac OS X/Windows/Linux에서 크로스플랫폼으로 실행할 수 있고, 라이브러리도 별로 무겁지 않다. 그래서 SDL2를 이용하게 됐다.

\n

SDL2

\n

SDL2는 rust-sdl 레포 내의 예제 코드를 보면서 따라하면 사용하기 쉽다. SDL2는 먼저 Window(창)을 만든 뒤, Window의 Canvas에 원하는 것을 그리고 렌더링하고 클리어하는 것을 반복한다. 이를 순서대로 나타내면 다음과 같다.

\n
\n

(A) Window 생성 → (B) Window의 Canvas를 Clear한다 → (C) Canvas에 뭔가를 그린다. → (D) Window의 Canvas를 Present한다. → (E) 화면이 표시한다. → (F) B로 되돌아간다.

\n
\n

학부 수준의 컴퓨터그래픽스 수업을 들었거나 OpenGL 프로그래밍을 조금이라도 맛보았다면 매우 이해하기 쉬울 것이다.

\n

SDL2는 키보드나 조이스틱 인식을 위한 EventPump 기능을 제공한다. 키보드 인풋이 들어오면 EventPump에 Event가 생성된다. 게임은 이 EventPump에 Event가 있는지 확인하여 만약 Event가 있다면 해당 Event의 데이터를 활용해 키보드 인풋을 처리할 수 있다. 게임 특성상 이 EventPump은 디버깅 용으로만 주로 이용됐다.

\n

GameCommonContext

\n

위에서 언급한 바에 같이 그래픽을 렌더링하기 위해서는 Canvas 객체에 대한 접근이 필요하다. 그래서 초기 게임 초기화시 Canvas와 Window, SDL Context 등을 담은 GameCommonContext 객체를 만들고 이를 함수간에 서로 주고받는 형태로 빠르게 게임을 구현했다.

\n
use kira::manager::AudioManager;\nuse sdl2::{render::Canvas, EventPump, video::Window};\n\npub(crate) struct GameCommonContext {\n    pub(crate) coins: u32,\n    pub(crate) price: u32,\n    pub(crate) sdl_context: sdl2::Sdl,\n    pub(crate) audio_manager: AudioManager,\n    pub(crate) canvas: Canvas<Window>,\n    pub(crate) event_pump: EventPump,\n}
\n

(속성은 나중에 게임이 개발되면 될 수록 더 늘어난다.)

\n

그리고 이는 추후 대규모 리팩토링으로 Rust의 특징을 온몸으로 깨닫는 계기가 됐다.... 나중에 소유권이랑 lifetime 문제가 미친 듯이 터져나와서 그거 씨름하느라 엄청 고생하게 됐다.

\n

Rust에서의 시리얼 통신

\n

게임을 장구 하드웨어와 연동하기 위해서는 시리얼 통신이 필요하다.

\n

Arduino Leonardo 등 HID 에뮬레이션을 지원하는 보드가 있으면 장구 하드웨어에서 키보드 인풋을 주도록 할 수도 있다. 그런데 당장 내 방에 있는 게 Arduino UNO 호환보드(흔히 "짭두이노"라고 불리는 보드)밖에 없었다. 그래서 시리얼 통신으로 구현했다. 나중의 미래에 레오나르도 보드를 사서 키보드 인풋으로도 구현해보긴 했는데.... 딱따구리마냥 장구 채를 갖다대기만 해도 장구 채를 미친듯이 연타한 것마냥 동작하는 버그가 있어서 그냥 시리얼 통신을 계속 쓰게 됐다.

\n

Rust에서는 시리얼 통신을 어떻게 할까? 고맙게도 serialport라는 라이브러리가 있다. 이를 이용해 장구 하드웨어와의 시리얼 통신 코드를 작성했다. 그리고 장구 하드웨어로부터 인풋을 읽는 코드를 멀티쓰레딩으로 분리하고 AtomicU8을 이용하여 게임 쓰레드와 인풋 쓰레드 간에 장구 인풋 상태를 서로 공유했다.

\n

왜 뜬끔없이 AtomicU8이냐? 장구 컨트롤러의 상태를 나타내는 데에는 4개의 비트만 있으면 충분하다. 그래서 컨트롤러는 1바이트의 데이터를 무한히 연속적으로 보낸다. 이 1바이트를 해석하는 코드는 다음과 같다.

\n
pub(crate) fn parse_janggu_bits(bits: u8) -> JangguState {\n    JangguState {\n        궁채: if bits & 1 != 0 {\n            Some(DrumPane::채편)\n        } else if bits & 2 != 0 {\n            Some(DrumPane::북편)\n        } else {\n            None\n        },\n        북채: if bits & 4 != 0 {\n            Some(DrumPane::채편)\n        } else if bits & 8 != 0 {\n            Some(DrumPane::북편)\n        } else {\n            None\n        },\n    }\n}
\n

장구 컨트롤러와 연동하는 쓰레드는 컨트롤러로부터 시리얼 통신으로 받은 데이터를 바로 GameCommonContext 개체의 janggu_bits_ptr 필드에 저장한다.

\n
// ... (생략) ....\n\npub(crate) struct GameCommonContext {\n   // ... (생략) ...\n   pub(crate) janggu_bits_ptr: Arc<AtomicU8>,\n}\n\nimpl GameCommonContext {\n    pub(crate) fn read_janggu_state(&self) -> JangguState {\n        return parse_janggu_bits(\n            self.janggu_bits_ptr\n                .load(std::sync::atomic::Ordering::Relaxed),\n        );\n    }\n}\n
\n

게임 쓰레드에서 장구의 상태를 확인할 때는 read_janggu_state 메소드를 이용한다.

\n

추후 미래에 AtomicU8을 없애고 Product-Consumer Lock으로 JangguState 객체를 직접 공유해보기도 했는데, 렉이 너무 심해서 그냥 AtomicU8을 계속 쓰게 됐다.

\n

결론

\n

Rust를 이용한 게임 개발은 초창기에는 할만했는데 뒤로 갈수록 어려웠다. 소유권, 대여, lifetime 문제를 해결하느라 골머리를 참 많이 썩었다. 그래서 결론적으로 짧은 기간 안에 게임을 개발해야 한다면 Rust는 별로 좋은 선택이 아니지 않을까라는 생각이 들었다.

\n", "url": "https://blog.litehell.info/post/bidrum_on_rust", "title": "Rust와 SDL2", "summary": "게임 개발에 Rust를 써보셨나요?", "image": "https://gravatar.com/avatar/837266b567b50fd59e72428220bf69b1", "date_modified": "2024-08-11T14:35:24.968Z" }, { "id": "bidrum_and_janggu_controller_prototyping", "content_html": "

서문

\n

문득 장구를 이용한 리듬 게임을 만들고 싶었다. 태고의 달인도 있는데 장구의 달인이 안 될 이유가 있을까? 그래서 군대 있을 때 계속 장구 게임을 상상만 하다가 복학하고 3학년 2학기에 캡스톤디자인 과목을 듣게 됐다.

\n

3학년 2학기 캡스톤디자인 과목에서 게임 아이디어를 제시했지만, 다른 팀원들은 리스크가 너무 크다고 반대했다. 지금 막 개강했는데 하드웨어 개발부터 시작하면 십중팔구 프로젝트가 망할 것이라는 지적이였다. 반박하기에는 너무나도 맞는 말이였다. 그래서 그냥 받아들이고 "알고모여"라는 웹 어플리케이션 프로젝트를 하게 됐다. 의외로 교수님의 평가가 계속 호평이여서 손쉽게 A+를 받았다.

\n

중앙대학교는 캡스톤디자인을 2번 해야 졸업이 가능하다. 그래서 4학년 1학기에도 캡스톤디자인을 해야 했는데, 이때 게임 아이디어를 다시 꺼내고 싶었다. 그러나 하드웨어도 없이 빈손으로 아이디어를 꺼낸다면 또다시 팀원들로부터 리스크 우려를 받을 것 같았다. 그래서 일단 프로토타이핑을 해서 설득해야 되겠단 판단이 들었다. 판단이 이루어졌으면 바로 실천해야 하지 않겠는가? 바로 당근마켓으로 45,000원짜리 어린이 장구를 샀고, 곧이어 아두이노랑 쿠킹호일, 그리고 몇가지 부품을 구매했다.

\n

프로토타이핑

\n

유선과 무선

\n

내가 만드는 게임은 단순히 타격 여부만을 인식하지 않는다. "어떤 채로 어떤 면이 타격됐는가"를 인식한다. 즉, 다시 말해 열편으로 궁편을 친다면 "궁편이 타격됐다"라는 정보가 아닌 "열편으로 궁편이 타격됐다"라는 정보가 인식된다.

\n

이를 구현하려면 채에도 센서가 있어야 된다. 그리고 이 센서는 게임 본체랑 연결되어야 한다. 어떻게 연결한 것인가? 무선과 유선 두가지 방법이 있다.

\n

무선과 유선 각 두 가지 방법의 장단점은 다음과 같다.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
무선유선
장점선이 걸리지 않음정확도가 매우 높고 딜레이가 낮음
단점너무 낮은 정확도 및 높은 딜레이선이 걸리적거림
\n

무선 통신은 정확도가 너무 낮다는 단점이 있다. 무선으로 채의 위치를 측정한다고 가정해보자. Inpixion의 자료에서의 무선 위치 측정 시스템의 정확도와 레어턴시는 다음과 같다.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
UWBChirp (CSS)BLEWi-Fi
정확도10~50 cm1~2m< 5 m10 m
레이턴시< 1 ms< 1 ms3~5s3~5s
\n

정확도가 너무 낮아서 무선 위치 측정 시스템은 쓸 수가 없다. 블루투스와 와이파이는 레이턴시때문에 리듬게임 컨트롤러로 쓸 수조차 없다.

\n

'그렇다면 무선이되 다른 방법을 쓰면 되는 것 아닌가?'라는 생각이 들 수도 있는데, 내 머리로는 그렇게 할 수 있는 방법이 딱히 떠오르지 않았다. 그래서 그냥 유선으로 연결하기로 하고, 걸리적거리는 문제는 나중에 해결하기로 했다.

\n

시분할

\n

자, 이제 유선으로 채의 종류를 인식하기로 했다. 장구는 두 개의 채와 두 개의 접촉면이 있다. 이 경우 가능한 경우의 수는 총 몇 가지인가? 답은 아래 표에서 볼 수 있듯 9가지이다. 두 개의 채로 하나의 접촉면을 동시에 치는 경우(아래 표에서 5번, 9번)도 고려해야 하므로 9가지이다.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
궁채북채
1XX
2궁면X
3북면X
4X궁면
5궁면궁면
6북면궁면
7X북면
8궁면북면
9북면북면
\n

전기는 색깔이 없다. 접촉면에서 흐르는 전기가 궁채에서 흐르는 전기인지 북채에서 흐르는 전기인지 알 수 없다. 그렇다면 접촉면에 닿은 채가 어떤 채인지 어떻게 구분해야 할까? 필자는 FDM(주파수 분할)과 TDM(시분할)을 생각했다. 처음엔 FDM을 생각했었는데, 아두이노로 구현하기에는 난이도가 높을 것 같아 비교적 구현이 손쉬운 TDM으로 결정했다.

\n

알고리즘

\n

시분할 알고리즘은 매우 단순하다.

\n
    \n
  1. 궁채에 전기를 흘리고 북채에 전기를 흘리지 않는다.
  2. \n
  3. 북면에 전기가 흐르는 지 확인한다. 북면에 전기가 흐르면 궁채는 북면에 접촉하고 있다.
  4. \n
  5. 궁면에 전기가 흐르는 지 확인한다. 궁면에 전기가 흐르면 궁채는 궁면에 접촉하고 있다.
  6. \n
  7. 북채에 전기를 흘리고 궁채에 전기를 흘리지 않는다.
  8. \n
  9. 북면에 전기가 흐르는 지 확인한다. 북면에 전기가 흐르면 북채는 북면에 접촉하고 있다.
  10. \n
  11. 궁면에 전기가 흐르는 지 확인한다. 궁면에 전기가 흐르면 북채는 궁면에 접촉하고 있다.
  12. \n
  13. 2, 3, 5, 6번에서의 정보를 종합하면 어떤 채가 어떤 접촉면에 접촉하고 있는 지 알 고 있다.
  14. \n
\n

이를 도식도로 나타내면 다음과 같다.

\n

\"알고리즘의

\n

위를 실제 회로로 구현하기 위해서는 특정 회로를 신호로 열거나 닫을 수 있어야 한다.

\n

릴레이

\n

특정 회로를 신호로 열거나 닫는 대표적인 부품은 릴레이이다. 릴레이는 일종의 스위치 역할을 하는 부품으로, 전자석을 이용하여 회로를 열거나 닫는다.

\n

\"아두이노

\n

매우 직관적이고 만들기 쉬워서 처음에 릴레이를 시도했었다. 그러나 문제가 있었다. 릴레이는 딜레이가 너무 크고(5ms) 결정적으로 딱따구리 같은 소음이 난다. 위 알고리즘을 빠르게 무한반복해야 하는 특성상 릴레이 열고닫기를 반복하니 전자석 딱딱거리는 소리가 무한히 들리는 것이었다.

\n

트랜지스터

\n

그래서 주변 분들의 조언을 받아 트랜지스터를 이용했다. 주변 분께 딜레이가 1ms이하인 릴레이가 있나고 여쭤보니, 그 분께서 그런 릴레이는 없으니 트랜지스터를 쓰라고 답변해주신 것이 큰 도움이 됐다. 트랜지스터는 라디오 만들 때나(증폭) 쓰는 건 줄 알았는데 검색해보니 트랜지스터도 스위치처럼 쓸 수 있음을 알게 됐다.

\n

NPN 트랜지스터와 저항으로 약간의 시행착오를 겪으니 잘 인식됐다. 트랜지스터를 이용한 회로는 아래와 같다.

\n

\"트랜지스터를

\n

위 회로에서 궁채, 열채, 북편, 채편 기호는 각각의 부위에 붙은 전도체를 의미한다.

\n

전도체

\n

전기가 흘려야 하니 채와 접촉면에는 전도체 물질을 부착해야 한다. 뭘 붙일까 고민하다가 펌프 발판을 수작업으로 제작할 때 은박지를 이용했다는 글이 생각났다. 그래서 다이소에서 쿠킹호일을 사다가 붙였다. 내구성은 썩 좋은 것 같진 않았지만 꽤 잘 인식됐다.

\n

\"장구

\n

사진은 위와 같다. 사진에는 보이지 않지만, 장구의 면에도 은박지가 부착되어 있다.

\n

결론

\n

쿠킹호일과 NPN 트랜지스터, 저항, 그리고 아두이노를 이용해 장구 컨트롤러를 만들었다. 내구성은 썩 좋지 않았지만 인식은 잘 됐다. 2023년 겨울방학의 일이었다.

\n", "url": "https://blog.litehell.info/post/bidrum_and_janggu_controller_prototyping", "title": "쿠킹호일과 트랜지스터로 리듬게임 컨트롤러 만들기", "summary": "학교 캡스톤디자인 작품으로 장구 리듬게임 만든 이야기", "image": "https://blog.litehell.info/img/bidrum/controller-algorithm.png", "date_modified": "2024-07-19T12:53:12.021Z" }, { "id": "show_all_slides_of_pptx_and_convert_to_pdf_batch_operation", "content_html": "

서문

\n

이번 학기에 데이터베이스시스템 과목을 수강하게 됐다. 이 과목의 강의자료는 교재 홈페이지에서 제공하는 pptx 파일을 이용하는데, 숨김 처리된 슬라이드도 모두 활용한다. 따라서 숨겨진 슬라이드를 모두 숨김 해제해야 했다.

\n

파일이 한두개면 그냥 직접 숨겨진 슬라이드를 숨김 해제하면 된다. 하지만 강의가 시작되는 Chapter 12이후의 파일은 약 20개 정도였다. 물론 그 pptx들을 다 강의하진 않겠지만, 한두개의 파일이 아닐 것임은 확실했다.

\n

이걸 어떻게 하면 일괄처리할 수 있을까?

\n

해결

\n

pptx 파일의 구조

\n

pptx 파일은 zip 파일이다. pptx파일을 압축 프로그램을 열면 다음과 같은 구조를 볼 수 있다.

\n

\"pptx파일을

\n

ppt/slides 디렉토리 내의 xml 파일들이 슬라이드를 나타내는 xml 파일이다. 숨김 처리된 슬라이드의 xml 파일을 보면 다음과 같이 루트 요소의 show 속성이 0으로 설정되어 있음을 확인할 수 있다.

\n

\"숨겨진

\n

그렇다면 ppt/slides 디렉토리 내의 xml 파일의 루트 요소에서 show 속성만 제거하면 되지 않을까?

\n

Python 스크립팅

\n

python을 이용하면 기본으로 제공되는 라이브러리만으로 간단히 해결할 수 있다.

\n
#!/bin/python3\nimport re\nfrom os import listdir\nfrom os.path import isfile, join\nfrom zipfile import ZipFile\nimport xml.etree.ElementTree as ET\n\npptx_dir = "."\n# pptx_dir 디렉토리 내에서 파일명이 .pptx로 끝나는 파일의 목록을 가져온다.\npptx_files = [f for f in listdir(pptx_dir) if isfile(join(pptx_dir, f)) and f.endswith(".pptx")]\n# ppt/slides/*.xml 패턴을 검사하기 위한 정규표현식\nslide_xml_filename_pattern = re.compile("ppt/slides/[^/]+\\\\.xml")\n\nfor pptx_file in pptx_files:\n    print("Processing pptx %s " % pptx_file)\n    # pptx파일을 zip 파일로 연다.\n    with ZipFile(pptx_file, 'a') as zipfile:\n        # ppt/slides/*.xml 형태의 파일 목록을 가져온다.\n        slide_xml_filenames = [i for i in zipfile.namelist() if not slide_xml_filename_pattern.fullmatch(i) is None]\n        for slide_xml_filename in slide_xml_filenames:\n            print("Processing xml %s" % slide_xml_filename)\n            xml = None\n            # xml을 파싱한다.\n            with zipfile.open(slide_xml_filename, mode = 'r') as file:\n                xml = ET.parse(file)\n            # 루트 요소에 show 속성이 있다면 제거한다.\n            if "show" in xml.getroot().attrib:\n                xml.getroot().attrib.pop("show")\n            # 수정된 xml을 pptx 파일 내에 쓴다.\n            with zipfile.open(slide_xml_filename, mode = 'w') as file:\n                xml.write(file)
\n

위 스크립트는 디렉토리내의 pptx 파일을 열고, pptx 파일 내에서 파일명이 ppt/slides/*.xml 형태인 파일을 xml로 파상한 뒤, 루트 요소에서 show 속성을 삭제하고 다시 쓰는 것을 일괄 반복하는 스크립트이다.

\n

사용 시에는 pptx_dir 변수값만 필요에 따라 수정하여 쓰면 된다. 위 스크립트를 실행하면 pptx_dir 변수에 설정된 디렉토리 내에 있는 pptx 파일들에서 숨김 처리된 슬라이드를 모두 숨김 해제한다.

\n

pptx ➡️ pdf 일괄 변환

\n

필자는 OneNote를 쓰는데 OneNote는 인쇄물 삽입을 pdf나 docx로만 해야한다. 따라서 모든 pptx를 pdf로 변환할 필요가 있다.

\n

이건 쉽다. 그냥 LibreOffice 명령어 한 줄이면 끝난다.

\n
libreoffice --headless --convert-to pdf *.pptx
\n

결론

\n

이상으로 여러개의 pptx 파일에서 숨김 처리된 슬라이드를 모두 일괄 숨김 해제하고 pdf로 일괄 변환하는 방법에 대해 알아보았다.

\n

다른 사람들에게 도움이 됐으면 좋겠다.

\n", "url": "https://blog.litehell.info/post/show_all_slides_of_pptx_and_convert_to_pdf_batch_operation", "title": "PPTX 파일 모든 슬라이드 숨김 해제하고 PDF 변환 일괄 작업하기", "summary": "Python과 LibreOffice를 이용한 pptx 일괄 처리", "image": "https://blog.litehell.info/img/show_all_slides_of_pptx_and_convert_to_pdf_batch_operation/pptx_zip_structure.png", "date_modified": "2024-03-06T08:33:00.099Z" }, { "id": "retrospective_of_2023", "content_html": "

들어가는 글

\n

2023년은 포스트코로나(POST COVID-19)의 해였다. 전역한 자에게 사회의 공기는 상쾌했고, 그리웠던 사람들을 오랜만에 볼 수 있어 좋았다. 그리고 2023년은 유난히 바쁜 해였다. 중요한 전공 과목들을 본격적으로 배우고, 첫 캡스톤디자인을 시작했다. 지난 날들을 되짚으며, 회고를 2023년 12월 30일쯤에 쓰기 시작했는데... 현생이 바빠서 못 쓰다가 이제서야 퇴고를 하게됐다. 두서없는 글이지만, 넓은 아량으로 읽어주셨으면 좋겠다.

\n

2023-1학기

\n

편의상 여름방학때 한 일도 이 문단에 같이 적겠다.

\n

학생자치 당선

\n

필자는 2020년에 중앙동아리 부회장을 한 경력이 있다. 이 경력을 바탕으로 동아리연합회 분과장 보궐선거에 출마했다. 예상 외로 영화동아리에서 후보가 출마해 혹시나 하는 마음이 들었지만, 경선에서 가볍게 압승했다.

\n

학생자치의 경험은 꽤 재밌었지만, 아쉬운 점도 많았다. 이에 관한 것들은 이야기거리가 많으니 차후에 별도의 글로 쓰도록 하겠다.

\n

알고리즘 학회 홈페이지 디자인 개편

\n

\"ChAOS

\n

중앙대학교 소프트웨어학부 알고리즘 학회 ChAOS의 부회장이 되면서 디자인을 간단하게 개편했다. Bootstrap 같은 라이브러리를 쓰진 않았고 그냥 HTML과 CSS로 간단하게 작성했는데, 나름 깔끔하게 잘 뽑혔다고 생각한다. 반응형이라서 모바일에서도 잘 보인다.

\n

SketchDaily references 앱 출시

\n

필자는 SketchDaily references 사이트를 이용한다. 데스크톱에선 좋은데, 모바일에선 사소한 버그가 있었다. 마침 Flutter에 관심이 있어서 이 웹사이트를 앱으로 만들어보면 좋을 것 같다는 생각이 들었고, 사이트 운영자에게서 허락을 받았다.

\n
\n

Hi there

\n

I'm ok with you doing it as long as:

\n
    \n
  1. it's free
  2. \n
  3. no ads
  4. \n
\n

-arto

\n
\n

---- On Wed, 30 Nov 2022 07:46:30 -0700 LiteHell litehell@litehell.info wrote ---

\n
\n

Dear artomizer:

\n

I’m Yeonjin Shin, an user of SketchDaily reference site.

\n

I usually use your SketchDaily reference site on my android phone, and it’s a good website for drawing references to me. But, I think It would be better if there is a mobile app for mobile users.

\n

So, I want to make SketchDaily reference app for mobile users. I hope you to let me know if I can make a mobile app for SketchDaily reference?

\n

Sincerely,
\nYeonjin Shin

\n
\n

Yeonjin Shin
\nCSE Student; Rookie software engineer

\n

Homepage: https://litehell.info
\nGitHub: https://github.com/litehell
\nEmail: litehell@litehell.info

\n
\n
\n

2022년 12월 1일에 위와 같이 허락을 받고 개발을 시작했다. 그리고 2022년 6월 중에 개발을 마무리하고 그 다음 달에 첫 프로덕션 버전을 배포했다.

\n

본래 앱 디자인은 다른 분께서 해주시기로 하셨으나, 불가피한 사정이 생기신 관계로 내가 직접 간단히 만들게 됐다.

\n

Google Play에 앱을 출시하면서 느낀 점은 이것저것 정책적으로 신경써야 하는 게 꽤 많다는 점이였다. 개인정보, 청소년보호... 등등 응답해야 하는 것들이 꽤 있었고 출시 이후에도 세법이나 정책에 관한 메일이 자주 날라왔다. 애플은 내가 출시를 안해봐서 모르겠다.

\n

2023년 12월 28일을 기준으로 이 앱을 설치한 사용자가 한 410명쯤 되며, 미국인이 가장 많고, 그 뒤로 인도, 멕시코, 러시아 순으로 많았다. 인터넷 없이도 작동했으면 좋겠다는 의견도 있었는데 이건 웹사이트 운영자랑 협의해야 하는 문제인지라 실현 가능할 지에 대해서 부정적이다.

\n

학교수업

\n

1학기에는 알고리즘, 운영체제, 컴파일러, 소프트웨어공학, 멀티코어컴퓨팅, 선형대수학 수업을 들었다.

\n

필자는 수학에 능숙하지 않은 관계로 선형대수학 수업에서 잘 따라가지 못할 것 같아 많은 걱정을 했는데 생각외로 할만했다. 행렬과 근사를 위한 수학이라는 느낌을 매우 강하게 받았고, 컴퓨터그래픽스를 위한 수학이라는 인상을 강하게 받았다.

\n

운영체제 수업은 Operating Systems Internals and Design Principles 교재의 PPT를 이용한 수업으로 진행됐다. 시험은 누가 누가 더 PPT를 잘 외우는 지를 겨루는 형태였다. 내용은 컴퓨터공학도라면 꼭 알아야 하는 내용이라고 생각했지만, 시험과 수업 방식이 너무 아쉬웠다. 특히 과제에 대해 필자는 세마포어를 주제로 과제를 낸다면 세마포어를 구현하는 과제가 나와야 한다고 생각하는데, 교수님께서 세마포어를 이용하는 과제를 내서셔 아쉬웠다. 그래도 교수님께서 성격은 매우 좋으셨다.

\n

소프트웨어공학에서는 다른 사람들과 함께 협업할 때 소프트웨어를 어떻게 개발하는 지, 일정 내에 복잡한 소프트웨어를 어떻게 개발하는 지, 복잡한 소프트웨어를 어떻게 하면 최대한 적은 리스크로 개발할 수 있는 지에 대해 배웠다. 수업 내용이 너무 많아서 힘들었지만 추후 회사생활에 꼭 필요하다고 생각하는 내용이였다. 다만 기존에 예정됐었던 프로젝트 과제가 취소된 것은 개인적으로 아쉬웠다.

\n

멀티코어컴퓨팅은 POSIX pthread, C++ threads, Java thread, OpenMP 등 멀티코어 컴퓨팅을 구현하는 방법과, Amhdal's Law, 데이터를 각 쓰레드에 어떻게 나누어야 하는 지, 병렬 컴퓨팅에는 어떤 종류가 있는 지 등 병렬 컴퓨팅에 대한 이론을 개괄적으로 배웠다. 과제가 많이 나왔는데 필자는 과제하는 것을 좋아하는 성격인지라 재밌었다.

\n

특히 멀티코어컴퓨팅 과제 보고서를 작성하면서 LaTeX을 적극적으로 이용하고, 친구한테 LaTeX를 설파했는데 확실히 논문 스타일의 공학 보고서를 쓰는 데에는 매우 유용했다. 코드를 명령어 하나로 삽입할 수 있다는 점, 그래프와 표를 삽입할 때 위치가 왔다갔다하면서 문서 레이아웃이 깨지는 문제가 없다는 점, git으로 버전 관리를 하기가 매우 용이하다는 점 등이 좋았다.

\n

컴파일러 수업은 오토마타(DFA, NFA 같은 것들), 번역, 최적화 등을 개론적으로 다루었다. 컴파일러 최적화가 어떻게 이루어지는 지 간략하게 알 수 있어서 좋았다.

\n

알고리즘 수업은 교수님께서 단순히 어떤 알고리즘이 있다고 암기하는 것보다는 알고리즘에 대한 접근방법과 관점을 이해시키려 한다는 느낌을 많이 받았다. 강의력이 훌륭하신 교수님이셨다.

\n

2023-2학기

\n

알고모여

\n

중앙대학교는 졸업하려면 캡스톤디자인을 2번 이상 해야 한다.

\n
\n

컴퓨터공학전문 프로그램 공학교육인증에 관한 규정

\n

제48조(졸업요건)
\n다음 각 호의 요건을 모두 충족한 경우 컴퓨터공학전문 프로그램 졸업요건을 갖춘 것으로 한다. (개정 2012.03.01, 2013.03.01, 2015.01.01, 2016.03.01, 2018.03.01)

\n

⑦ 창의적설계, 캡스톤디자인(1), 캡스톤디자인(2) 교과목을 반드시 이수하여야 한다. 창의적설계 교과목 이수 전과 캡스톤디자인(1),(2) 교과목 이수 후에 수강한 설계학점(프로젝트학점)은 인정하지 않는다.

\n
\n

그래서 방학때 (필자포함) 3인으로 이루어진 팀을 만들고 아이디어만 간단하게 정했다. 필자는 리듬게임을 좋아하는 지라 리듬게임을 만들자고 했었는데 토론 결과 아무래도 무난한 아이디어가 좋을 것 같다는 쪽으로 합의가 되어 알고모여라는 서비스를 만들게 됐다.

\n

\"알고모여의

\n

알고모여는 알고리즘 스터디를 목표로 하는 웹사이트이다. 필자는 앞서 언급했듯이 알고리즘 학회의 부회장이였다. 그래서 1학기때 알고리즘 문제풀이 스터디를 운영했었는데 스터디원이 문제를 풀었는지 확인하는 일이 은근 귀찮아서 봇을 만들어 돌렸었다. 그래서 이걸 아예 서비스화해서 스터디원과 스터디장이 알고리즘 스터디를 편하게 운영할 수 있는 웹서비스를 만들면 좋지 않겠느냐는 의견이 나왔고, 그 아이디어가 채택되어 웹개발을 하게 됐다.

\n

나는 React와 Next.js로 프론트엔드를 개발하고, 다른 팀원은 Spring Boot로 백엔드를 개발하며, 나머지 팀원은 알고리즘 문제 채점 서버를 개발했다. Next.js를 이용한 이유는 React 개발환경 구축하는 데 들이는 시간을 단축하기 위해서이다. (다른 프레임워크도 있긴 하지만 써본 경험이 있는 프레임워크를 채택하는 것이 시간 단축에 더 유리할 것이라 판단한 것도 있었다.)

\n

내가 백엔드와 프론트엔드 중 프론트엔드를 채택한 것은 백엔드를 담당한 팀원이 웹개발 경험이 별로 없어서이기 때문이다. 프론트엔드는 어느 정도의 HTML/CSS 지식과 경험을 갖추고 있어야만 (비록 미적으로 아름답지는 않아도) 그럴싸하게 완성된 결과물이 나오지만, 백엔드는 데이터베이스와 HTTP, 그리고 웹 프레임워크(e.g. Spring)에 대한 대략적인 지식만 갖추고 있어도 그럴싸하게 완성된 결과물이 나온다. 따라서 웹개발이 부족한 팀원에게는 프론트엔드보다 백엔드를 맡기는 게 더 낫겠다는 판단이 들어서 내가 프론트엔드를 맡게 됐다.

\n

캡스톤 프로젝트를 하는 과정에서 JIRA 이슈 트래커를 이용하고, Pull Request에서 최소 1인 이상 Code Review해야 Merge하는 규칙을 세워 개발했는데 확실히 도움이 많이 됐다. 이슈 트래커를 이용하니 내가 지금 무슨 일을 해야하는 지 빠르게 알 수 있었고, 특히 JIRA의 칸반 보드 디자인이 직관적으로 보기가 편했다.

\n

\"알고모여

\n

\"알고모여

\n

그리고 Pull Request에서 최소 1인 이상의 코드 리뷰를 강제함으로써 코드 퀼리티를 높일 수 있었다. 필자는 백엔드 개발도 어느정도 알았기에 백엔드 코드를 읽어서 머리속으로 화이트박스 테스트를 했고, 프론트엔드 개발을 담당한 팀원께서는 프론트엔드 개발 지식이 많은 편이 아니었기에 직접 PR을 checkout하고 실행하여 버그를 찾는 블랙박스 테스트를 주로 해주셨다. 필자는 PR에서 백엔드 개발자가 놓친 부분(검증이라던가)이나 애매모호한 변수/함수명에 대한 의견을 주로 제시했고 프론트엔드 개발자는 필자가 미처 발견하지 못한 버그를 많이 찾아주셨다.

\n

위와 같은 개발 과정을 통해 꽤 완성도 있는 결과물을 산출할 수 있었고 그 결과 캡스톤디자인 과목에서 A+를 받을 수 있었다.

\n

수업

\n

컴퓨터그래픽스 수업에서는 행렬을 이용해 3D 객체가 어떻게 2D 화면에 투영되는지, 3D 공간에서의 변형(회전, 이동 등)이 행렬을 이용하여 어떻게 이루어지는 지부터 시작하여 OpenGL을 이용한 쉐이더와 기법들, 빛과 그림자, 이를 이용한 실제 OpenGL 프로그램 제작까지 했다. 컴퓨터그래픽스에 대해 무지했던더라 수업이 굉장히 재밌었고, Vertex shader와 Geometry shader를 활용한 Phong shading 등의 기법과 그림자 및 빛의 구현을 처음으로 배웠다.

\n

컴퓨터통신 수업에서는 물리 레이어부터 시작해 4계층 직전까지를 다룬다. Bottom-Up 방식으로 진행되어 컴퓨터통신 구조에 대한 전체적인 통찰을 얻기에 훌륭한 강의였다. 지금까지 기껏해봐야 추상화된 TCP/IP 소켓 API만 이용해보았기에 이더넷, Wi-Fi, IP 프로토콜의 작동 원리를 배울 수 있는 컴퓨터통신 수업에서 많은 걸 얻을 수 있었다. 비록 수업은 힘들었지만, 상위 레이어가 하위 레이어를 이용하고, Peer와 Peer 간에는 프로토콜(메세지 포맷)이 동일하다는 원칙 하에서 인터넷이 어떻게 동작하는 지 알 수 있었다.

\n

리눅스 시스템 응용설계에서는 리눅스 커널의 소스코드를 살펴보며 리눅스의 Mutex와 같은 동시성(Concurrency) 매카니즘과 프로세스 관리, 스케쥴링이 어떻게 이루어지는 지를 배웠다. 교수님께서 리눅스에 대해 진심이라는 것을 느꼈고, 리눅스 CFS의 동작 원리를 배울 수 있어 좋았다.

\n

머신러닝프로젝트와 패턴인식 과목에서는 머신러닝과 패턴인식에 대한 이론을 기초적인 수학에 기반하여 배웠다. 서로 다른 과목이긴 한데 교수님이 똑같으서셔 내용이 비슷했다. 머신러닝과 패턴인식이 무엇인지 몰랐던 필자에게 "머신러닝과 패턴인식은 단순히 어떠한 데이터(점)이 어떠한 구역(분류)에 해당되는 지를 선/평면(퍼셉트론)이나 통계적 방법론(패턴인식)을 이용하여 찾아내는 것이다."라는 거대한 통찰을 제시하고 이러한 통찰 하에 머신러닝과 패턴인식에 필요한 기초적인 수학과 이에 기반한 이론(퍼셉트론, 다층 퍼셉트론, 딥러닝)을 간략하게 가르치셨다. 필자는 인공지능에 대해 아무것도 몰랐기에 수업이 매우 보람찼다.

\n

코딩부트캠프는 코딩테스트 보는 과목이다. 코딩테스트가 너무 쉬웠기에 그냥 졸입필수과목이라서 수강했다라는 말 외에는 딱히 쓸 말이 없다.

\n

2023년 소감

\n

매우 바쁜 해였다. 동아리 부회장도 하고 동아리연합회 임원도 하고 캡스톤디자인도 하고 면접도 보러다니고 하루하루가 바빴다. 하루하루를 바쁘게 산 만큼 언젠가 보상이 왔으면 좋겠다.

\n", "url": "https://blog.litehell.info/post/retrospective_of_2023", "title": "2023년의 회고", "summary": "학생자치와 캡스톤디자인", "image": "https://blog.litehell.info/img/retrospective_for_2023/cauchaos.png", "date_modified": "2024-02-05T15:55:48.396Z" }, { "id": "receiving_gpl_from_fsf", "content_html": "

자유/오픈소스 소프트웨어 라이선스는 MIT, BSD, Apache 등 여러가지 종류의 라이선스가 있다. 이 중 가장 유명한 카피레프트 라이선스로는 Linux, GNU, gcc 등으로 널리 알려진 GNU GPL 라이선스가 있으며, notepad++, Git, uBlock Origin, MariaDB 등의 많은 프로그램에서도 채택하고 있다. 주로 GPLv2 라이선스와 GPLv3 라이선스가 많이 쓰이는 데, GPLv2의 하단부를 읽으면 다음과 같은 문구를 볼 수 있다.

\n
\n

You should have received a copy of the GNU General Public License along
\nwith this program; if not, write to the Free Software Foundation, Inc.,
\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

\n
\n

만약 GNU GPLv2 전문을 받지 못했을 시 자유 소프트웨어 재단으로 편지를 보내주면 친절히 전문을 인쇄해서 보내준다고 써져있다. 여기서 호기심이 생겼다, 진짜로 보내줄까?

\n

선지자

\n

이런 궁금증은 나만 든게 아니었다. 외국의 개발자 mendhak이 이미 이를 시도했다. 글에 나온 바에 따르면, 진짜로 보내준다!

\n

진짜로 된다는 걸 확인해보니 나도 한 번 해보고 싶어졌다. 따라서 나도 국제우편을 한 번 보내보았다.

\n

우편과 국제반신우표권

\n

먼저 주변의 우체국에 가서 국제반신우표권을 구매했다. 국제반신우표권은 수신자가 나라와 상관없이 주변 우체국에서 우표로 교환할 수 있는 일종의 교환권이다. 집 주변 가장 가까운 우체국에서는 재고가 없었기에 조금 거리가 있는 큰 우체국에 가서 1,450원을 주고 구매했다. 이 글을 읽는 여러분도 국제반신우표권을 살 생각이 있다면 미리 우체국에 재고가 있는 지 확인해보는 것을 추천한다.

\n

\"국제반신우표권의

\n

(위 사진은 도장의 지역명을 삭제한 사진이다.)

\n

국제반신우표권과 함께 간단한 편지를 작성했다. 그 편지의 내용은 다음과 같다.

\n
\n

Dear Free Software Foundation

\n

I received a software, and its license notice said,

\n
You should have received a copy of the GNU General Public License
\nalong with this program; if not, write to the Free Software
\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
\n\n

However, the license was not provided with the software.
\nPlesae can you provide me with a copy of GNU General Public License?

\n

I have enclosed a international reply coupon, which is can be exchanged with a stamp in a nearby post office.

\n

Sincerely,\nYeonjin Shin

\n
\n

회신용 봉투를 안 넣으면 FSF 로고가 인쇄된 봉투에 담아서 줄 것 같다는 생각이 들어서 회신용 봉투는 일부러 안 넣었다. 이 편지와 국제반신우표권을 집에 있던 규격봉투(소봉투)에 집어넣고 우표를 붙였다.

\n

\"봉투와

\n

무게에 따라 다르겠지만, 미국으로 국제우편을 부치는 데에는 총 700원이 들었다. 사진에는 710원의 우표가 붙여져있지만, 부치는 데에는 700원이면 충분하다. (물론 무게에 따라 달라질 수 있다.)

\n

위와 같이 준비한 편지와 국제반신우표권을 봉투에 넣은 뒤, 2023년 7월 7일 주변 우체통에 넣어서 부쳤다. 오랜만에 우체통에 편지를 넣으니 감희가 새로웠다.

\n

\"봉함된

\n

진행

\n

지금(12월 7일)도 답장이 오지 않았다... 아무래도 중간에 유실된 것 같다. 그래도 혹시 만약에 답장이 오게 된다면 업데이트하겠다.

\n", "url": "https://blog.litehell.info/post/receiving_gpl_from_fsf", "title": "FSF에 편지 보내서 GPL 받아보기", "summary": "그러나 편지는 오지 않았다", "image": "https://blog.litehell.info/img/upu-irc-mosaic.jpg", "date_modified": "2023-12-07T14:19:37.603Z" }, { "id": "korean_style_award_with_latex", "content_html": "

들어가는 말

\n

최근 알고리즘 동아리에서 주최한 대회에 운영진으로 참가했다. 대회가 마무리된 후 1,2,3등을 위한 상장을 만들어야 했는데 이때 LaTeX를 써보면 재밌을 것 같다는 생각이 들었다.

\n

따라서 LaTeX을 이용해 아래와 같은 상장을 만들었다.

\n

TikZ

\n

TikZ는 LaTeX에서 그림을 그릴 때 쓰는 패키지이다. 주로 pgfplots와 함께 사용하거나 그래프를 그릴 때 이용되지만 간단한 그림 정도는 그릴 수 있다.

\n

current page

\n

TikZ에는 current page라는 특별한 노드가 있다. 이 노드는 current page.south westcurrent page.west와 같이 이용되며, 페이지의 모서리를 가리키고 있다.

\n

아래 예시를 보자. TikZ & PGF 메뉴얼 3.1.10버전의 260페이지에 있는 예시이다.

\n
\\begin{tikzpicture}[remember picture, overlay]\n    \\draw [line width=1mm, opacity=.25]\n        (current page.center) circle (3cm);\n\\end{tikzpicture}
\n

위 예시 코드를 빌드하면 페이지의 정중앙에 반지름이 3cm인 원이 그려진다. 위 코드에서 remember pictureoverlaycurrent page 노드를 사용하기 위해 필요하다.

\n

호 그리기

\n

TikZ는 \\draw arc (시작각도:종료각도:반지름) 형태의 명령어로 호(arc)도 그릴 수 있다.

\n

cycle

\n

cycle을 이용하면 마지막 노드에서 다시 처음 노드로 이어지는 닫힌 경로를 만들 수 있다.

\n

예시를 보면 이해가 쉽다.

\n
Without cycle\n\n\\begin{tikzpicture}\n\\draw (0, 0) -- (1, 1) -- (2, 0);\n\\end{tikzpicture}\n\nWith cycle\n\n\\begin{tikzpicture}\n\\draw (0, 0) -- (1, 1) -- (2, 0) -- cycle;\n\\end{tikzpicture}\n
\n

위 코드를 빌드하면 아래와 같은 결과가 나온다.

\n

\"tikz

\n

상장 템플릿 소스코드

\n

본 템플릿에서 그린 상장 테두리 장식은 호와 직선만을 이용해 그릴 수 있는 기하학적인 무늬(문방구 상장용지에서 흔히 볼 수 있는 무늬)이다. 따라서 TikZ를 잘 활용하면 상장 테두리 장식을 그릴 수 있다.

\n

GitHub Gist에서도 볼 수 있다.

\n
\\documentclass{minimal}\n\\usepackage{kotex}\n\\usepackage[svgnames]{xcolor}\n\\usepackage{tikz}\n\\usepackage[a4paper, top=100pt, left=80pt, right=80pt, bottom=120pt]{geometry}\n\\usetikzlibrary{calc}\n\n\\newcommand{\\thickframemargin}{40pt}\n\\newcommand{\\thickframewidth}{6pt}\n\\newcommand{\\thinframemargin}{46pt}\n\\newcommand{\\thinframewidth}{1pt}\n\\newcommand{\\framecornerradius}{30pt}\n\\newcommand{\\framecolor}{Goldenrod}\n\\begin{document}\n\\begin{tikzpicture}[remember picture, overlay]\n    % thick corner\n    \\draw[color=\\framecolor, line width=\\thickframewidth]\n     % north west rounded corner\n    ([xshift=\\thickframemargin, yshift=-\\thickframemargin-2*\\framecornerradius] current page.north west) arc (270:450:\\framecornerradius)\n    -- ([xshift=\\thickframemargin, yshift=-\\thickframemargin] current page.north west) arc (180:360:\\framecornerradius)\n     % north east rounded croner\n    -- ([xshift=-\\thickframemargin-2*\\framecornerradius, yshift=-\\thickframemargin] current page.north east) arc (180:360:\\framecornerradius)\n    -- ([xshift=-\\thickframemargin, yshift=-\\thickframemargin] current page.north east) arc (90:270:\\framecornerradius)\n     % south east rounded corner\n    -- ([xshift=-\\thickframemargin, yshift=\\thickframemargin+2*\\framecornerradius] current page.south east) arc (90:270:\\framecornerradius)\n    -- ([xshift=-\\thickframemargin, yshift=\\thickframemargin] current page.south east) arc (0:180:\\framecornerradius)\n    % south west rounded corner\n    -- ([xshift=\\thickframemargin+2*\\framecornerradius, yshift=\\thickframemargin] current page.south west) arc (0:180:\\framecornerradius)\n    -- ([xshift=\\thickframemargin, yshift=\\thickframemargin] current page.south west) arc (270:450:\\framecornerradius)\n    % cycle\n    -- cycle;\n\n\n    % thin corner\n    \\draw[color=\\framecolor, line width=\\thinframewidth]\n     % north west rounded corner\n    ([xshift=\\thinframemargin, yshift=-\\thinframemargin-2*\\framecornerradius] current page.north west) arc (270:360:\\framecornerradius)\n    -- ([xshift=\\thinframemargin+\\framecornerradius, yshift=-\\thinframemargin-\\framecornerradius] current page.north west) arc (270:360:\\framecornerradius)\n     % north east rounded croner\n    -- ([xshift=-\\thinframemargin-2*\\framecornerradius, yshift=-\\thinframemargin] current page.north east) arc (180:270:\\framecornerradius)\n    -- ([xshift=-\\thinframemargin-\\framecornerradius, yshift=-\\thinframemargin-\\framecornerradius] current page.north east) arc (180:270:\\framecornerradius)\n     % south east rounded corner\n    -- ([xshift=-\\thinframemargin, yshift=\\thinframemargin+2*\\framecornerradius] current page.south east) arc (90:180:\\framecornerradius)\n    -- ([xshift=-\\thinframemargin-\\framecornerradius, yshift=\\thinframemargin+\\framecornerradius] current page.south east) arc (90:180:\\framecornerradius)\n    % south west rounded corner\n    -- ([xshift=\\thinframemargin+2*\\framecornerradius, yshift=\\thinframemargin] current page.south west) arc (0:90:\\framecornerradius)\n    -- ([xshift=\\thinframemargin+\\framecornerradius, yshift=\\thinframemargin+\\framecornerradius] current page.south west) arc (0:90:\\framecornerradius)\n    % cycle\n    -- cycle;\n\\end{tikzpicture}\n\\fontsize{16pt}{16pt}\\selectfont 제 1 호\n\n\\vspace{32pt}\n\n\\begin{center}\n\\fontsize{48pt}{48pt}\\selectfont\n상\\hspace{1.25em}장\n\\end{center}\n\n\\vspace{50pt}\n\n\n\\fontsize{20pt}{20pt}\\selectfont\n최우수상\\hspace{\\stretch{1}}홍길동\n\n\n\\vspace{80pt}\n\n\\begin{center}\n \\fontsize{20pt}{30pt}\\selectfont\n 위 사람은 \\LaTeXe를 잘 활용하여 타의 모범이 되었으므로 이 상을 수여합니다.\n\\end{center}\n\n\n\\vspace{\\stretch{1}}\n\n\\begin{center}\n \\fontsize{20pt}{20pt}\\selectfont\n 1970년 1월 1일\n\\end{center}\n\n\n\\vspace{2em}\n\n\\begin{center}\n \\fontsize{30pt}{30pt}\\selectfont\n \\LaTeXe{} 애호가 김철수\n\\end{center}\n\n\\end{document}\n
\n

사진

\n

\"한국식

\n", "url": "https://blog.litehell.info/post/korean_style_award_with_latex", "title": "LaTeX을 이용한 한국식 상장 템플릿", "summary": "한국인이라면 한 번쯤 봤을 법한 그 양식", "image": "https://blog.litehell.info/img/latex_tikz_cycle.png", "date_modified": "2023-11-05T04:26:36.499Z" }, { "id": "docker_for_testing", "content_html": "

들어가는 글

\n

필자는 중앙대학교 공지사항을 RSS로 만들어서 구독한다. RSS로 만든 후 메신지 봇을 붙이면 알아서 알려주니 편하다.

\n

그러나 최근 해당 RSS 프로그램의 테스트가 실패하는 현상이 발견됐다. 확인한 결과, 중앙대학교 SW교육원 홈페이지의 TLS 인증서 이슈였던 것으로 확인됐다. 따라서 이를 해결하기 위해 일단 실행되고 있는 Docker 컨테이너에 직접 접근해서 해당 사이트의 CA 인증서를 설치했다.

\n

기존 테스트 방법의 한계점

\n

버그는 일단 임시방편으로 수정한 것이니 레포에는 반영되지 않았다. 따라서 테스트 실패 메일이 매일매일 내 메일함으로 전송됐다.

\n

어떻게 하면 이 버그를 수정하고 잘 테스트할 수 있을까? 먼저 이 버그를 수정하려면 Dockerfile을 수정해야 한다. Dockerfile에 다음 내용을 추가하여 Docker 이미지 빌드시 CA 인증서를 복사하도록 했다. LiteHell/cau-rss 레포의 커밋 21013a3에서 확인할 수 있다.

\n
COPY swedu-cert.pem /usr/local/share/ca-certificates/swedu-cert.crt\nRUN cat /usr/local/share/ca-certificates/swedu-cert.crt >> /etc/ssl/certs/ca-certificates.crt
\n

이제 위 버그 수정도 같이 테스트해야 한다. 아래에 있는 기존의 GitHub Action으로는 이 버그 수정을 테스트할 수 없다. go test -v ./... 명령어가 빌드된 Docker 이미지 내에서 실행되는 것이 아니기 때문이다.

\n
      - name: Build\n        run: go build -v ./...\n\n      - name: Test\n        run: go test -v ./...
\n

어떻게 하면 테스트할 수 있을까? 답은 간단하다. Docker로 테스트도 하면 된다.

\n

Docker를 이용한 테스트

\n

Multi-stage 빌드

\n

Docker는 빌드를 여러 단계로 나누어 진행할 수 있다. 아래 예시 Dockerfile을 보자.

\n
FROM node AS base\nWORKDIR /app\nADD src package.json package-lock.json tsconfig.json .\n\nRUN npm i\nRUN npm bulid\n\nCMD ["npm", "run", "start"]
\n

Typescript 프로젝트를 위한 간단한 Dockerfile이다. 이를 다음과 같이 여러개의 단계(stage)로 쪼갤 수 있다.

\n
FROM node AS base\nWORKDIR /app\nADD src package.json package-lock.json tsconfig.json .\n\nFROM base AS deps\nRUN npm i\n\nFROM deps AS build\nRUN npm bulid\n\nFROM build AS deployment\nCMD ["npm", "run", "start"]\n
\n

위와 같은 Dockerfile을 이용하면 Docker 빌드시 특정 스테이지까지만 빌드할 수 있다. 예를 들어 아래 명령어는 deps 스테이지까지만 빌드한다.

\n
docker build --target deps
\n

스테이지가 직선적이여야 할 필요는 없다. 다음과 같이 스테이지가 중간에 분기하도록 작성할 수도 있다.

\n
FROM node AS base\nWORKDIR /app\nADD src package.json package-lock.json tsconfig.json .\n\nFROM base AS deps\nRUN npm i\n\nFROM deps AS build\nRUN npm bulid\n\nFROM build AS english\nCOPY english .\n\nFROM english AS deployment-international\nCMD ["npm", "run", "start", "--lang=english"]\n\nFROM build AS korean\nCOPY korean .\n\nFROM korean AS deployment-domestic\nCMD ["npm", "run", "start", "--lang=korean"]
\n

위 Dockerfile의 경우 build 스테이지에서 english 스테이지와 korean 스테이지로 분기한다.

\n

BuildKit

\n
FROM node AS base\nWORKDIR /app\nADD src package.json package-lock.json tsconfig.json .\n\nFROM base AS deps\nRUN npm i\n\nFROM deps AS build\nRUN npm bulid\n\nFROM build AS english\nCOPY english .\n\nFROM english AS deployment-international\nCMD ["npm", "run", "start", "--lang=english"]\n\nFROM build AS korean\nCOPY korean .\n\nFROM korean AS deployment-domestic\nCMD ["npm", "run", "start", "--lang=korean"]
\n

위 Dockerfile을 가지고 아래 명령어를 실행한다고 가정해보자.

\n
docker build --target deployment-domestic
\n

위 경우 빌드에 필요한 스테이지는 base, deps, build, korean, deployment-domestic이다. 그러나 실제로 위 명령어를 실행해보면 불필요한 english, deployment-international 스테이지도 빌드하는 것을 확인할 수 있다.

\n

이는 도커 레거시 빌더를 이용하기 때문에 생기는 문제이다. Docker BuildKit은 사용되지 않는 스테이지를 자동으로 파악하여 불필요한 스테이지는 빌드를 생략한다. 따라서 Docker BuildKit을 설치한 후 다음 명령어로 빌드하면 필요한 스테이지만 빌드할 수 있다.

\n
DOCKER_BUILDKIT=1 docker build --target deployment-domestic
\n

Multi-stage 빌드를 이용한 테스트

\n

이제 Docker를 이용해 테스트를 하는 방법에 대해 알아보자. 다음은 cau-rss 레포의 Dockerfile 내용을 약간 수정한 예시이다.

\n
FROM golang:alpine AS base\nWORKDIR /app\n\nCOPY go.mod go.sum ./\nRUN go mod download && go mod verify\n\nCOPY cau_parser ./cau_parser\nCOPY server ./server\n\n# To avoid tls error from swedu.cau.ac.kr\nCOPY swedu-cert.pem /usr/local/share/ca-certificates/swedu-cert.crt\nRUN cat /usr/local/share/ca-certificates/swedu-cert.crt >> /etc/ssl/certs/ca-certificates.crt\n\nCOPY static ./static\nCOPY html ./html\n\nCOPY *.go ./\n\nFROM base AS build\nRUN go build -v -o ./app ./\nCMD ["/app/app"]\n\nFROM base AS test\nRUN ["go", "test" ,"-v", "./..."]\n
\n

base 스테이지에서 의존성을 설치한 뒤 각종 필요한 파일들을 복사하고 TLS 인증서 오류 해결을 위한 CA 인증서를 복사한다. test 스테이지는 base 스테이지에서 테스트 명령어를 실행하는 스테이지이며, build 스테이지는 base 스테이지를 바탕으로 도커 이미지를 빌드하는 스테이지이다.

\n

따라서 위 Dockerfile을 이용해 build 스테이지까지 빌드하면 도커 이미지를 만드는 것이며, test 스테이지까지 빌드하면 테스트를 실행하게 되는 것이다. 이를 명령어로 나타내면 다음과 같으며, 캐시로 인해 테스트가 진행되지 않는 것을 방지하기 위해 --no-cache 매개변수를 추가했다.

\n
# Test\nDOCKER_BUILDKIT=1 docker build --no-cache --target test .\n\n# Build\nDOCKER_BUILDKIT=1 docker build --target build
\n

테스트 실패시 Docker 빌드 오류가 발생한다. 이를 응용하면 다음과 같이 테스트 성공시 빌드를 진행하고, 실패시 오류 메세지를 출력하는 bash 스크립트를 작성할 수 있다.

\n
export DOCKER_BUILDKIT=1\n\ndocker build --no-cache --target test .\ntest_status=$?\nif [ $test_status -eq 0 ]; then\n  docker build --taget build . --tag example-application\nelse\n  echo "ERROR while testing!"\nfi
\n

Github Action을 이용한 활용

\n

GitHub Action을 이용하면 다음과 같이 push시 테스트가 이루어지도록 할 수 있다.

\n
name: Test\non: push\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Test\n        run: docker build --no-cache --target test .\n        env:\n          DOCKER_BUILDKIT: 1\n
\n

빌드도 잘 되는지 확인하고 싶다면 빌드하는 job을 하나 더 추가하면 된다.

\n
name: Build and test\non: push\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Build\n        run: docker build --target build .\n        env:\n          DOCKER_BUILDKIT: 1\n\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Test\n        run: docker build --no-cache --target test .\n        env:\n          DOCKER_BUILDKIT: 1\n
\n

결론

\n

Docker 이미지로 배포를 진행하는 경우, Docker로 테스트도 같이 진행하면 실제 배포 환경과 유사한 환경에서 테스트를 진행할 수 있다는 큰 장점이 있다. 따라서 복잡한 어플리케이션이라면 이 글을 참고해 Docker로 테스트도 같이 하는 것이 좋은 선택이 될 수 있다.

\n", "url": "https://blog.litehell.info/post/docker_for_testing", "title": "Docker로 테스트하기", "summary": "Docker로 빌드만 하지 말고 테스트도 하자", "image": "https://gravatar.com/avatar/837266b567b50fd59e72428220bf69b1", "date_modified": "2023-10-25T14:49:49.041Z" }, { "id": "fcitx5_for_101_key_keyboard_kde_laptop", "content_html": "

서론

\n

필자는 초창기에 ibus를 썼었다. ibus는 웬만한 프로그램에서 아무 버그없이 잘 작동한다. 딱 한가지, 리브레오피스에서 공백 입력이 안 된다는 치명적인 버그만 빼면 말이다.

\n

그래서 ibus 다음으로 하모니카에서 유지보수하는 nimf를 썼었다. nimf는 리브레오피스에서의 치명적인 버그는 없었지만, 엔터키를 누르면 텍스트가 사라지는 버그가 있었다. 근데 이 버그, 처음에만 짜증나지 좀 지나면 적응된다. 그래서 적응해서 쓰다가 생각해보니 '이건 좀 아닌 것 같다'싶어서 다른 입력기를 설치했다.

\n

본 블로그 글은 Arch Linux를 기준으로 설명한다.

\n

KDE에서의 키보드 레이아웃

\n

입력기를 바꾸기 위해 삽질하는 과정에서 한글키가 오른쪽 Alt키로 인식되는 현상을 확인했다. 분명히 아치 리눅스 설치 초기에 매핑을 했었는데, 시스템 업데이트를 하는 과정에서 원상복구가 된 것 같다. 그래서 이번에는 KDE 설정 프로그램을 이용해 한글키와 한자키를 매핑했다.

\n

\"KDE

\n

위와 같이 시스템 설정 프로그램의 입력 장치 🠞 키보드 화면에서 오른쪽 Alt 키를 한/영 키로 만들기, 오른쪽 Ctrl 키를 한자 키로 만들기 항목을 체크하면 된다. (키보드 레이아웃에 따라 약간 다를 수 있다.) 노트북 등의 101/104키 호환 레이아웃이라면 위 과정을 반드시 거쳐야 한다.

\n

한/영, 한자키 인식여부 확인방법

\n

본인 키보드가 101/104키인지 106키인지 헷갈린다면 키보드 키 갯수 세지말고 먼저 xev 프로그램을 설치한다.

\n
sudo pacman -S xorg-xev
\n

그리고 콘솔 창에서 xev 프로그램을 실행한다.

\n
xev
\n

xev 프로그램 창을 활성화하고 한글키랑 한자키를 눌러본다. 다음과 같이 콘솔 창에 Hangul이나 Hangul_Hanja키가 인식된 메세지가 출력되면 한/영 키, 한자 키가 정상적으로 인식되는 것이다.

\n
KeyRelease event, serial 39, synthetic NO, window 0x9000001,\n    root 0x79b, subw 0x0, time 1234567, (-10, 10), root:(10, 10),\n    state 0x0, keycode 108 (keysym 0xff31, Hangul), same_screen YES,
\n
KeyPress event, serial 39, synthetic NO, window 0x9000001,\n    root 0x79b, subw 0x0, time 1234567, (10, 10), root:(10, 10),\n    state 0x0, keycode 105 (keysym 0xff34, Hangul_Hanja), same_screen YES,
\n

만약 위와 같은 메세지가 안 뜨고 Alt_R이나 Control_R이 인식된다면 위에 써진 내용에 따라 매핑하면 된다.

\n

fcitx5 설치 방법

\n

먼저, 다음 명령어를 실행해 fcitx5를 설치한다.

\n
sudo pacman -S fcitx5-im fcitx-hangul
\n

/etc/environment 파일에 다음 내용을 추가한다. 입력기로 fcitx를 쓰도록 지정하는 작업이다.

\n\n
GTK_IM_MODULE=fcitx\nQT_IM_MODULE=fcitx\nQT4_IM_MODULE=fcitx\nQT5_IM_MODULE=fcitx\nXMODIFIERS=@im=fcitx
\n

그 다음에 ~/.xprofile 파일에 다음 내용을 추가한다. 부팅시에 fcitx5가 실행되도록 한다.

\n
fcitx5 -d
\n

재부팅하고 env | grep fcitx 명령어를 실행해 환경변수가 제대로 변경됐는지 확인해보자. 제대로 변경됐다면 다음과 같이 뜰 것이다.

\n
GTK_IM_MODULE=fcitx\nQT4_IM_MODULE=fcitx\nXMODIFIERS=@im=fcitx\nQT5_IM_MODULE=fcitx\nQT_IM_MODULE=fcitx
\n

만약 환경변수가 제대로 변경되지 않았다면 ~/.xprofile 파일에서 fcitx5 -d 위에 다음 내용을 추가하고 재부팅한다. 그러면 환경변수가 정상적으로 변경될 것이다.

\n
export $(/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator)
\n

fcitx5 설정

\n

fcitx5-configtool 명령어를 실행하면 다음 창이 뜬다.\n\"fcitx5

\n

위 화면에서 한국어가 안 보이면 입력기 추가버튼을 눌러서 추가한다. (입력기 추가 화면에서 한국어가 안 보이면 현재 언어만 표시 옵션을 해제하면 된다.)

\n

밑에서 전역 옵션 구성하기... 버튼을 누르면 다음 화면이 뜬다.

\n

\"fcitx5

\n

Trigger Input Method가 한/영을 전환하는 단축키 설정이다. 오른쪽의 + 버튼을 눌러 한글 키를 추가하면 된다.

\n

fcitx5는 기본적으로 한/영을 전환할때 작은 툴팁을 표시한다. 거슬리면 위 화면에서 Show Input Method Information when switch input method를 체크 해제하면 된다.

\n

이제 한글 입력을 버그없이 잘 할 수 있게 됐다. 끝!

\n", "url": "https://blog.litehell.info/post/fcitx5_for_101_key_keyboard_kde_laptop", "title": "한글 입력을 위한 fcitx5 설치", "summary": "KDE 노트북에서의 버그없는 한글 입력을 위한 삽질기", "image": "https://blog.litehell.info/img/kde_keyboard_settings.png", "date_modified": "2023-10-17T12:34:16.790Z" } ] }