Physical Address
South Korea
Physical Address
South Korea


얼마 전 진행 중인 프로젝트에서 애플리케이션 아키텍트(AA) 분에게 급한 요청이 하나 들어왔습니다. 개발, QA, 운영 서버로 이어지는 CI/CD 배포 파이프라인을 구축해 달라는 업무였죠.
사실 요즘 같은 DevOps 시대에 서버에 일일이 접속해서 git pull을 댕기거나 빌드 파일을 수동으로 올리는 건 상상하기 힘듭니다. 특히 보안이 까다로운 프로젝트일수록 비밀번호 없이 안전하게 인증하는 비밀번호 없는 인증(Passwordless Authentication)은 선택이 아닌 필수입니다.
오늘은 제가 Docker 기반의 GitLab Runner를 활용해 각 서버에 안전하게 코드를 배포하기까지, 직접 맨땅에 헤딩하며 구축한 SSH Key 관리 전략과 구성 방법을 공유해 보려고 합니다. 저와 같은 시행착오를 겪지 않도록 실무에서 바로 쓸 수 있는 스크립트와 트러블슈팅 경험까지 꾹꾹 눌러 담았습니다.
가장 직관적인 방법은 서버 ID와 비밀번호를 스크립트에 적어두는 것입니다. 하지만 이 방식은 몇 가지 치명적인 문제가 있습니다.
반면 SSH 키 쌍(Public/Private Key)을 이용하면, 물리적인 비밀키 파일 없이는 서버 접속 자체가 불가능해집니다. 게다가 GitLab의 CI/CD Variables 기능을 활용하면 서버의 민감한 정보를 외부로 노출하지 않고 안전하게 배포 경로를 제어할 수 있습니다.
가장 먼저 배포의 핵심이 될 SSH 키를 생성해야 합니다. 예전에는 RSA 알고리즘을 많이 썼지만, 최근에는 연산 속도가 빠르고 보안성이 더 우수한 Ed25519 알고리즘 사용을 강력히 권장합니다.
터미널을 열고 아래 명령어를 입력합니다. 제 경우 관리의 편의성을 위해 파일명을 id_ed25519_gitlab으로 명시해 주었습니다.
Bash
# 특정 환경과 관리를 위한 키 생성
ssh-keygen -t ed25519 -C "gitlab-ci-deployment" -f ~/.ssh/id_ed25519_gitlab
명령어를 실행하면 두 개의 파일이 생깁니다.
보안과 안정성을 확보하려면 개발(Dev), QA, 운영(Prod) 서버의 권한이 철저히 분리되어야 합니다. 아무리 급해도 개발 서버 배포 키로 운영 서버까지 접근할 수 있게 만들면 안 됩니다.
배포 대상 서버에 접속한 뒤, 방금 생성한 공개키(Public Key) 내용을 복사해서 서버의 authorized_keys 파일에 등록합니다. 보안을 위해 권한 설정도 반드시 600으로 조여주어야 합니다.
Bash
# 서버의 authorized_keys에 공개키 추가
echo "ssh-ed25519 AAAAC3Nza..." >> ~/.ssh/authorized_keys
# 권한을 엄격하게 제한 (매우 중요!)
chmod 600 ~/.ssh/authorized_keys
이제 GitLab 웹 콘솔로 이동합니다. [Settings] > [CI/CD] > [Variables] 메뉴로 들어가서 배포에 필요한 변수들을 등록해 줍니다.
| 변수명 | 설명 | 보안 설정 (추천) |
| SSH_PRIVATE_KEY | 생성한 Private Key 내용 전체 | Masked & Protected 체크 |
| SERVER_IP_DEV | 개발 서버 IP 주소 | 일반 설정 |
| SERVER_IP_PROD | 운영 서버 IP 주소 | Protected 체크 |
💡 여기서 꿀팁!
SSH_PRIVATE_KEY를 등록할 때 반드시 Masked 옵션을 켜주세요. 그래야 혹시 모를 스크립트 에러로 인해 오픈된 빌드 로그에 비밀키가 평문으로 찍히는 대참사를 막을 수 있습니다.
Docker 기반의 GitLab Runner에서 배포가 시작될 때, 메모리상에 SSH 에이전트를 띄우고 변수로 받아온 비밀키를 주입하는 과정입니다. 아래는 제가 실제 프로젝트에 적용한 파일의 일부입니다.
YAML
before_script:
# ssh-agent가 설치되어 있는지 확인하고 없으면 설치합니다.
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
# SSH 에이전트를 백그라운드에 실행합니다.
- eval $(ssh-agent -s)
# GitLab 변수에 저장된 Private Key를 에이전트에 등록합니다.
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
# SSH 디렉토리를 만들고 권한을 설정합니다.
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# 서버 변조 공격을 막기 위해 대상 서버의 SSH 호스트 키를 신뢰할 수 있는 목록에 추가합니다.
- ssh-keyscan $SERVER_IP_PROD >> ~/.ssh/known_hosts
deploy_prod:
stage: deploy
script:
# root가 아닌 일반 배포 계정(webadmin)으로 접속하여 무중단 배포 명령을 내립니다.
- ssh webadmin@$SERVER_IP_PROD "cd /var/www/app && git pull origin main && docker-compose up -d --build"
only:
- main
environment:
name: production
이 배포 파이프라인이 돌아갈 때 내부 아키텍처는 꽤나 유기적으로 움직입니다.
main 브랜치에 코드를 푸시하면 파이프라인이 켜집니다.SSH_PRIVATE_KEY를 슥 던져줍니다.webadmin)으로 SSH 접속을 시도합니다.docker-compose up)이 무사히 끝나면 Runner 컨테이너는 통째로 파괴됩니다. 메모리에 있던 비밀키 흔적도 완벽하게 사라지므로 물리 서버에 키 파일을 저장하는 것보다 훨씬 안전합니다.
설정을 다 끝내고 신나게 깃랩 파이프라인을 돌렸는데, 로그창에 Password:라는 글자만 뜬 채 멈춰버리는 경우가 있습니다. 자동화 배포에서 비밀번호 입력 창이 뜬다는 건 그냥 배포 실패를 의미하죠. 제가 이 문제로 반나절을 날리며 알아낸 3가지 원인과 해결책입니다.
OpenSSH 시스템은 보안에 결벽증이 있습니다. 키 파일이나 상위 디렉토리 권한이 너무 느슨하면, “이 서버는 위험하다”고 판단해 키 인증을 아예 무시하고 차선책인 패스워드 인증을 요구합니다. 배포 대상 서버에 들어가서 다음 권한을 칼같이 맞춰주세요.
Bash
# 1. 유저 홈 디렉토리 권한 체크 (그룹이나 타인에게 쓰기 권한이 있으면 안 됩니다)
chmod 755 /home/webadmin
# 2. .ssh 디렉토리는 소유자만 접근 가능하게
chmod 700 /home/webadmin/.ssh
# 3. authorized_keys 파일은 오직 읽고 쓰기만 가능하게
chmod 600 /home/webadmin/.ssh/authorized_keys
이것저것 테스트하느라 키를 여러 개 만들다 보면 깃랩에 등록한 Private Key와 서버에 넣은 Public Key가 짝이 안 맞는 경우가 생깁니다. 혹은 템플릿에 지정된 기본 키 경로(~/.ssh/id_rsa)와 내가 생성한 파일명이 다를 때 발생합니다. 이럴 때는 배포 스크립트에서 명시적으로 -i 옵션을 주어 키 경로를 지정하는 것이 안전합니다.
Bash
ssh -i ~/.ssh/id_ed25519_gitlab webadmin@$SERVER_IP_PROD "echo '접속 성공!'"
가끔 보안이 극도로 높은 공공기관 인프라나 구형 서버의 경우, 애초에 공개키 인증 방식 자체가 비활성화되어 있는 황당한 경우가 있습니다. 서버의 /etc/ssh/sshd_config 파일을 열어 아래 옵션을 확인해 보세요.
Plaintext
# 이 부분이 no나 주석(#) 처리가 되어 있다면 yes로 변경해야 합니다.
PubkeyAuthentication yes
설정을 바꿨다면 주저 없이 SSH 서비스를 재시작해 줍니다.
Bash
sudo systemctl restart sshd
안전한 DevOps 환경을 위해 제 경험을 바탕으로 3가지 철칙을 제안합니다.
root 계정으로 SSH 접속하게 만들면 안 됩니다. 스크립트 실수 한 번에 서버 전체가 날아갈 수 있습니다. 반드시 webadmin이나 deployer 같은 일반 계정을 파서 서비스 디렉토리에만 쓰기 권한을 주어야 합니다..ssh 폴더에서 개인키 파일(id_ed25519_gitlab)은 즉시 지우세요. 깃랩 서버가 안전하게 보관해 주니 로컬에 남겨둘 이유가 없습니다. 파일이 존재하지 않아야 유출도 안 됩니다.GitLab CI/CD를 통한 배포 자동화는 개발 생산성을 엄청나게 끌어올려 줍니다. 하지만 그 기반이 되는 SSH 인증 체계가 허술하다면, 기업의 가장 소중한 자산인 소스 코드와 인프라가 통째로 위협받게 됩니다.
오늘 정리한 내용들을 바탕으로 지금 운영 중인 파이프라인의 보안 설정을 다시 한번 점검해 보시는 건 어떨까요? 안전한 자동화가 곧 비즈니스의 신뢰를 만듭니다.
[RHEL 9.6: Nginx – Tomcat 리버스 프록시 구성]