코드 맵

이미지를 구성하는 4 개 파일 + Kubernetes 매니페스트 5 개의 책임 분담. 변경할 파일을 정하고 영향 범위를 가늠하는 인덱스.

docker/

파일 책임 변경 빈도 변경 후 점검
Dockerfile Debian slim 12 베이스 + vsftpd + libpam-pwdfile + tini + 가상 사용자 PAM 설정 보안 패치 분기별 maintenance.md — 이미지 보안 패치
conf/vsftpd.conf vsftpd 가상 사용자 / PASV / 로그 / 최대 클라이언트 설정 정책 변경 시 컨테이너 재기동 후 operating/testing.md — local-smoke.sh
conf/pam_vsftpd_virtual PAM 구성 — pam_pwdfile 모듈로 /etc/vsftpd_user_passwd 참조 거의 없음 변경 시 PAM 동작 직접 테스트 (가짜 사용자 1 명 추가 후 로그인)
entrypoint.sh tini 아래에서 user-syncer 백그라운드 시작 + named pipe 로그 + vsftpd foreground 거의 없음 operating/testing.md — local-smoke.sh
user-syncer.sh users.txt inotify 감지 → db_loadusers.db atomic rename 사용자 sync 로직 변경 시 operating/testing.md — zero-downtime-useradd.sh

k8s/

파일 책임 변경 빈도
00-namespace.yaml ftp namespace 1 회
01-pvc.yaml ftp-data RWX PVC (NAS StorageClass) 1 회
02-configmap.yaml PASV_ADDRESS (LB IP) LB IP 변경 시
03-secret.yaml.example 가상 사용자 자격증명 템플릿. 실제 Secret 은 kubectl create secret 으로 생성 (Git 커밋 금지) 사용자 추가/제거 (별도 운영 절차)
04-deployment.yaml vsftpd Pod (vsftpd + user-syncer 사이드카) 리소스 / 이미지 태그 변경 시
05-service.yaml LoadBalancer Service — 21 + 30000–30099 PASV 포트 노출, MetalLB annotation 포트 범위 / LB IP 변경 시

컴포넌트별 상세

Dockerfile

  • Base: debian:12-slim — 가상 사용자 + PAM 모듈 필요해서 alpine 안 씀.
  • Packages: vsftpd, libpam-pwdfile, db-util (Berkeley DB CLI for db_load), tini, inotify-tools.
  • COPY 4 종: vsftpd.conf, pam_vsftpd_virtual, entrypoint.sh, user-syncer.sh.
  • 영구 디렉토리: /var/run/vsftpd/ (named pipe), /srv/ftp/ (사용자 데이터), secure_chroot_dir.
  • ENTRYPOINT: tini -- /entrypoint.sh — PID 1 / zombie reaping.
  • EXPOSE: 21 30000-30099.

conf/vsftpd.conf

  • 가상 사용자: guest_enable=YES + guest_username=ftpvirt (Pod 내 실제 시스템 사용자) + virtual_use_local_privs=YES.
  • chroot: chroot_local_user=YES + allow_writeable_chroot=YES (/srv/ftp/<user> 가 사용자별 root).
  • PASV: pasv_enable=YES + pasv_min_port=30000 + pasv_max_port=30099 + pasv_address=$PASV_ADDRESS.
  • 로그: xferlog_enable=YES + xferlog_std_format=NO + xferlog_file=/var/run/vsftpd/vsftpd.log (named pipe).
  • 동시성: max_clients=600 + max_per_ip=10.

conf/pam_vsftpd_virtual

2 줄. auth required pam_pwdfile.so pwdfile=/etc/vsftpd_user_passwd + account required pam_permit.so. pam_pwdfile 모듈이 user-syncer 가 만든 Berkeley DB 를 평문 비교 인증으로 참조.

entrypoint.sh

순서: 1. mkfifo /var/run/vsftpd/vsftpd.log — vsftpd 가 RO root 에서도 로그 출력 가능하게. 2. tail -F /var/run/vsftpd/vsftpd.log & — named pipe 를 stdout 으로 흘려 kubectl logs 에 노출. 3. /user-syncer.sh & — 백그라운드 sync 데몬. 4. exec /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf — foreground (tini 아래 PID 2).

user-syncer.sh

입력: /etc/vsftpd/users.txt (Secret 마운트). 출력: /etc/vsftpd_user_passwd (db_load 결과 Berkeley DB).

동작: 1. inotifywait -m -e modify,create,move /etc/vsftpd/users.txt 루프. 2. 줄 수 짝수 검증 (사용자명 + 비밀번호 페어). 3. 사용자명 정규식 검증 (^[a-z][a-z0-9_-]{0,31}$). 4. db_load -T -t hash /tmp/users.db.new — 표준 입력으로 페어를 받아 DB 생성. 5. mv /tmp/users.db.new /etc/vsftpd_user_passwd — atomic rename (POSIX 보장). 6. 신규 사용자별 mkdir -p /srv/ftp/<user>/ + owner/perms 일관.

vsftpd 는 매 로그인마다 DB 를 다시 열기 때문에 재기동 불필요. 반영 지연은 inotify → db_load → mv 합계 약 18 초 (관측치).

공통 변경 흐름

docker/ 변경 시.

  1. 로컬 변경 후 tests/local-smoke.sh 통과 확인.
  2. docker build --no-cache -t <registry>/vsftpd-k8s:<tag> docker/ + docker push.
  3. kubectl set image deployment/vsftpd -n ftp vsftpd=<new-tag> user-syncer=<new-tag> (두 컨테이너 동시 — 같은 이미지).
  4. operating/testing.md — 배포 검증 의 스모크 실행.

k8s/ 변경 시.

  • 매니페스트만 변경 → 개별 kubectl apply -f k8s/<file>.yaml.
  • 포트 범위 / LB IP 등 큰 변화는 operating/maintenance.md 의 절차 참고 — Service + ConfigMap + Deployment 의 변경 순서가 중요.

알려진 한계 (의도적으로 안 한 것)

  • process supervisor 미사용. entrypoint 의 백그라운드 + foreground 패턴이 단순해서 supervisord / s6 도입 안 함. 다음 프로세스 추가 시 재검토.
  • multi-stage build 미사용. 이미지 크기보다 디버깅 용이성 우선. apt 패키지 그대로.
  • vsftpd 빌드 안 함. Debian 패키지 vsftpd 3.0.5 를 그대로 사용. 패치 필요 시점에서 fork 결정.
  • PAM 모듈 자체 변경 불가. libpam-pwdfile 의존. 다른 인증 백엔드 (LDAP / OIDC) 는 별도 PAM 모듈 + 큰 재설계 필요.