Network & Server Factory

개인 공부 기록

Project/홈랩

[홈랩] PHP 사이트 6개와 MySQL을 옮기고 Gateway 하나로 합치기 (5편)

1nfra 2026. 10. 29. 08:11
두 번째 이관 대상은 VM 한 대에서 포트로 나눠 운영하던 PHP 사이트 6개와 MySQL이었습니다. 사이트마다 이미지를 따로 만들고 DB는 StatefulSet으로 옮겼으며, 입구는 Gateway 하나로 합쳐 호스트 이름으로 사이트를 나눴습니다. 합치는 과정에서 VM처럼 포트로 나누려다 약 4분 동안 다른 사이트의 내용이 보이는 문제가 있었고, 이 일로 Cilium Gateway에서는 포트가 아니라 호스트 이름으로 사이트를 나눠야 한다는 것을 확인했습니다. 이 글에서는 이미지 설계, MySQL 이관, Gateway 통합과 그때 생긴 문제, 호스트 이름 라우팅으로 전환한 과정을 정리합니다. 홈랩 재구성 시리즈 5편이며, 이전 글은 Jenkins와 Argo CD로 홈랩 CI/CD 구성하기입니다.

5편을 마친 시점의 구성입니다. 입구를 Gateway 하나로 합치고 호스트 이름으로 사이트를 나눴습니다

1. 이관 대상

 

항목 내용
구성 Ubuntu VM 1대(백업 VM 1대 별도), Apache와 PHP 7.4, MySQL 8.0
사이트 포트 80의 WordPress 1개, 포트 1111~1115의 정적 사이트와 PHP 사이트 5개
DB WordPress용 DB 1개, 732KB
트래픽 14일 동안 상태 확인을 뺀 요청이 거의 없음

 

앞단 로드밸런서가 도메인마다 이 VM의 다른 포트로 요청을 보내는 구조였습니다. PHP 7.4는 지원이 끝났지만, 이번에는 동작을 그대로 옮기는 것을 목표로 하고 버전은 VM과 같게 맞췄습니다. 이관과 업그레이드를 한 번에 하면 문제가 생겼을 때 어느 쪽 때문인지 구분하기 어렵기 때문입니다.

2. 이미지 설계

 

이미지 내용
공통 베이스 공식 php:7.4-apache 이미지에 VM과 같은 PHP 확장, 같은 php.ini, rewrite 모듈
사이트 이미지 6개 하나의 Dockerfile에서 빌드 인자로 문서 경로만 바꿔 사이트 내용을 넣음

 

사이트마다 이미지를 따로 만든 이유는 하나를 고쳐 다시 배포해도 다른 사이트에 영향이 없게 하기 위해서입니다. 대신 PHP 확장 설치처럼 시간이 오래 걸리는 부분은 공통 베이스로 빼서 한 번만 빌드했습니다. 작업하는 PC가 Apple Silicon이라 amd64 이미지를 에뮬레이션으로 빌드하는데, PHP 확장 컴파일이 특히 오래 걸렸기 때문입니다.

 

WordPress의 DB 접속 정보는 이미지에 넣지 않았습니다. wp-config.php가 접속 정보를 환경 변수에서 읽도록 바꾸고, 값은 Kubernetes Secret에서 넘깁니다. 이미지에 비밀번호가 들어가면 이미지 저장소에 접근할 수 있는 누구나 비밀번호를 볼 수 있기 때문입니다.

 

이미지를 만들면서 만난 문제는 두 가지였습니다.

 

문제 해결
php:7.4 이미지의 기반인 Debian 11이 아카이브로 옮겨져 패키지 설치 실패 패키지 저장소를 archive.debian.org로 바꿈
한 사이트의 한글 파일 이름이 EUC-KR로 인코딩되어 있어 macOS에서 압축을 풀 수 없음 PC에서 풀지 않고 압축 파일째 이미지에 넣어 Linux 안에서 풂, 파일 이름이 원래 바이트 그대로 보존됨

 

두 번째 문제는 macOS에서 EUC-KR로 인코딩된 파일 이름을 처리하지 못해 생겼습니다. 오래된 사이트일수록 이런 파일 이름이 남아 있을 수 있으므로, 파일은 가능하면 원래 OS와 같은 환경 안에서 다루는 편이 안전합니다.

3. MySQL 옮기기

MySQL은 VM과 같은 8.0 버전을 StatefulSet으로 띄우고, 데이터는 영구 볼륨에 두었습니다. 영구 볼륨에는 지난 글에서 정리한 대로 Argo CD가 지우지 못하게 하는 어노테이션을 붙였습니다. DB 비밀번호는 Git에 넣지 않고 클러스터에서 Secret으로 직접 만들었습니다. 기존 접속 계정의 비밀번호는 화면에 출력하지 않고 바로 Secret으로 넣었습니다.

 

VM에서 덤프를 받아 가져온 뒤, 테이블 수가 16개, 게시물 수가 91개로 VM과 같다는 것을 확인했습니다. 다만 WordPress가 DB에 접속하지 못하는 문제가 하나 있었습니다. MySQL 8은 새로 만든 계정의 인증 방식이 기본으로 caching_sha2_password인데, VM의 계정은 mysql_native_password였습니다. PHP 7.4가 TLS 없이 접속할 때 문제가 없도록 VM과 같은 인증 방식으로 맞췄습니다.

 

사이트는 3편과 같은 방법으로 VM과 응답을 비교했습니다. 230개 경로 중 219개가 같았고, 남은 11개는 오류 페이지에 찍히는 서버 서명, phpinfo 페이지, VM에서도 오류가 나던 페이지였습니다.

4. Gateway 하나로 합치기

이때까지는 서비스마다 LoadBalancer IP를 따로 쓰고 있었습니다. 서비스가 늘어날수록 IP와 앞단 프록시 설정이 늘어나므로, 웹 서비스의 입구를 Gateway 하나로 합치고 각 서비스는 라우트(HTTPRoute)만 붙이기로 했습니다.

 

처음에는 VM과 같은 구조를 유지하려고 Gateway에 포트 1111~1114 리스너를 추가해 사이트를 포트로 나눴습니다. 그런데 이 변경을 적용한 직후, 포트 80으로 들어간 요청에 첫 번째 웹 사이트가 아니라 다른 사이트의 내용이 나왔습니다. 상태 코드는 계속 200이었습니다.

Cilium Gateway는 호스트 이름 없는 HTTP 리스너를 포트와 상관없이 하나로 합쳐, 포트로 나눈 사이트들이 서로 섞였습니다

 

원인은 Cilium의 Gateway 구현 방식이었습니다. Cilium은 호스트 이름을 지정하지 않은 HTTP 리스너들을 포트와 상관없이 하나의 라우팅 표로 합칩니다. 그래서 포트 80과 포트 1111~1114로 들어온 요청이 하나의 라우팅 표에서 경로만으로 처리되어 다른 사이트로 연결되었습니다.

 

항목 내용
영향 약 4분 동안 첫 번째 웹 사이트 주소에 다른 사이트 내용이 보임
발견 VM과 비교하는 스크립트를 새 입구로 다시 돌렸을 때 본문이 다른 것을 확인
복구 Git에서 포트 리스너와 라우트를 지워 push하고, 클러스터의 라우트도 바로 삭제
확인 페이지 제목과 본문 해시, 설정 파일 해시가 원본과 같음

 

앞단 로드밸런서가 PHP 사이트로 들어오는 요청을 아직 VM으로 보내고 있어서, PHP 사이트에는 영향이 없었습니다.

 

이 일에서 얻은 교훈은 세 가지입니다.

  1. 전환을 확인할 때는 상태 코드가 아니라 내용(제목, 본문 해시)을 본다
  2. Cilium Gateway에서는 포트로 서비스를 나누지 않고 호스트 이름으로 나눈다
  3. 운영 중인 입구와 새 라우트를 한 번에 바꾸지 않는다

5. 호스트 이름으로 나누기

다시 설계할 때는 Gateway에 포트 80을 사용하는 HTTP 리스너 하나만 두고, 사이트마다 고유한 하위 도메인을 정해 라우트에 호스트 이름을 적었습니다. 첫 번째 웹 사이트의 라우트는 호스트 이름 없는 기본 라우트로 두었습니다. Gateway는 호스트 이름이 정확히 맞는 라우트를 먼저 적용하므로, 하위 도메인으로 들어온 요청은 각 사이트로 가고 나머지는 모두 첫 번째 사이트로 갑니다.

 

라우트 호스트 이름
첫 번째 웹 사이트 없음 (기본 라우트)
PHP 사이트 5개 사이트별 하위 도메인

 

이번에는 운영 중인 사이트를 지켜보면서 적용했습니다. push하기 전후로 첫 번째 사이트의 본문 해시를 기록하고, Argo CD가 동기화하는 90초 동안 2초마다 확인했습니다. 45번 모두 해시가 바뀌지 않았습니다. 그 뒤 VM과 비교하자 174개 경로 중 167개가 같았고, 남은 7개에서는 앞서 확인한 것처럼 오류 페이지의 서버 서명과 phpinfo 출력 등에 차이가 있었습니다.

 

전환은 앞단 프록시가 사이트별 하위 도메인을 Gateway로 보내도록 바꾸는 것으로 끝났습니다. 공개 주소로 접속해 각 사이트의 본문 해시가 VM 원본과 같은지 확인했고, 전환한 시각 이후 VM의 접속 로그에는 요청이 한 건도 남지 않았습니다. 각 사이트의 요청이 모두 새 환경으로 넘어갔다는 뜻입니다. 기존 VM은 먼저 꺼 두고, 문제가 없는 것을 확인한 뒤 첫 번째 서비스의 VM과 합쳐 총 4대를 삭제했습니다.

6. 지금 상태

전환한 뒤 노드 장애에 대비해 사이트별 배치를 다시 정했습니다.

 

사이트 지금 상태
정적 사이트 3개 복제본 2개, 두 호스트에 하나씩 분산
PHP 사이트 2개 PHP 세션을 Pod 안의 /tmp에 저장하므로 복제본 1개, 노드가 멈추면 30초 뒤 다른 노드에서 다시 실행
WordPress와 MySQL 호스트 이름 라우팅으로 바꾼 뒤 연결된 주소가 없어 쓰는 곳이 없었음, 덤프를 보관하고 정리

 

WordPress는 VM에서는 포트 80으로 서비스되었지만, Gateway로 옮긴 뒤에는 포트 80의 기본 라우트를 첫 번째 웹 사이트가 쓰고 있었고, WordPress에 연결된 주소는 없었습니다. 쓰는 사람이 없는 것을 확인하고 DB 덤프를 따로 보관한 뒤, Git에서 매니페스트를 지워 Argo CD가 정리하게 했습니다. 데이터가 든 볼륨은 4편에서 설정한 보호 덕분에 그대로 남아 있습니다.

7. 정리

  • 이관과 업그레이드를 나누려고 PHP와 MySQL 버전은 VM과 같게 맞췄습니다.
  • 사이트별 이미지는 공통 베이스 위에 만들고, DB 접속 정보는 이미지 대신 Secret과 환경 변수로 넘겼습니다.
  • 파일 이름의 인코딩이 다른 압축 파일은 macOS에서 풀지 않고 이미지 안에서 풀었습니다.
  • MySQL 8의 기본 인증 방식은 VM 계정의 인증 방식과 달라서 VM과 같은 방식으로 맞췄습니다.
  • Cilium Gateway는 호스트 이름 없는 HTTP 리스너를 포트와 상관없이 합치므로, 서비스는 호스트 이름으로 나눠야 합니다.
  • 전환을 확인할 때는 상태 코드가 아니라 본문 내용을 비교합니다.

참고

728x90
서울
--:--:--
-전체 글
-카테고리
누적 조회수