본문 바로가기

일상

nextstep - 인프라 공방 1주차

나는 자바 개발인데? 왜 인프라에 대해 공부하는지에 대해 먼저 얘기를 하는것이 좋을 것 같다.

 

 

면접을 보다 보면 Spring에 대한 질문도 하지만, AWS 인프라 관련한 질문도 많이 들었다.

멋사에서 EC2에서 docker 로 이미지 올리는 자동화 기능을 추가한적이 있다.

 

aws에 탭에 대해서 얘기를 하라고 하면 자신이 없었다.

이렇게 까지는 아니여도 인프라적인 https 나 로그 관련한 일들을 공부하고 싶어 수업을 듣게 되었다.

 

VPC및 서브넷을 만들고 자바를 실행하는 것 까지를 진행하였다.

 

첫번째는 EC2를 만들었다.

크게 public(web), internal(DB) , Baston(baston) 세개를 만들었다.

 

OS는 우분투, instanct Type은 t3.medium 을 생성하였다.

 

 

 

두번째로 VPC를 만들었다.

 

 

 

VPC는 먼저 만든 사람 순서대로 192.168.순서.0/24으로 만들었다.

 

 

서브넷을 총 4개를 만드는데, public은 두개, manager 은 보스턴 EC2에 internal은 DB와 연결하도록 하였다.

서브넷을 제어하기 위해 라우팅 테이블을 만들어 둔다.

 

 

 

이 상태로 EC2에서 인터넷이 연결이 안되어 있어 필요한 라이브러리나 다른 것을 설정할 수 없으므로 public 과 bostan에는 인터넷 게이트를 연결하도록 한개를 만들어 연결합니다.

 

 

 

internal 쪽은 nat 게이트를 만들어 인터넷을 끌어다 쓰는 식으로 사용한다.

 

 

 

탄력적 ip를 이용하여 고정된것을 사용하기 위해 이것도 추가하였다.

 

 

 

이제 보안 그룹을 설정을 해야하는데, bostan 에서는 내 ip에서 접속 및 멘토님께서 접속되게 22 포트를 두개 열었다.

 

 

DB EC2도 3306를 public-1번, 2번이 연결되도록 수정하였다.

ssh 도 bostan 서버에서만 연결되도록 수정 하였다.

 

 

public 에서는 보안그룹으로 8080은 java실행되면 알수 있게, 443과 80은 https를 위해 또, 22은 bostance에서 연결될 수 있게 끔 설정을 한다.

 

 

 

 

EC2 접속 방법은 key 페어를 이용하여 접속을 할것이다.

 

# 터미널 접속한 후 앞 단계에서 생성한 key가 위치한 곳으로 이동한다.
$ chmod 400 [pem파일명]
$ ssh -i [pem파일명] ubuntu@[SERVER_IP]

 

이 형식으로 들어갈 수 있게 된다.

 

현재 내 설정으로는 내IP에서는 보스턴이라는 서버만 접속이 가능하게한 상태여서 여기서 web server 와 DB 서버에 접속하도록 하겠하였다.

다른 EC2에 갈때도 pem을 써서 가야한다는 단점이 있다.

그래서 bostan server key생성하여 다른 EC2에 저장하여 사용하도록 사용하였다.

 

## Bastion Server에서 공개키를 생성합니다.
ssh-keygen -t rsa
cat ~/.ssh/id_rsa.pub

 

web server와 DB서버에 이 키를 저장을 한다.

 

vi ~/.ssh/authorized_keys

 

bostan server에서 별칭을 써서 더 쉽게 접속 가능하게 수정을 하였다.

 

vi /etc/hosts

[서비스용IP]    [별칭]

 

그런 이제 ssh [별칭]으로 쉽게 이동 되게 수정되었다.

 

 

2. 서버 환경 설정

 

보안을 위해 Session Timeout 설정을 위해 이렇게 설정을 하였습니다.

 

$ sudo vi ~/.profile
  HISTTIMEFORMAT="%F %T -- "    ## history 명령 결과에 시간값 추가
  export HISTTIMEFORMAT
  export TMOUT=600              ## 세션 타임아웃 설정 
    
$ source ~/.profile
$ env

 

logger을 사용하여 감사 로그 세팅을 해보겠다.

 

$ 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
  # 원격지에 로그를 남길 경우 
  local0.*                        @원격지서버IP
    
$ sudo service rsyslog restart
$ tail -f /var/log/command.log

 

원격이나 현재 EC2에 커맨드 log를 저장한다.

 

 

3. 자바 웹서비스 띄우기

 

git clone [저장 repository url] 을 사용하여 저장되어 있는 프로젝트를 EC2에 저장한다.

저장한 위치에 가서 빌드를 시도한다.

 

$ ./gradlew clean build

# jar파일을 찾아본다.
$ find ./* -name "*jar"

 

gradlew clean build로 war나 jar로 gralde설정대로 압축을 해제한다. -> 저는 jar로 해제했습니다.

 

 

$  nohup java -jar [jar파일명] 1> [로그파일명] 2>&1  &

 

빌드가 실행되었다면, 파일명을 찾아 위의 명령어로 실행합니다.

nohup은 EC2가 접속 끊겨도 살아남게 꼭 사용합니다.

 

 

또한, PID로 실행한 프로세스를 종료할 수 있다.

 

$ ps -ef | grep java
$ pgrep -f java

 

로 pid를 찾은다음, kill -9 로 접속을 끊을 수 있다.

 

 

4. nginx로 https 웹 서비스 띄우기

 

nginx은 직접 설치하는 방법도 있지만, docker를 사용하여 nginx을 사용하는 시간을 가집니다.

 

$ sudo apt-get update && \
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && \
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 && \
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

 

docker를 먼저 설치하게 된다.

 

https를 위해 인증서를 먼저 받는다.

 

docker run -it --rm --name certbot \
  -v '/etc/letsencrypt:/etc/letsencrypt' \
  -v '/var/lib/letsencrypt:/var/lib/letsencrypt' \
  certbot/certbot certonly -d 'yourdomain.com' --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

 

let's encrypt를 이용할것이고 실행하게 되면 중간에 TXT 관련 key값이 나올것입니다.

이것을 도메인에 넣으면 되는데 저는 여기 사이트를 이용하였습니다.

 

 

 

도메인을 무료로 받고 수정에서 고급설정에 key를 넣는다. EC2 public ip 도 미리 넣어둔다.

 

$ cp /etc/letsencrypt/live/[도메인주소]/fullchain.pem ./
$ cp /etc/letsencrypt/live/[도메인주소]/privkey.pem ./

 

letencrypt에서 받은 무료 인증서를 원하는 위치에 넣고 nginx를 돌릴때 사용할것이다.

 

Dockerfile을 만들어준다.

 

FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf 
COPY fullchain.pem /etc/letsencrypt/live/[도메인주소]/fullchain.pem
COPY privkey.pem /etc/letsencrypt/live/[도메인주소]/privkey.pem

 

nginx.conf도 미리 만들어 둔다.

 

events {}

http {       
  upstream app {
    server 172.17.0.1:8080;
  }
  
  # Redirect all traffic to HTTPS
  server {
    listen 80;
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl;  
    ssl_certificate /etc/letsencrypt/live/[도메인주소]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[도메인주소]/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;    
    }
  }
}

 

방금 전에 만든 dockerfile을 실행해 준다.

 

$ docker stop proxy && docker rm proxy
$ docker build -t nextstep/reverse-proxy:0.0.2 .
$ docker run -d -p 80:80 -p 443:443 --name proxy nextstep/reverse-proxy:0.0.2

 

Spring boot에서는 실행할때 -Dspring.profiels.active 를 옵션으로 실행하여 profile 별 실행할 수 있다.

 

java -jar -Dspring.profiles.active=prod ...

 

 

이제 깃에서 수정되면 자동으로 배포가 되게 crontab을 이용하여 shell script을 실행하는 프로세스를 만들었다.

 

#!/bin/bash

## 변수 설정

txtrst='\033[1;37m' # White
txtred='\033[1;31m' # Red
txtylw='\033[1;33m' # Yellow
txtpur='\033[1;35m' # Purple
txtgrn='\033[1;32m' # Green
txtgra='\033[1;30m' # Gray

PID=0
BRANCH=$1
PROFILE=$2
BUILD_FILE=subway-0.0.1-SNAPSHOT.jar

## 저장소 pull
function pull() {
  echo -e ""
  echo -e "${txtpur}>> Pull Request 🏃♂️ ${txtrst}"
  git pull origin $BRANCH
}

## gradle build
function gradle_build() {
  echo -e ""
  echo -e "${txtpur}>> gradle build${txtrst}"
  ./gradlew clean build

}

## 프로세스 pid를 찾는 명령어
function find_pid() {
  echo -e ""
  echo -e "${txtpur}>> find running process id${txtrst}"
  PID=$(pgrep -f ${BUILD_FILE})
  echo -e "${txtred}$PID${txtrst}"
}

## 프로세스를 종료하는 명령어
function kill_pid() {
  echo -e ""
  if [[ $PID == 0 ]]; then
    echo "${txtred}isn't running process${txtrst}"
  else
    echo -e "${txtpur}>> kill process $pid ${txtrst}"
    kill -9 $pid
  fi
}

# jar 실행
function deploy() {
  echo -e ""
  echo -e "${txtpur}>> deploy ${txtrst}"
  echo -e "$( find ./* -name "*subway*jar")"
  nohup java -jar -Dspring.profiles.active=${PROFILE} $( find ./* -name ${BUILD_FILE}) >1 nextstep.log 2>&1  &
}

# 변경이 있을 경우 pull
function check_df() {
  git fetch

  master=$(git rev-parse $BRANCH)
  remote=$(git rev-parse origin/$BRANCH)

  if [[ $master == $remote ]]; then
    echo -e "[$(date)] Nothing to do!!! 😫"
    exit 0
  fi
}

## 조건 설정
if [[ $# -ne 2 ]]
then
    echo -e "${txtylw}=======================================${txtrst}"
    echo -e "${txtgrn}  << 스크립트 🧐 >>${txtrst}"
    echo -e ""
    echo -e "${txtgrn} $0 브랜치이름 ${txtred}{ test | local | prod }"
    echo -e "${txtylw}=======================================${txtrst}"
    exit
fi

echo -e $BRANCH
echo -e $PROFILE

check_df;
gradle_build;
find_pid;
kill_pid;
deploy;

 

저는 이런식으로 수정을 하였다.