Java & Spring/스프링 부트와 AWS로 혼자 구현하는 웹 서비스

8장) EC2 서버에 프로젝트를 배포해 보자

Zin0_0 2020. 7. 14. 23:55
반응형

EC2에 프로젝트 Clone 받기

  • git 설치

    • sudo yum install git으로 git 설치
    • git --version으로 설치 상태 체크
  • 프로젝트 저장할 디렉토리 만들기

    • mkdir ~/app && ~app/step1 생성
    • cd ~/app/step1 이동
  • git clone하기

    • git clone 깃헙주소
  • 테스트로 코드 검증하기

    • ./gradlew test
    • 성공했다면, BUILD SUCCESSFUL이 뜬다.
    • 여기서 한 번 실패가 떴는데, 실행권한이 없다는 표시가 떴다.
    • chmod +x ./gradlew로 권한을 부여해줘서 해결

배포 스크립트 만들기

  • 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 비효율적

    • 쉘 스크립트로 작성해서 스크립트만 실행하면, 위의 과정이 진행되도록 생성

    • 쉘 스크립트

      • .sh라는 파일 확장자를 가진 파일
      • 노드JS가 .js라는 파일을 통해 서버에서 작동하듯, 쉘 스크립트는 리눅스에서 기본적으로 사용 가능한 스크립트 파일의 한 종류
      • Vim은 편집도구
    • ~/app/step1/에 deploy.sh 파일 생성

      • vim ~app/step1/deploy.sh
        #!/bin/bash

        REPOSITORY=/home/ec2-user/app/step2
        PROJECT_NAME=springboot-webservice

        cd $REPOSITORY/$PROJECT_NAME/

        echo "> Git pull"

        git pull

        echo "> 프로젝트 Build 시작"

        ./gradlew build

        echo "> step1 디렉토리로 이동"

        cd $REPOSITORY

        echo "> Build 파일 복사"

        cp $REPOSITORY/PROJECT_NAME/build/libs/*.jar $REPOSITORY/

        echo "> 현재 구동중인 애플리케이션 pid 확인"

        CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)

        echo "현재 구동중인 어플리케이션 pid: $CURRENT_PID"

        if [ -z "$CURRENT_PID" ]; then

          echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."

        else

          echo "> kill -15 $CURRENT_PID"
          kill -15 $CURRENT_PID
          sleep 5

        fi

        echo "> 새 어플리케이션 배포"

        JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)

        echo "> JAR Name: $JAR_NAME"

        nohup java -jar $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

      • REPOSITORY=/home/ec2-user/app/step2

        • 프로젝트 디렉토리 주소는 스크립트 내에서 자주 사용 ~> 변수로 저장
        • PROJECT_NAME 도 마찬가지
        • 쉘에서는 타입 없이 선언해서 저장
        • $ 변수명으로 변수 사용
      • cd $REPOSITORY/$PROJECT_NAME/

        • git clone을 받았던 디렉토리로 이동
      • git pull

        • 디렉토리 이동 후, master 브랜치에서 최신 내용을 pull
      • ./gradlew build

        • 프로젝트 내부의 gradlew로 build 수행
      • cp $REPOSITORY/PROJECT_NAME/build/libs/*.jar $REPOSITORY/

        • build의 결과물인 jar 파일을 복사해 jar 파일을 모아둔 위치로 복사함
      • CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)

        • 기존에 수행 중이던 스프링 부트 애플리케이션 종료
        • pgrep => process id만 추출하는 명령어
        • -f 옵션 => 프로세스 이름으로 찾음
      • JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)

        • 새로 실행할 jar 파일명을 찾는다.
        • 여러 jar파일이 생겨서, tail -n으로 가장 나중의 jar 파일(최신)을 변수에 저장
      • nohup java -jar $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

        • 찾은 jar파일명으로 해당 jar 파일을 nohup으로 실행
        • 일반적으로 자바를 실행할 때는 java -jar라는 명령어를 사용 ~> 사용자가 터미널 접속을 끊을 때 어플리케이션도 종료됨
        • 애플리케이션 실행자가 터미널을 종료해도 애플리케이션은 계속 구동될 수 있도록 nohup 명령어를 사용
    • 쉘 스크립트에 권한 추가

      • chmod +x ./deploy.sh
    • 쉘 스크립트 실행

      • ./deploy.sh
      • 여기서 오류가 한 번 발생했었는데, 쉘 스크립트 내부에서 변수를 선언하고 저장할 때 띄어쓰기를 하면 안된다. (a=1 (O) <------> a = 1 (X) 이걸로 시간을 조금 낭비했다)
      • 성공적으로 실행이 되면 BUILD SUCCESSFUL이 나온다.
    • 실행되는 애플리케이션에서 출력되는 내용 확인

      • vim nohup.out
      • nohup으로 실행하면, 기본적으로 로그가 nohup.out에 저장된다.
      • 위의 실행결과로 Fail이 뜨는데, Security 파일인(application-oauth.properties가 없기 때문)

외부 Security 파일 등록하기

  • 위에서 ClientRegistrationRepository 생성 하려고 할 때, clientId와 clientSecret이 없었다. (생성 시 필수) ~> application-oauth.properties가 .gitignore에 포함됐기 때문 ( 민감정보 보호 )
  • step1이 아닌, app 디렉토리에 properties 파일을 생성 (다른 디렉토리에서도 사용하기 위해)
    • vim /home/ec2-user/app/application-oauth.properties
      • 로컬에 있는 application-oauth.properties를 그대로 복붙하기
        • putty에서는 로컬에서 저장한 클립보드를 오른쪽 마우스 클릭으로 붙여넣기 가능
  • application-oauth.properties을 쓰도록 deploy.sh 파일 수정
    ...
    nohup java -jar \
        -Dspring.config.location=classpath:/application.properties,/home/ec2-user/app/application-oauth.properties \
        $REPOSITORY/$JAR_NAME 2>&1 &
    • -Dspring.config.location
      • 스프링 설정 파일 위치를 지정
      • classpath가 붙으면 jar 안에 있는 resources 디렉토리를 기준으로 경로가 생성
      • application-oauth.properties은 절대경로로 사용 ~> 외부에 파일이 있기 때문에
    • 여기에서도 에러가 한 번 발생했었다. 띄어쓰기에 주의해야함

스프링 부트 프로젝트로 RDS 접근하기

  • RDS는 MariaDB 사용, 다음 세 작업이 필요
    • 테이블 생성 : H2에서 자동으로 생성해주던 테이블들을 MariaDB에선 직접 쿼리로 생성
    • 프로젝트 설정 : 자바 프로젝트가 MariaDB에 접근하려면 데이터베이스 드라이버가 필요
      ~> MariaDB에서 사용 가능한 드라이버를 프로젝트에 추가
    • EC2(리눅스 서버) 설정 : DB 접속 정보는 중요하게 보호해야할 정보 ~> EC2 서버 내부에서 접속 정보를 관리하도록 설정
  • RDS 테이블 생성
    • JPA가 사용될 엔티티 테이블과 스프링 세션이 사용될 테이블 2 종류를 생성
      • JPA가 사용될 엔티티 테이블
        • 테스트 코드 수행 시 로그로 생성되는 쿼리 사용
        • Hibernate: create table posts , Hibernate: create table user ~
      • 스프링 세션 테이블
        • schema-mysql.sql 파일에서 복붙
        • Shift + ctrl + N에서 위의 파일 검색
  • 프로젝트 설정
    • MariaDB 드라이버 build.gradle에 등록하기
      • compile("org.mariadb.jdbc:mariadb-java-client")
    • 서버에 구동될 환경 구성하기
      • 로컬 src/main/resources/에 application-real.properties 파일 추가
        • profile=real인 환경이 구성된다고 생각하면 된다.
          spring.profiles.include=oauth,real-db
          spring.jpa.properties.hibernate.dialect=org.hibernate.spring.session.store-type=jdbc
        • 이후 커밋 ~> 푸시 ~> 서버에서 ./deploy.sh 실행 (쉘 스크립트에 풀 명령 존재)
  • EC2 설정

    • app 디렉토리에 application-real-db.properties 파일 생성

      • vim ~/app/application-real-db.properties

        spring.jpa.hibernate.ddl-auto=none
        
        spring.datasource.url=jdbc:mariadb://rds주소:포트명(기본은 3306)/database명
        spring.datasource.username=db계정
        spring.datasource.password=db계정 비밀번호
        spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
        • spring.jpa.hibernate.ddl-auto=none
          • JPA로 테이블이 자동 생성되는 옵션을 None(생성하지 않음)으로 지정
          • RDS에는 실제 운영으로 사용될 테이블 ~> 절대 스프링 부트에서 새로 만들지 않도록 설정
          • 매우매우매우 중요한 옵션 ~> 이 옵션을 안하면 테이블이 모두 새로 생성될 수 있음
    • deploy.sh 개선

      ...
      nohup java -jar \
          -Dspring.config.location=classpath:/application.properties,classpath:/application-real.properties,/home/ec2-user/app/application-oauth.properties,/home/ec2-user/app/application-real-db.properties \
          -Dspring.profiles.active=real \
          $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
      • -Dspring.profiles.active=real
        • application-real.properties를 활성화
        • application-real.properties의 spring.profiles.include=oauth,real-db 옵션 때문에 real-db 역시 함께 활성화 대상에 포함된다.
    • curl 명령어로 확인하기

      • curl localhost:8088(포트번호)
        • html 코드가 보이면 성공

EC2에서 소셜 로그인하기

  • AWS 보안 그룹 변경
    • EC2 보안그룹의 인바운드에 해당 포트가 열려있는지 확인
    • 안돼있다면, 포트 열어주기
  • AWS EC2 도메인으로 접속
    • 퍼블릭 DNS 확인하기 => EC2에 할당된 도메인
    • 퍼블릭 DNS 주소:8088(포트번호) 로 서버에 접근
    • google login과 naver login을 누르면, 작동하지 않음 ~> google, naver 서비스 등록해야함
  • 구글에 EC2 주소 등록
    • API 및 서비스 ~> Ouath 동의 화면 ~> 승인된 도메인에 EC2 퍼블릭 DNS 추가하기
      (이 때, http://는 제거해줘야한다.)
    • 사용자 인증 정보 ~> 승인된 리디렉션 URI
      • http://퍼블릭 DNS:8088/login/oauth2/code/google 추가
    • 이 과정에서 500에러가 떠서 몇 시간을 고생했다.
      • 첫번째, db에 접근할 수 없다고 나왔다.
        • 위의 경우, 서버 용량 초과 or 서버 과부화(?) 문제라는 검색 결과가 있었다.
        • ~> 재부팅하니까 해결 ( 불필요한 데몬 등이 돌아가고 있어서 과부화 가능성 )
      • 두번째, line9 에서 username을 찾을 수 없다고 나왔다.
        • 삭제하고 다시 해보고, 이번 장에서 진행한 코드들을 다 확인해봐도 이상이 없었다. 구글 설정이 잘못됐나 확인했는데도 아니었다. 그래서 nohup 로그를 뒤져보니까 mustache에서 에러가 뜬 것을 확인했다. index 파일에서 문제가 생겼음을 직감했고, 클래스 파일과 머스태치 파일을 확인했다. 그 결과, index.mustache의 9번째 줄에 userName을 출력하는 과정에서, 변수 명을 userName이 아닌 username으로 작성한 것을 발견했다... ~> 해결 (근데, username으로 해도 로컬에서는 id가 떴고 에러가 없었다.. 그래서 나는 내가 설정한 이름이 그건줄... 이상하다... ㅠ)
    • 구글 등록이 정상적으로 완료되면, 이름이 뜬다.
  • 네이버에 EC2 주소 등록
    • 서비스 URL에 EC2 퍼블릭 DNS 주소 입력
      • http://퍼블릭DNS주소/ 입력
      • 로그인을 시도하는 서비스가 네이버에 등록된 서비스인지 판단
      • 포트번호는 제외하고 실제 도메인 주소만 입력
      • 네이버 서비스는 한번에 하나의 서비스 URL만 지원하기 때문에, 로컬에서도 이용하고 싶으면, 서비스 하나를 더 만들어서 키 하나 더 발급받기
    • Callback URL에 redirect 받을 서버 주소 입력
      • http://퍼블릭DNS주소:8088/login/oauth2/code/naver 입력
반응형