드디어 마지막이다!
대상 VM: dns(192.168.56.60)
이번에는 내부망 전용 DNS 서버를 구축하고, 모든 VM의 DNS를 내부 DNS 서버로 변경한다. 이후 각 서비스 설정 파일의 IP 주소를 도메인명으로 교체하고, lb_external에 SSL을 구성한다. 마지막으로 DNS 전환 후 미뤄뒀던 프론트엔드 배포를 진행한다.
도메인 구조
| 호스트명 | IP |
| sesac.cloud.com | 192.168.56.50 (lb_external) |
| dns.sesac.cloud.com | 192.168.56.60 |
| storage.sesac.cloud.com | 192.168.56.10 |
| db1.sesac.cloud.com | 192.168.56.21 |
| db2.sesac.cloud.com | 192.168.56.22 |
| lb-internal.sesac.cloud.com | 192.168.56.30 |
| web1.sesac.cloud.com | 192.168.56.41 |
| web2.sesac.cloud.com | 192.168.56.42 |
| lb-external.sesac.cloud.com | 192.168.56.50 |
VM 부팅
vagrant up dns
vagrant ssh dns
BIND 설치
BIND(Berkeley Internet Name Domain)는 널리 쓰이는 DNS 서버 소프트웨어다.
Rocky Linux의 Appstream 레포에 기본 포함되어 있어 외부 레포 없이 설치 가능하고, 내부망 전용 권위 DNS 서버 구성에 충분하다.
여기서 권위 서버라는 게 뭔지 좀 더 알아보자. DNS 서버는 크게 2가지 역할로 나뉜다.
- 권위 서버: 특정 도메인의 원본 데이터를 직접 가지고 있는 서버. 해당 도메인이 어느 IP인지 공식적으로 답할 수 있다.
- etc: 이 프로젝트의 dns VM
- 재귀 리졸버: 클라이언트 대신 여러 DNS 서버를 돌아다니며 답을 찾아주는 서버. 원본 데이터는 없고 다른 서버에게 물어봐서 결과를 가져다 준다.
- etc: Google DNS(8.8.8.8), ISP DNS
클라이언트: "sesac.cloud.com이 어디야?"
↓
재귀 리졸버: "모르겠는데, 권위 서버한테 물어볼게"
↓
권위 서버: "내가 직접 관리하는 도메인이야. 192.168.56.50이야"
이를 알아봤으면 dns VM에 설치할 패키지들을 설치해주자.
- bind: DNS 서버 데몬(named)
- bind-tuils: dig, nslookup 등 DNS 조회 도구
sudo dnf install -y bind bind-utils
BIND 설정
1] /etc/named.conf 작성
/etc/named.conf 파일은 BIND 데몬(named)의 전체 동작을 정의하는 메인 설정 파일이다. 크게 2가지를 구성한다.
좀 더 쉽게 말해 BIND Daemon(named)에게 "너 어떻게 동작하고, 어떤 도메인 관리해" 라고 명시해준다.
- options { }: DNS 서버 전체에 적용되는 전역 설정이다. 어느 IP/포트에서 요청을 받을지, 어느 클라이언트의 질의를 허용할지, 재귀 조회 여부 등을 정의한다.
- etc: 53번 포트는 192.168.56.60에서만 열어, 192.168.56.0/24 대역에서 오는 질의만 받고, 재귀 조회는 허용해.
- zone { }: 이 서버가 권위 서버로 관리할 도메인 존을 선언한다. 존 이름, 존 파일 경로, 서버 역할(master/slave)을 지정한다. 존 파일에 실제 도메인-IP 매핑 데이터가 담긴다.
- type master: 이 서버가 sesac.cloud.com 존의 권위 서버라고 선언하는 것
- recursion yes: 권위 서버 역할과 동시에 내부망 클라이언트를 위한 재귀 리졸버 역할도 겸하도록 한 것.
- 단, forwarders { } 를 비워뒀으므로 외부 도메인 조회는 안되고 내부 도메인만 해석된다.
- etc: sesac.cloud.com 도메인은 내가 직접 관리해, 데이터는 sesac.cloud.com.zone 파일에 있어.
- 참고로 실제 도메인-IP 매핑은 위에서도 말햇듯 sesac.cloud.com.zone 파일에 담긴다.
- 참고로 Master, Slave 서버 역할은 간단히 Primary/Secondary라고 봐도 된다.
말만 들으면 어려우니 우선 코드를 한번 봐보자.
sudo tee /etc/named.conf << 'EOF'
options {
listen-on port 53 { 127.0.0.1; 192.168.56.60; };
listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
recursion yes;
allow-query { 192.168.56.0/24; localhost; };
forwarders { };
dnssec-validation no;
};
zone "sesac.cloud.com" IN {
type master;
file "sesac.cloud.com.zone";
allow-update { none; };
};
EOF
각 옵션을 한번 봐보자. 먼저 options(DNS 서버에 적용되는 설정)이다.
| 옵션 | 설명 |
| listen-on port 53 | DNS 요청을 받을 포트와 IP를 정의한다. 53번 포트에서 127.0.0.1, 192.168.56.60를 허용한다. 외부 NIC(enp0s3)는 포함하지 않아 외부에서 접근 불가하다. |
| directory "/var/named" | 존 파일 기본 경로. 존 파일 경로를 상대 경로로 지정하면 이 디렉토리 기준으로 찾는다. |
| recursion yes | 클라이언트가 모르는 도메인을 질의했을 때 DNS 서버가 대신 외부에 조회해 결과를 돌려준다. |
| allow-query { ... } | DNS 질의를 허용할 클라이언트 범위를 설정한다. 내부망과 로컬만 허용한다. |
| forwarders { } | 내부 DNS가 모르는 도메인을 넘길 외부 DNS 서버. 비워두면 외부 도메인 조회가 안 된다. 이 프로젝트는 내부망 전용이므로 없다. |
| dnssec-validation no | DNSSEC 서명 검증 비활성화. 자체 서명한 내부 존 파일은 DNSSEC 서명이 없으므로 활성화하면 조회가 실패한다. |
개인적으로 헷갈렸던 부분은 listen-on port, allow-query 내부에 정의된 IP 주소가 무슨 차이를 가졌는지였다.
- listen-on: DNS 서버가 어느 NIC에서 요청을 받을지 결정한다.
- 현재 dns VM에는 NIC가 2개다. enp0s3(NAT, 외부망)과 enp0s8(내부망, 192.168.56.60)이다.
- listen-on에 192.168.56.60만 적으면 내부망 NIC로 들어오는 요청만 받고, enp0s3으로 들어오는 요청은 포트 자체를 열지 않아 받지 않는다.
- allow-query: 문을 통해 들어온 요청이 어느 IP에서 왔는지 확인한다.
- 내부망 NIC로 들어온 요청이라도 출발지 IP가 192.168.56.0/24 대역이 아니면 거절한다.
외부망(enp0s3)으로 들어오는 요청
→ listen-on에 없음 → 포트 자체가 닫혀있음 → 차단
내부망(enp0s8)으로 들어오는 요청, 출발지 IP가 192.168.56.X
→ listen-on 통과 → allow-query 통과 → 응답
내부망(enp0s8)으로 들어오는 요청, 출발지 IP가 다른 대역
→ listen-on 통과 → allow-query에서 차단
다음으로는 도메인 존 설정을 해주는 zone { }에 정의된 옵션을 봐보자.
| 옵션 | 설명 |
| type master | 이 서버가 sesac.cloud.com 존의 원본 데이터를 가진 Primary 서버임을 의미한다. |
| allow-update { none; } | 외부에서 존 파일을 동적으로 수정하는 것을 차단한다. |
2] zone 파일 작성 (/var/named/sesac.cloud.com.zone)
존 파일은 도메인명과 IP를 매핑해주는 데이터 파일이다.
sudo tee /var/named/sesac.cloud.com.zone << 'EOF'
$TTL 86400
@ IN SOA dns.sesac.cloud.com. admin.sesac.cloud.com. (
2026022401 ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
@ IN NS dns.sesac.cloud.com.
dns IN A 192.168.56.60
sesac.cloud.com. IN A 192.168.56.50
storage IN A 192.168.56.10
db1 IN A 192.168.56.21
db2 IN A 192.168.56.22
lb-internal IN A 192.168.56.30
web1 IN A 192.168.56.41
web2 IN A 192.168.56.42
lb-external IN A 192.168.56.50
EOF
레코드 타입 설명
| 레코드 | 설명 |
| SOA | Start of Authority. 이 존의 관리 정보를 정의한다. 어느 서버가 권위 서버인지, 갱신 주기는 얼마인지 등을 담는다. |
| NS | Name Server. 이 존을 담당하는 DNS 서버를 지정한다. |
| A | Address. 도메인명을 IPv4 주소로 매핑한다. |
SOA 필드 설명
| 필드 | 값 | 설명 |
| dns.sesac.cloud.com. | — | 이 존의 Primary DNS 서버 |
| admin.sesac.cloud.com. | — | 관리자 이메일 (.이 @ 역할, 실제론 admin@sesac.cloud.com) |
| Serial | 2026022401 | 존 파일 버전 번호. Secondary DNS가 변경 여부를 이 값으로 판단한다. 존 파일 수정 시 반드시 증가시켜야 한다. |
| Refresh | 3600 | Secondary DNS가 Primary에 갱신 여부를 확인하는 주기 (초) |
| Retry | 1800 | Refresh 실패 시 재시도 간격 (초) |
| Expire | 604800 | Primary 연결 불가 시 Secondary가 존 데이터를 유지하는 최대 시간 (초) |
| Minimum TTL | 86400 | 레코드의 기본 캐시 유지 시간 (초). 클라이언트가 이 시간 동안 결과를 캐시한다. |
권한 설정 및 시작
sudo chown root:named /var/named/sesac.cloud.com.zone
sudo chmod 640 /var/named/sesac.cloud.com.zone
sudo named-checkconf
sudo named-checkzone sesac.cloud.com /var/named/sesac.cloud.com.zone
sudo systemctl enable --now named
- chown root:named: 파일 소유자는 root, 그룹은 named로 설정한다. named 데몬은 named 계정으로 실행되므로 그룹 권한으로 파일을 읽을 수 있어야 한다.
- chmod 640: 소유자(root)는 읽기/쓰기, 그룹(named)은 읽기만 허용, 그 외는 접근 불가. 존 파일에는 내부망 IP 구조가 담겨 있으므로 외부 노출을 차단한다.
- named-checkconf: named.conf 문법 오류 검사
- named-checkzone: 존 파일 문법 및 레코드 오류 검사. 시작 전 반드시 확인한다.
방화벽 설정
sudo firewall-cmd --permanent --add-service=dns
sudo firewall-cmd --reload
DNS 동작 확인
dig를 이용해 DNS 서버가 정상적으로 도메인을 해석하는지 확인한다.
- 아래의 결과에서 NOERROR, aa, ANSWER SECTION에서 제대로 뜨는 것을 확인할 수 있다.
dig @192.168.56.60 sesac.cloud.com
dig @192.168.56.60 web1.sesac.cloud.com
dig @192.168.56.60 lb-internal.sesac.cloud.com
# 첫번째 결과
[vagrant@dns ~]$ dig @192.168.56.60 sesac.cloud.com
; <<>> DiG 9.16.23-RH <<>> @192.168.56.60 sesac.cloud.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21409
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 5f9ae96f4221389c01000000699f92eb62d39503ce9ba7c9 (good)
;; QUESTION SECTION:
;sesac.cloud.com. IN A
;; ANSWER SECTION:
sesac.cloud.com. 86400 IN A 192.168.56.50
;; Query time: 0 msec
;; SERVER: 192.168.56.60#53(192.168.56.60)
;; WHEN: Thu Feb 26 00:25:15 UTC 2026
;; MSG SIZE rcvd: 88
[vagrant@dns ~]$
각 VM DNS 변경
NIC 구조
Vagrant VM은 NIC가 2개다.
| NIC | 용도 | DNS |
| enp0s3 | NAT — 호스트를 통해 인터넷 접근 | DHCP가 자동으로 외부 DNS 주입 |
| enp0s8 | 내부망 — VM 간 통신 | 내부 DNS 서버(192.168.56.60)로 변경 |
- 참고로 lo(loopback)은 자기 자신과 통신하는 가상 인터페이스로 실제 NIC가 아니다.
문제는 enp0s3의 DHCP가 외부 DNS(8.8.8.8)를 자동으로 /etc/resolve.conf에 넣는다는 것이다. 내부 DNS만 쓰도록 하려면 enp0s3의 auto-dns도 비활성화해야 한다. 대신 외부 인터넷 DNS 조회는 되지 않지만, 이 프로젝트는 내부망 전용이므로 괜찮다.
[DHCP가 뭔지 모르면 아래의 영상을 봐보자]
https://opentutorials.org/course/3265/20039
DHCP - 생활코딩
수업소개 DHCP(Dynamic Host Configuration Protocol)은 네트워크에 접속한 장치의 ip, subnet mask, gateway address, DNS와 같은 정보를 자동으로 설정해주는 기술입니다. 여기서는 이 기술의 원리와 사용법에 대해
opentutorials.org
그래서 모든 VM(storage, db1, db2, lb_internal, web1, web2, lb_external)에서 아래의 명령을 실행해야 한다.
# enp0s8: 내부망 NIC — 내부 DNS 설정
sudo nmcli con mod "System enp0s8" ipv4.dns "192.168.56.60" ipv4.ignore-auto-dns yes
sudo nmcli con up "System enp0s8"
# enp0s3: NAT NIC — auto-dns 비활성화 (외부 DNS가 resolv.conf에 혼입되지 않도록)
sudo nmcli con mod "enp0s3" ipv4.ignore-auto-dns yes
sudo nmcli con up "enp0s3"
# 확인
cat /etc/resolv.conf
# nameserver 192.168.56.60
혹시나 연결 이름이 VM마다 다를 수 있다. nmcli con show로 확인 후 적용하자.
- enp0s8에 해당하는 연결: System enp0s8 (대부분의 VM)
- enp0s3에 해당하는 연결: enp0s3
IP를 도메인으로 교체
각 VM의 DNS가 내부 DNS로 변경되었으므로 이제 IP 대신 도메인명으로 통신할 수 있다. 각 설정 파일의 IP를 도메인명으로 교체한다.
[이전에 설정한 곳도, 안한 곳도 있지만 여기서 최종 정리를 한다.]
1] web1 - config/db.php (NFS 공유라 1번만 해도 web2에 반영된다.)
sudo sed -i "s/192.168.56.30/lb-internal.sesac.cloud.com/" /var/www/html/config/db.php
# 확인
grep DB_HOST /var/www/html/config/db.php
# define('DB_HOST', 'lb-internal.sesac.cloud.com');
2] web1, web2 - /etc/fstab (NFS 마운트를 각각 다시 해주자)
sudo sed -i "s/192.168.56.10/storage.sesac.cloud.com/" /etc/fstab
# 재마운트
sudo umount /var/www/html
sudo mount -a
# 확인
grep nfs /etc/fstab
# storage.sesac.cloud.com:/srv/nfs/webroot /var/www/html nfs ...
3] lb_external - Nginx upstream
sudo sed -i "s/192.168.56.41/web1.sesac.cloud.com/" /etc/nginx/conf.d/saa.conf
sudo sed -i "s/192.168.56.42/web2.sesac.cloud.com/" /etc/nginx/conf.d/saa.conf
sudo nginx -t && sudo systemctl reload nginx
SSL 구성 (lb_external)
1] 왜 SSL을 구성하는가?
지금까지 클라이언트와 lb_external 사이의 통신은 HTTP 였다. HTTP는 데이터를 평문으로 주고받으므로 보안에 좋지 않다.
이 프로젝트는 로그인, 문제 풀이 등 사용자 데이터를 다루기 때문에 암호화가 필요하다고 판단해 HTTPS를 적용하기로 했다.
HTTP는 TLS 프로토콜로 암호화를 하고, 이 TLS가 동작하려면 개인키/인증서가 필요하다.
- 개인키(Private Key): 서버만 가지고 있는 비밀 키. 암호화된 데이터를 복호화할 때 사용한다.
- 인증서(Certificate): 개인키와 쌍을 이루는 공개 키와 서버 신원 정보를 담은 파일. 클라이언트한테 "나는 sesac.cloud.com이야"라고 증명하는 역할이다.
클라이언트가 처음 접속하면 서버가 인증서를 보낸다. 클라이언트는 인증서를 보고 '이거 진짜 sesac.cloud.com이 맞네' 하고 암호화 통신을 시작한다.
이 프로젝트는 실습 환경이라 공인 도메인이 없으므로 공인 인증서가 아닌 자체서명 인증서를 사용한다.
2] SSL Termination
인증서는 lb_external에만 설치한다. lb_external이 클라이언트와 HTTPS로 통신하고, 뒤쪽 web1/web2로는 HTTP로 전달한다. 이를 SSL Termination이라고 한다.
클라이언트 ←HTTPS→ lb_external ←HTTP→ web1 / web2
이러면 lb_external 한 곳에서 인증서를 설치해 관리하면 되므로 운영이 편해진다.
3] 자체서명 인증서 생성
sudo mkdir -p /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/sesac.key \
-out /etc/nginx/ssl/sesac.crt \
-subj "/C=KR/ST=Seoul/L=Seoul/O=SAA/CN=sesac.cloud.com"
Nginx SSL 설정 업데이트
이제 lb_external의 saa.conf 파일을 업데이트하자. 기존 saa.conf는 80번 포트 블록 하나였다. 모든 요청을 받아서 web1/web2로 전달하는 구조였다.
# 기존 — 80번에서 모든 처리
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://web_servers;
...
}
}
HTTPS를 적용하면서 역할이 분리된다. 80번은 리다이렉트만 담당하고, 실제 처리는 443번 블록으로 이동한다.
# 변경 후 — 역할 분리
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri; # 리다이렉트만
}
server {
listen 443 ssl default_server;
server_name sesac.cloud.com; # 실제 처리
location / {
proxy_pass http://web_servers;
...
}
}
server_name도 바뀐다. 80번은 _(모든 요청 ok)를 유지했찌만, 443번은 sesac.cloud.com으로 지정한다.
DNS가 설정됐으므로 이제 도메인으로 접근이 가능하다.
return 301 https://$host$request_uri
- host: 요청의 Host 헤더값
- etc: sesac.cloud.com
- request_uri: 요청 경로와 쿼리스트링
- etc: /api/questions?page=1
최종 saa.conf는 아래와 같다.
- 중간에 ssl_ 로 시작하는 녀석들은 인증서/개인키 파일 경로, TLS 버전, 암호화 알고리즘을 명시하는 거다.
- 위에 인증서를 생성할 때 우리가 입력한 값들을 사용하면 된다.(인증서/개인키 파일 경로 경우)
sudo tee /etc/nginx/conf.d/saa.conf << 'EOF'
upstream web_servers {
server web1.sesac.cloud.com:80;
server web2.sesac.cloud.com:80;
}
# HTTP → HTTPS 리다이렉트
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
# HTTPS 메인 서버
server {
listen 443 ssl default_server;
server_name sesac.cloud.com;
ssl_certificate /etc/nginx/ssl/sesac.crt;
ssl_certificate_key /etc/nginx/ssl/sesac.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://web_servers;
proxy_set_header Host sesac.cloud.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
방화벽 HTTPS 추가
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
프론트엔드 배포
DNS 전환 후로 미뤘던 index.html 배포를 진행한다. index.html은 내부에서 API를 sesac.cloud.com 도메인으로 호출하므로 DNS가 설정된 지금 배포한다.
NFS 공유라 web1에만 올리자.
# 호스트 머신에서 (vagrant upload는 /var/www/html에 직접 쓰기 권한 없음)
vagrant upload webroot/index.html /home/vagrant/index.html web1
# web1에서
sudo mv /home/vagrant/index.html /var/www/html/index.html
sudo chown apache:apache /var/www/html/index.html
혹시나 https://sesac.cloud.com 접속 시 404가 반환되는 경우 .htaccess에 DirectoryIndex가 index.php에 반영되어 있는지 확인해보자.
sudo sed -i '1s/^/DirectoryIndex index.html index.php\n/' /var/www/html/.htaccess
# 확인
head -2 /var/www/html/.htaccess
# DirectoryIndex index.html index.php
# RewriteEngine On
최종 동작 확인
이전까지 배운 Linux를 활용해 플랫폼을 만들어보니.. 비록 규모가 작은 프로젝트였지만 설정을 할 부분이 많았다는 것을 몸소 느끼게 되었다.
이제는 AWS를 이용해 사이트를 요구사항에 맞게 구현해보고, 배포해보는 과정을 수행해보려고 한다.
'Infra > Project' 카테고리의 다른 글
| [세미 프로젝트 Ch.2] AWS SAA 기출문제 풀이 플랫폼 - AWS 아키텍쳐 설계 (0) | 2026.02.26 |
|---|---|
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - LoadBalancer(External) 구성 (0) | 2026.02.25 |
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - Web 서버 (2) | 2026.02.25 |
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - LoadBalancer(internal) 구성 (0) | 2026.02.25 |
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - DB 서버 (0) | 2026.02.25 |