이 블로그는 하루 만에 만들었다. 아침에 빈 솔루션을 만들었고, 저녁에는 운영 서버에서 방문 통계까지 돌아가고 있었다. 그 사이에 운영 배포를 했고, 프레임워크를 통째로 바꿨고, 디자인을 한 번 갈아엎었다.
하루치 작업이라 쉬웠을 것 같지만, 실제로는 장애가 몇 번 났다. 사이트 전체가 500을 낸 적도 있고, 배포 비밀번호가 로그에 평문으로 찍힌 적도 있다. 이 시리즈에서는 그날 있었던 일들을 하나씩 기록해 두려고 한다. 첫 글에서는 전체 흐름을 먼저 정리한다.
왜 직접 만들었나
블로그 서비스를 쓰면 글쓰기는 바로 시작할 수 있다. 그래도 직접 만들기로 한 데는 이유가 있다.
- 회사가 다루는 주제(IIS, ASP.NET, MySQL, 메일 서버, SSL)를 우리가 운영하는 서버 위에서 직접 굴려 보고 싶었다. 블로그 자체가 실습장이 된다.
- 애드센스, 검색 노출, 구조화 데이터 같은 요소를 원하는 대로 넣고 싶었다.
- 이미 IIS와 MySQL이 돌아가는 서버가 있어서 추가 비용이 들지 않았다.
작업 방식: 단계를 잘게 나누고, 단계마다 이슈 하나
작업은 12단계(Phase)로 나눴다. 단계마다 사내 레드마인에 이슈를 먼저 등록하고, 작업이 끝나면 fixed #이슈번호를 넣어 커밋했다. 커밋이 올라가면 이슈가 자동으로 닫힌다.
하루에 몰아서 하는 작업일수록 이 규칙이 도움이 됐다. 어디까지 했는지, 무엇을 왜 바꿨는지가 이슈와 커밋에 그대로 남는다. 지금 이 회고도 그 기록을 보면서 쓰고 있다.
코드 작성은 AI 코딩 도구(Claude Code)와 함께 했다. 단계별 요구사항을 정리해서 맡기고, 결과를 확인하면서 다음 단계로 넘어가는 식이었다. 하루 만에 끝낼 수 있었던 가장 큰 이유다. 다만 아래에 나오는 장애들은 도구가 막아 주지 못했고, 운영 환경에 올려 보고 나서야 드러났다.
12단계 타임라인
| 단계 | 내용 |
|---|---|
| 1 | 솔루션 생성. ASP.NET MVC 5 / .NET Framework 4.8, Autofac DI, Markdig |
| 2 | EF6 엔티티와 마이그레이션. 글·카테고리·태그 다대다, 개발용 MySQL은 Docker |
| 3 | 공개 화면. 목록, 글 상세, 카테고리·태그별 목록, 검색, 404 |
| 4 | 관리자. 로그인, 마크다운 에디터와 미리보기, 이미지 붙여넣기 업로드 |
| 5 | SEO. 메타 태그, Open Graph, JSON-LD, canonical, sitemap.xml, RSS |
| 6 | 애드센스 준비. 광고 위치, ads.txt, 소개·개인정보처리방침·문의 페이지 |
| 7 | 보안과 운영. HTTPS 강제, HSTS, 보안 헤더, 오류 페이지, 파일 로그 |
| 8 | 첫 운영 배포. Web Deploy 스크립트와 운영 DB 준비 |
| 9 | 배포 체크리스트 문서화 |
| 10 | ASP.NET Core 10으로 전환. 기능, URL, 운영 DB는 그대로 유지 |
| 11 | Tailwind CSS v4로 디자인 개편. 라이트·다크 모드, 블로그명 변경 |
| 12 | 방문 통계 수집과 관리자 통계 대시보드 |
8단계에서 이미 운영 배포를 마쳤는데, 10단계에서 프레임워크를 바꿨다. 같은 날 같은 사이트를 두 번 만든 셈이다. 처음에는 익숙한 MVC 5로 빠르게 완성했고, 돌아가는 것을 확인한 뒤 앞으로 오래 쓸 ASP.NET Core로 옮겼다. 이미 운영 중인 DB의 테이블 이름과 인덱스를 그대로 유지한 채 옮겨야 해서 꽤 신경이 쓰였다. 자세한 이야기는 6편에 정리했다.
그날 겪은 일들
시리즈에서 하나씩 다룬 에피소드는 다음과 같다. 제목을 누르면 해당 글로 이동한다.
- 2편: EF6와 MySQL 프로바이더의 버그 세 가지 마이그레이션 코드 생성기가 인덱스 이름에 따옴표를 빠뜨리고,
dbo.접두사 때문에 되돌리기가 실패하고, 기본값 지정이 무시됐다. - 3편: MySQL을 재시작하자 사이트 전체가 500을 냈다 MySQL 8부터 기본 인증 방식인
caching_sha2_password가 서버 재시작 직후 비암호화 연결을 거부했다.SslMode=Required한 줄로 해결했다. - 4편: 배포 로그에 비밀번호가 평문으로 찍혔다 msdeploy는 실패하면 인수 전체를 다시 출력한다. 마스킹 코드가 있었는데도 PowerShell 예외 처리 순서 때문에 소용이 없었다. 비밀번호는 바로 교체했다.
- 5편: app_offline.htm이 남아서 사이트가 503을 냈다 디자인 개편을 배포하다 동기화가 중간에 실패했고, 2~3분 동안 사이트가 내려가 있었다.
- 6편: MVC 5에서 ASP.NET Core 10으로 같은 날 전환한 이야기
- 7편: Bootstrap에서 Tailwind로 옮기면서 생긴 일 마크다운 표에 붙은
class="table"이 Tailwind의table유틸리티와 충돌했고, 코드 블록 배경이 두 겹으로 깔렸다. - 8편: 쿠키 하나로 만든 방문 통계 IP와 User-Agent는 저장하지 않고, 기록은 메모리 큐에 모았다가 5초마다 한 번에 저장한다.
돌아보며
하루 만에 만든 것보다 그날 남긴 기록이 더 값지다고 생각한다. 장애가 날 때마다 증상, 원인, 조치를 배포 문서의 표에 한 줄씩 추가했다. 같은 문제가 다시 생기면 그 표부터 보면 된다.
다음 글 2편에서는 EF6와 MySQL 프로바이더의 버그 세 가지를 다룬다.