AWS EC2를 이용해서 아래의 구조로 배포를 해보려고 한다.(보편적인 구조인지는 잘 모르겠지만 현재 시점에서 내가 이해한대로..)
AWS에 RDS라는 관계형 데이터베이스 서비스가 있다고는 하지만 아직 안 써봤으므로 패스. 이 포스팅에서는 AWS의 EC2만 사용한다는 점..
총 4개의 EC2 인스턴스가 필요하다
1) Bastion Server 용 EC2 인스턴스
2) Reverse Proxy 용 EC2 인스턴스
3) WAS 용 EC2 인스턴스
4) DB 용 EC2 인스턴스
EC2 인스턴스는 만들어져 있다고 가정하겠다. 또한 각 인스턴스 별로 적절한 보안 그룹이 설정되어있다고 가정한다. SSH 접속을 위한 키 페어도 발급받았다고 가정하고 진행한다.
키 페어가 저장된 위치에서 아래의 명령어로 접속한다. 이때 아이피는 퍼블릭 ip주소를 사용한다.(퍼블릭 ip는 서버를 껐다키면 변경될 수 있으므로 탄력적 ip를 설정해주고 탄력적 ip를 사용하는 것이 좋다.)
ssh -i [pem파일 이름] ubuntu@[퍼블릭 ip주소]
Bastion Server는 자신의 공인 IP에서만 22번 포트로 접근이 가능하도록 보안 그룹을 설정해줘야한다.
서비스용 서버(Reverse Proxy, WAS, DB) 22번 포트 접근은 Bastion Server에서만 가능하도록 보안 그룹을 설정해줘야한다.
따라서 Bastion Server를 통해서만 Reverse Proxy, WAS, DB의 shell에 접근할 수 있도록 할 것이다.
먼저 bastion 서버의 공개키를 생성해준다.
ssh-keygen -t rsa
아래의 명령어로 확인해보자.
cat ~/.ssh/id_rsa.pub
나머지 3개의 인스턴스에 위의 공개키를 등록해준다. Reverse Proxy, WAS, DB EC2 인스턴스의 shell에 접속 후 아래의 경로에 Bastion 서버의 공개키를 저장해준다.
vi ~/.ssh/authorized_keys
그리고 나면 bastion server 내부에서 ssh로 각각 인스턴스의 private ip 주소를 통해 각각 인스턴스의 shell로 접속할 수 있다.
ssh ubuntu@[서비스 인스턴스 private ip]
Bastion sever에서 다른 서비스 서버의 shell에 쉽게 접근하기 위해 미리 서비스 서버들의 private ip를 등록해두고 별칭으로 쉽게 접근할 수 있다.
아래로 접근해서 각각 private ip와 별칭을 적어준다.
sudo vi /etc/hosts
그 후에 별칭으로 각 서비스 서버의 shell에 접속할 수 있다.
ssh reverse-proxy
bastion server는 보안 설정을 해줘야한다. bastion server로만 다른 서비스의 shell 접근할 수 있기 때문이다.
profile에 접근한다.
sudo vi ~/.profile
session timeout을 설정해준다.
HISTTIMEFORMAT="%F %T -- " ## history 명령 결과에 시간값 추가
export HISTTIMEFORMAT
export TMOUT=600 ## 세션 타임아웃 설정
변경 값 반영해주기.
source ~/.profile
서버에 직접 접속하여 작업할 경우, 작업 이력 히스토리를 기록해두어야 장애 발생시 원인을 분석할 수 있다.
bashrc에 접속한다.
sudo vi ~/.bashrc
아래 구문 추가하기
tty=`tty | awk -F"/dev/" '{print $2}'`
IP=`w | grep "$tty" | awk '{print $3}'`
export PROMPT_COMMAND='logger -p local0.debug "[USER]$(whoami) [IP]$IP [PID]$$ [PWD]`pwd` [COMMAND] $(history 1 | sed "s/^[ ]*[0-9]\+[ ]*//" )"'
변경 값 반영해주기.
source ~/.bashrc
아래 설정파일에 들어가서,
sudo vi /etc/rsyslog.d/50-default.conf
이 구문을 추가해주자
local0.* /var/log/command.log
그 후 rsyslog 재시작
sudo service rsyslog restart
tail 명령어로 로그를 살펴볼 수 있다.
tail -f /var/log/command.log
ClamAV를 설치해보자. ClamAV는 리눅스용 오픈소스 바이러스 검사 프로그램
아래와 같은 기능이 있다.
# ClamAV 설치
sudo apt install clamav
# Virus 검사
sudo clamscan -r [검사할디렉토리경로]
chkrootkit이라는 프로그램도 설치해 볼 수 있다.
chkrootkit은 시스템에 루트킷이 설치되었는지 쉽게 체크할 수 있는 프로그램이다.(루트킷이란 시스템에 전반적으로 접근할 수 있는 루트 권한을 쉽게 얻게 해주는 킷)
# chkrootkit 설치
sudo apt install chkrootkit
# 루트킷 체크
sudo chkrootkit
bastion 서버의 공개키를 등록한 이후에는 bastion 서버를 통해서 shell에 접속한다.
bastion $ ssh reverse-proxy
reverse proxy의 역할을 하기 위해 Nginx를 사용하려고 한다. Nignx를 도커를 통해 사용할 것이다.
도커를 사용하기 위해 도커를 설치한다.
# 최신 버전으로 패키지 업데이트
sudo apt-get update
# 도커 다운을 위해 필요한 패키지 설치
sudo apt-get install apt-transport-https # 패키지 관리자가 https를 통해 데이터 및 패키지에 접근할 수 있도록 해준다.
sudo apt-get install ca-certificates # certificate authority에서 발행되는 디지털 서명. SSL 인증서의 PEM 파일이 포함되어 있어 SSL 기반 앱이 SSL 연결이 되어있는지 확인할 수 있다.
sudo apt-get install curl # 특정 웹사이트에서 데이터를 다운로드 받을 때 사용한다.
sudo apt-get install software-properties-common # *PPA를 추가하거나 제거할 때 사용한다.
# curl 명령어로 도커의 공식 GPG 키를 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add
# 도커의 공식 GPG 키가 추가된 것을 확인
sudo apt-key fingerprint 0EBFCD88
# 도커의 저장소를 추가, 등록
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# 최신 버전으로 패키지 업데이트
sudo apt-get update
# 도커 설치
sudo apt-get install -y docker-ce
# 도커 실행해보기
sudo usermod -aG docker ubuntu
# 도커 compose 설치
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 권한 설정 해주기
sudo chmod +x /usr/local/bin/docker-compose
# 링크 파일 생성
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
참고
[도커 스터디#2] 개발환경 세팅 및 배포 실습
[Docker] Ubuntu에 도커 설치하기
이제 도커를 이용해서 Nginx를 설치해준다.
TLS라는 디렉토리를 만들어서 진행하겠다.
mkdir TLS
cd TLS
vi Dockerfile
Dockerfile에 아래와 같이 적어준다.
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
Dockerfile과 동일 디렉토리에 nginx.conf 파일을 만들어준다.
vi nginx.conf
nginx.conf 파일에 아래와 같이 적어준다.
events {}
http {
upstream app {
server 127.0.0.1:8080;
}
server {
listen 80; # 80으로 요청이 오면 8080으로 연결해준다는 의미
location / {
proxy_pass http://app;
}
}
}
# Dockerfile을 통해 이미지를 만들어준다.
sudo docker build -t reverse-proxy:0.0.1 . # -t는 새로 생성할 이미지 이름을 지정하는 것. 마지막의 .은 Dockerfile의 경로를 지정해주는 것이다. 동일 디렉토리이므로 .
# 도커 실행
sudo docker run -d -p 80:80 --name proxy reverse-proxy:0.0.1 # proxy라는 이름으로 새로운 컨테이너를 실행하라는 명령. -d는 실행되는 컨테이너를 백그라운드로 실행하고 컨테이너 id를 출력, -p 80:80은 [호스트에서 사용하는 port]:[컨테이너에서 사용하는 port]를 80:80으로 발행. 즉, host의 80 포트를 컨테이너의 80포트로 매핑
도커 실행 후, nginx가 잘 동작하는지 확인하기 위해 아래의 명령어를 입력해보자
curl localhost
아래와 같이 나오면 성공!
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.19.10</center>
</body>
</html>
서버와 클라이언트 간 통신의 보안을 위해 TLS를 설정해주는 것이 좋다. 좋으니까 한 번 해보자.
먼저 reverse proxy용 EC2 인스턴스의 DNS를 설정해준다. 무료 도메인 등록 사이트인 내도메인.한국을 이용해보겠다.
사이트 접속 후, 원하는 도메인 명을 검색하여 사용할 수 있는 도메인 명을 등록한다. 아래와 같이 EC2 인스턴스의 퍼블릭 IP를 적어주면 된다.(퍼블릭 ip는 서버를 껐다키면 변경될 수 있으므로 탄력적 ip를 설정해주고 탄력적 ip를 사용하는 것이 좋다.)
이제 등록된 DNS로 TLS를 설정해준다. letsencrypt를 사용해서 무료로 TLS 인증서를 사용할 수 있다.
sudo docker run -it --rm --name certbot \
-v '/etc/letsencrypt:/etc/letsencrypt' \
-v '/var/lib/letsencrypt:/var/lib/letsencrypt' \
certbot/certbot certonly -d 'air-subway.p-e.kr' --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory
(만약 서브 도메인을 쓰려면 도메인 주소에 와일드 카드를 붙여줘야한다. ex) woowa.air-subway.p-e.kr.
을 사용한다면 *.air-subway.p-e.kr
이런식으로 넣어줘야함!)
명령어를 입력하면 email을 입력하라는 문장이 나오는데, 이메일을 입력하면 인증서가 만료되면 메일을 보내준다고 한다.
성공적으로 TLS 인증서가 발급되면 아래와 같이 나온다.
이걸 TXT 설정에 적어주면 된다.
도메인 정보 수정 후, 터미널에서 엔터를 누르면 TLS 설정 끝!
DNS에 TLS 설정은 완료했고, 이제 Reverse Proxy에 TLS 설정을 해줘야한다.
EC2 서버의 /etc/letsencrypt/live/air-subway.p-e.kr
경로에 TLS 인증서가 생겨있을 것이다.
이 중 fullchain.pem과 privkey.pem만 사용하면 되므로 2개만 현재 경로로 옮겨주자.
sudo cp /etc/letsencrypt/live/air-subway.p-e.kr/fullchain.pem ./
sudo cp /etc/letsencrypt/live/air-subway.p-e.kr/privkey.pem ./
Dockfile에 인증서 관련 부분을 추가해준다.
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY fullchain.pem /etc/letsencrypt/live/air-subway.p-e.kr/fullchain.pem
COPY privkey.pem /etc/letsencrypt/live/air-subway.p-e.kr/privkey.pem
nginx.conf 파일도 수정해준다. 이때 중요한 것은 sever 부분에 WAS 용 EC2 인스턴스의 private ip 주소를 넣어줘야 된다는 것이다!
events {}
http {
upstream app {
server [WAS 인스턴스의 private ip 주소!]:8080;
}
# Redirect all traffic to HTTPS
server {
listen 80;
return 308 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/air-subway.p-e.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/air-subway.p-e.kr/privkey.pem;
# Disable SSL
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# 통신과정에서 사용할 암호화 알고리즘
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
# Enable HSTS
# client의 browser에게 http로 어떠한 것도 load 하지 말라고 규제.
# 이를 통해 http에서 https로 redirect 되는 request를 minimize 할 수 있다.
add_header Strict-Transport-Security "max-age=31536000" always;
# SSL sessions
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://app;
}
}
}
설정을 변경해줬으니 도커를 다시 실행시키자.
sudo docker stop proxy
sudo docker rm proxy
sudo docker build -t reverse-proxy:0.0.2 .
sudo docker run -d -p 80:80 -p 443:443 --name proxy reverse-proxy:0.0.2
revers proxy 용 EC2 인스턴스 세팅이 끝났다! 👏👏👏
bastion 서버의 공개키를 등록한 이후에는 bastion 서버를 통해서 shell에 접속한다.
bastion $ ssh was
java 기반의 spring boot를 사용할 것이기 때문에 먼저 JAVA를 설치해준다.
sudo apt update
sudo apt install default-jre
sudo apt install default-jdk
설치 되었는지 확인해보자!
GitHub을 이용해서 spring boot 프로젝트를 가져와서 실행시켜보려고 한다. 내가 배포해 볼 프로젝트는 3개의 프로파일 설정을 가지고 있다.
1) test - h2 DB 사용
2) local - docker(mysql) 사용
3) prod - mysql 사용
local은 실제 개발시 사용해보는 환경이므로 배포때는 신경쓰지 않는다.
2), 3) 프로파일 설정은 자동으로 schema.sql
을 실행하도록 해주었다.
혹시 따라해보고 싶은 분들이 있을까봐 포스팅에 사용한 프로젝트 저장소를 첨부한다.
git clone [원격 저장소 주소]
cd [저장소]
받아온 프로젝트를 빌드한다.
./gradlew clean build # gradle인 경우
빌드가 완료되면 jar 파일이 생성된다. 생성된 jar파일의 위치는 아래의 명령어로 찾을 수 있다.
find ./* -name "*jar"
jar 파일을 실행시켜보자. 현재는 mysql을 설치하지 않았기 때문에, h2를 사용하여 제대로 동작하는지 확인해보자.
java -jar -Dspring.profiles.active=test [jar파일]
postman이나 curl로 제대로 동작하는지 확인할 수 있다.
이 어플리케이션에서 발생하는 로그를 AWS의 Cloudwatch로 수집해보려고한다.
먼저 AWS EC2에 접속하여 WAS 인스턴스에 cloudwatch와 관련된 I AM role을 설정해준다.
WAS shell로 돌아와서
cloudwatch logs agent를 설치하자.
curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O
sudo python ./awslogs-agent-setup.py --region ap-northeast-2
python command not found
메세지가 뜨면 파이썬을 설치해주고 다시 실행
# 파이썬 설치
sudo apt install python
설치 후, 로그 설정 파일에 접속한다.
sudo vi /var/awslogs/etc/awslogs.conf
수집할 로그를 아래 양식에 맞게 설정해준다.
[/var/log/syslog]
datetime_format = %b %d %H:%M:%S
file = /var/log/syslog
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = end_of_file
log_group_name = [로그 그룹 이름]
나는 sys, application 총 2개의 로그를 수집하려고 한다.
설정을 반영하기 위해 restart 해주기
sudo service awslogs restart
그 후 로그 파일 위치를 포함하여 jar 파일을 다시 실행 시킨다.
nohup java -jar -Dspring.profiles.active=test [jar 파일] 1> /home/ubuntu/application.log 2>&1 &
AWS CloudWatch에서 로그 그룹을 검색해보면 제대로 생성된 것을 볼 수 있다.
bastion 서버의 공개키를 등록한 이후에는 bastion 서버를 통해서 shell에 접속한다.
bastion $ ssh db
도커 컨테이너에 MySQL을 설치해도 되지만, 일반적으로 실제 운영 환경에서 컨테이너로 데이터베이스의 영속성 데이터를 다루지는 않는다.
따라서 도커 컨테이너를 통해서가 아닌 서버 자체에 MySQL을 설치한다
sudo apt update
sudo apt install mysql-server
sudo service mysql status # 잘 동작하는지 확인!
설치를 완료했으면 mysql에 접속해보자. schema.sql
이 대소문자 구분에 영향을 받는다면(우분투에 도커 MySQL 설치하기 - MySQL 대소문자 관련 이슈 부분을 참고) 대소문자 구분하지 않게 설정을 바꿔줘야한다.(위 링크에서 도커의 컨테이너에서 했던 일을 shell에서 똑같이 하면 된다.)
sudo mysql -uroot -p
유저를 생성해주고 권한을 주겠다. 이때 WAS 서버의 private ip로만 소통하게 해준다.
mysql> create user '[user]'@'[WAS private ip]' identified by '[password]';
mysql> grant all privileges on *.* to '[user]'@'[WAS private ip]'; # 권한 부여
mysql> flush privileges; # 권한 적용
테이블도 만들어준다.
mysql> CREATE DATABASE [db_name] DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
shell로 나온 후, mysql 설정에서 외부에서 접근할 수 있게 해준다. bind-address에 db인스턴스의 private ip를 적어주거나, 0.0.0.0
을 적어준다.(bind-address가 해당 url로 요청이 들어오면 접근을 허용해준다는 뜻인듯)
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
+) 요즘은 보통 AWS의 보안 그룹 같은 걸 사용해서 필터해주기 때문에, bind-address를 0.0.0.0(네트워크 전체 대역, 모든 트래픽을 허용)으로 두고, 다른 거로 필터해준다고한다.
설정을 바꿨으니 mysql 재시작~
sudo service mysql restart
그리고 이때 spring boot의 application.yml 파일에서 MySQL 인스턴스의 Private IP주소로 url을 적어줘야한다.
그리고 WAS 인스턴스에서 바뀐 jar파일을 다시 빌드한 후 mysql을 사용하는 프로파일(여기선 prod)로 실행시키면 끝!
WAS $ nohup java -jar -Dspring.profiles.active=test [jar 파일] 1> /home/ubuntu/application.log 2>&1 &