퍼블릭 IP를 단 베스천을 만들지 않고 SSM Session Manager로 전부 대신했다. 인바운드 포트를 하나도 열지 않고, 접속 주체가 IAM으로 식별되고 기록된다. 편해진 만큼 새로 생긴 위험도 있었다. AWS가 주는 관리형 포트포워딩 문서는 목적지를 호출자가 정하기 때문에, 그걸 그냥 허용하면 경유 박스에서 닿는 내부망 전체로 터널을 열 권한을 주는 셈이 된다. 권한을 "무엇을 할 수 있는가"가 아니라 "어디까지 닿는가"로 읽어야 보이는 문제였다.
새 리전은 3일 만에 올라갔다
1편에서 모듈을 갈라 놓은 덕에, 새 리전은 값만 채워서 세웠다.
7/3 network + EC2 3대(admin / web / api) 그린필드 구성
7/3 Aurora MySQL 클러스터(writer 1대) + 삭제 보호
7/7 EC2 에 SSM 접속용 IAM 프로필 부착 + AMI 드리프트 방지
7/7 DNS(zone) + ALB 호스트 기반 라우팅
기존 계정에서 몇 년에 걸쳐 쌓인 게 나흘에 끝났다. 모듈을 먼저 판 값을 여기서 받았다.
점프 서버를 두지 않기로 했다
퍼블릭 IP를 단 베스천을 만들지 않고 SSM Session Manager로 전부 대신했다.
- 인바운드 포트를 하나도 열지 않는다 (22번도 없다)
- 접속 주체가 IAM으로 식별되고 기록된다
- 키 파일을 나눠 갖지 않아도 된다
마지막 항목이 실무에서 제일 크다. 사람이 들어오고 나갈 때마다 키를 돌리는 절차가 없어졌다.
서버 접속과 DB 접속은 셸 함수로 감쌌다.
kr-ssm admin # 태그로 인스턴스를 찾아 세션을 연다
kr-db # 로컬 포트로 DB 터널을 연다
로컬 포트에 규칙을 넣었다
앞자리가 계정, 뒤가 원래 포트.
| 계정 A | 계정 B | |
|---|---|---|
| DB | 13306 |
23306 |
| APM | 16100 |
26100 |
| CI | 18080 |
— |
두 계정을 동시에 열어 놓고도 포트만 보면 어느 쪽인지 안다. 로컬 3306은 도커로 DB를 띄울 때가 있어 일부러 비워 뒀다.
편해진 만큼 생긴 위험 — 터널의 목적지
여기서 나중에(8월 말) 한 번 크게 고쳤다.
AWS가 주는 관리형 포트포워딩 문서 AWS-StartPortForwardingSessionToRemoteHost는 목적지를 호출자가 정한다. host와 portNumber가 문서 파라미터이기 때문이다.
즉 이 문서를 허용하는 것은 "경유 박스에서 닿는 내부망 전체로 터널을 열 권한"을 주는 것이다. DB 하나만 열어 주려던 의도와 실제 권한의 크기가 완전히 다르다.
해결은 목적지를 properties에 박은 전용 세션 문서를 만들고 그것만 허용하는 것이다. 그러면 host를 넘기려는 순간 이렇게 거부된다.
Parameter "host" is not defined in the document
권한을 "무엇을 할 수 있는가"가 아니라 "어디까지 닿는가"로 읽어야 보이는 종류의 문제다.
IAM 프로필이 없는 박스는 조사 자체가 안 된다
기존 계정을 훑다가 알게 된 것이다. EC2 6대 중 2대에 IAM 인스턴스 프로필이 없었다.
이건 단순히 "SSM으로 못 들어간다"에서 끝나지 않는다.
- SSM 에이전트가 보고를 안 하니 OS 버전조차 원격으로 확인이 안 된다
- 나중에 마이그레이션할 때 원격으로 조사하거나 명령을 돌릴 수단이 없다
나머지 4대는 SSM이 보고한 값으로 OS를 확정할 수 있었는데, 그 2대만 추정이었다. 조사할 수단이 없다는 것 자체가 리스크로 잡힌다.
한 가지 더. 에이전트는 오래되면 기능이 어긋나므로, 자동 업데이트 association을 걸어 두는 것까지가 한 세트다.
AMI를 어떻게 잡느냐가 두 계정의 실질적 차이였다
같은 "EC2 한 대"인데 코드가 이렇게 갈렸다.
기존 계정 — AMI ID 하드코딩. 그리고 ami 속성은 ForceNew다.
ami = "ami-0123456789abcdef0" # 2022년 이미지
값만 바꾸면 plan이 destroy + create를 낸다. 루트 볼륨이 delete_on_termination = true라 데이터도 같이 사라진다. 지금은 종료 방지가 걸려 있어 API가 막아 주지만, 코드만 보면 안 보인다.
새 계정 — data.aws_ami (most_recent + 표준 필터) + ignore_changes.
data "aws_ami" "base" {
most_recent = true
owners = ["amazon"]
filter { ... }
}
이러면 재구축할 때 최신 이미지가 나온다. 기존 계정은 재구축하면 2022년 이미지가 나온다.
ignore_changes를 같이 두는 이유는, 그게 없으면 새 이미지가 올라올 때마다 plan이 인스턴스 교체를 들고 오기 때문이다. 평소엔 무시하고, 교체할 때만 의도적으로 한다.
교체 순서도 정해 뒀다. 새 리소스를 따로 정의해 띄우고 → 옮기고 → 옛 것을 지운다. ami 값을 바꾸는 방식은 쓰지 않는다.
접속 함수는 "설치 스크립트"로 배포한다
셸 함수를 팀에 나눠 주는 방법은 보통 "이거 복붙하세요"인데, 그러면 버전이 갈린다. 설치 스크립트로 만들어서 마커 블록만 갈아끼우도록 했다.
# >>> aws tunnels >>>
...
# <<< aws tunnels <<<
여러 번 실행해도 중복이 안 쌓이고, 함수가 바뀌면 저장소를 당겨 다시 실행하면 끝이다.
스크립트가 하는 일은 네 가지다.
- CLI와 Session Manager 플러그인 존재 확인
- 프로필 존재 확인
- 함수 설치
- 문법 검사
4번이 중요하다. 셸 프로필에 깨진 문법을 심으면 다음 셸이 아예 안 뜬다. 설치 스크립트가 사람 환경을 망가뜨릴 수 있다는 걸 전제로 만들어야 한다.
1번에 하나 덧붙이면, Session Manager 플러그인은 AWS CLI와 별개 패키지다. 없으면 접속이 이렇게 죽는데,
SessionManagerPlugin is not found
메시지만 보면 권한 문제로 오해하기 쉽다. 실제로 한 번 그쪽을 뒤졌다.
정리
- 베스천 대신 SSM. 인바운드를 안 열고, 접속이 IAM으로 기록된다.
- 관리형 포트포워딩 문서는 목적지를 호출자가 정한다. 전용 세션 문서로 목적지를 박는다.
- IAM 프로필 없는 박스는 조사·실행 수단 자체가 없다.
- AMI는 데이터 소스 +
ignore_changes. 하드코딩 + ForceNew는 데이터를 날릴 수 있다. - 접속 함수는 설치 스크립트로. 마지막에 문법 검사를 넣는다.
다음 편은 배포다. "무중단"이라고 믿고 있던 구조가 실은 이중화 0이었다는 걸 옮기는 과정에서 알게 됐다.
'AWS' 카테고리의 다른 글
| S3 프리티어 초과 청구 — 5GB보다 2,000 PUT이 먼저 터진다 (0) | 2026.09.03 |
|---|---|
| 930일 동안 이중화가 0이었다 — mod_jk에서 ALB 블루그린으로 (0) | 2026.09.02 |
| Terraform import로 콘솔에서 만든 AWS 계정 코드화하기 (0) | 2026.08.31 |
| CloudWatch Logs 요금이 계속 오르는 이유와 보관 기간 점검 (0) | 2026.08.30 |
| NAT 게이트웨이 요금이 갑자기 늘었을 때 확인할 순서 (0) | 2026.08.29 |
댓글