RIANAEH / learning

학습 저장소
1 stars 0 forks source link

학습한 내용을 기록합니다. #2

Open RIANAEH opened 2 years ago

RIANAEH commented 2 years ago

🕐 TimeZone 설정하기

AWS EC2의 기본 TimeZone은 UTC

현재 모락 서비스는 AWS EC2 환경에 배포되어 있습니다. 모락의 백엔드 서버에서는 프로그램 실행 과정에서의 중요 내용들을 로깅하여 로그 파일에 저장하고 있는데, 이때 시간 정보 또한 출력하고 있습니다. 이 과정에서 로그 파일에 출력된 시간이 현재 한국 시간과 같지 않은 문제가 발생했습니다. 이는 EC2의 TimeZone이 기본적으로 UTC로 설정되어 있어 발생한 문제였습니다.

당시 현재 시각은 2022년 8월 8일 오후 12시 34분이었는데, 로그 파일에는 오전 3시 34분으로 찍혀있었습니다.

image Screen Shot 2022-08-10 at 11 19 19 AM

UTC vs. KST

잠시 UTC와 KST에 대한 개념을 잡고가자면, UTC(Universal Time Coordinated)는 세계 협정시를 의미하고, KST(Korean Standard Time)은 한국 표준 시간을 의미합니다. 이때 KST의 경우 UTC + 09:00시를 나타내기 때문에 로그 파일에 현재보다 9시간 전인 3시 34분으로 찍혀있었던 것이었습니다.

AWS EC2의 TimeZone 설정하기

그렇다면 이제 TimeZone을 우리가 원하는 KST로 설정해봐야겠죠?

$ date
Wed Aug 10 02:24:32 UTC 2022
$ timedatectl list-timezones | grep Seoul
Asia/Seoul
$ sudo timedatectl set-timezone Asia/Seoul
$ date
Wed Aug 10 11:26:06 KST 2022
Screen Shot 2022-08-10 at 11 28 38 AM

Java의 LocalDateTime.now()

현재 모락팀에서 구현한 로직에서는 Java의 LocalDateTime.now()를 호출하는 부분이 있습니다.

Screen Shot 2022-08-10 at 12 47 10 PM Screen Shot 2022-08-10 at 12 48 50 PM

Jenkins에는 왜 시간 설정이 적용되지 않았을까?

Screen Shot 2022-08-10 at 2 44 00 PM
$ date
Wed Aug 10 14:45:50 KST 2022

알고보니 Jenkins의 경우 본인의 시간 설정이 따로 있었습니다. (귀찮게..)

image
$ echo $JENKINS_JAVA_OPTIONS

$ JENKINS_JAVA_OPTIONS="-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Seoul"
$ echo $JENKINS_JAVA_OPTIONS
-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Seoul

젠킨스 재시작하기

sudo service jenkins restart

사이트트에서 확인해보면 timezone이 잘 변경되어 있습니다:)

image

"지금 빌드"로 직접 빌드를 해보면 빌드 시간 또한 잘 적용되어 있는 것을 확인할 수 있습니다.

image

끝!!

참고 자료

RIANAEH commented 2 years ago

🏷 PR 라벨에 따라 Jenkins 빌드 유발 설정하기

현재 모락의 Jenkins CI/CD 구조

image

morak-ci-back 어떤 브랜치든 PR 요청 시 backend 디렉토리에서 테스트를 진행하고 PR에 이에 대한 체크를 달아준다. morak-ci-front 어떤 브랜치든 PR 요청 시 frontend 디렉토리에서 테스트를 진행하고 PR에 이에 대한 체크를 달아준다. morak-was-dev dev 브랜치에 Merge 진행 시 Jenkins 서버에서 build를 진행해 jar 파일을 생성한다. 그리고 이 jar 파일을 was-dev 서버로 보내 실행시킨다. morak-was-main main 브랜치에 Merge 진행 시 Jenkins 서버에서 build를 진행해 jar 파일을 생성한다. 그리고 이 jar 파일을 was-main 서버로 보내 실행시킨다. morak-front main 브랜치에 Merge 진행 시 Jenkins 서버에서 build를 진행해 dist 디렉토리를 생성한다. 그리고 이 dist 디렉토리를 ws 서버로 보내 실행시킨다.

Generic Webhook Trigger Plugin을 설치한 후 Jenkins를 재시작해줍니다.

image

왜 CI 요청이 두번이나 가는걸까??

모락 팀은 CI/CD 도구로 Jenkins를 사용하고 있습니다. dev 브랜치에 PR 시 CI가 일어나며, main에 Merge 시 CI/CD가 일어나도록 설정했었습니다. 하지만 이 과정에는 살짝의 문제가 있었습니다. 현재 모락 서비스는 하나의 레포지토리에서 관리되고 있습니다. 프론트엔드와 백엔드의 소스 코드가 한 곳에서 관리되고있기 때문에 CI/CD 시 프론트엔드의 코드가 변경되도 백엔드 코드까지 CI/CD가 일어고 있었습니다. 저희는 이 문제를 해결하기 위해 Jenkins에서 PR과 Merge 시 라벨을 인식해 프론트엔드와 백엔드를 필터링하도록 설정하였습니다. 다른 분들에게도 도움이 되었으면 하는 마음에 해당 과정을 공유합니다. (잘못 된 부분이 있다면 알려주세요🙇‍♀️)

image

이번에 소개해드릴 방법은 두가지로, 하나는 GitHub Pull Request Builder에 라벨을 적용해 이용해 CI만을 하는 방법이고, 다른 하나는 Generic Webhook Trigger에 라벨을 적용해 CI/CD를 모두 하는 방법입니다.모락 팀에서는 두 방법 모두가 필요했었지만, 필요한 방법만 선택하시면 될 것 같습니다.

GitHub Pull Request Builder에 라벨 적용하기

GitHub Pull Request Builder Plugin 문서를 살펴보면 라벨에 따라 트리거를 설정할 수 있는 기능을 제공한다는 것을 알 수 있습니다.

Untitled

Generic Webhook Trigger에 라벨 적용하기

이 방법은 구글에 "Jenkin PR 라벨 적용" 이런 식으로 검색하면 많이 나오는 방법입니다. 처음에는 CI 과정에 Generic Webhook Trigger를 적용했었습니다. 하지만 webhook 요청이 일어났을 때 Jenkins에서 CI를 여러 번 하는 문제가 발생했고, PR의 코드를 CI하는 것이 아니라

RIANAEH commented 2 years ago

프론트, 백엔드 dev 서버 따로 구축하기

프론트 dev 인스턴스 띄우기

Nginx 설치 및 실행

sudo apt-get update
sudo apt-get install nginx -y
sudo service nginx start

WS public ip의 80번 포트로 접속하면 다음과 같이 nginx가 환영해준다!

image

가비아에서 도메인 등록

dev.mo-rak.com으로 연결했습니다.

Jenkins CD 설정하기

image

트러블 슈팅

Host key verification failed. Jenkins에서 ws 서버로 ssh 접속을 사전에 해주어야합니다. 왜?

백엔드 dev 인스턴스 띄우기

jdk 11 설치하기

apt-get update
apt-get install openjdk-11-jdk
java -version
RIANAEH commented 2 years ago

Sonar Cube vs. Sonar Clund

image

https://jojoldu.tistory.com/662

RIANAEH commented 2 years ago

📊 정적 분석 리포트를 공유한다.

정적 분석

요구사항을 보고 1차적으로 "정적 분석이란 뭘까?"라는 의문이 들었습니다.

정적 분석 도구

정적 분석 도구에는 PMD, SonarQube, checkstyle, cppcheck 등이 있습니다.

Sonar Blog: SonarCloud vs. SonarQube

현재 상황

현재 모락의 Github 저장소에 PR이 오면 Jenkins에서는 이에 대한 빌드(테스트)를 진행하고, 해당 PR의 코멘트로 결과를 남겨줍니다. 이때 빌드가 실패하면 Merge를 할 수 없도록 하고 있습니다.

목표

현재 구조에서 모락의 Github 저장소에 PR이 오면,

  1. 빌드를 진행하고, PR의 코멘트로 결과를 남겨준다.
  2. 해당 PR의 코드를 정적 분석해 리포트를 생성하고, PR의 코멘트로 결과를 남겨준다.

EC2(Ubuntu)에 SonarQube 설치하기

SonarQube를 설치할 새로운 인스턴스 생성합니다.

SonarQube를 설치합니다.

설치할 때 소나큐브 공식 사이트에서 최신 버전과 LTS를 확인 후 원하는 버전을 선택해 다운받습니다.

unzip을 다운 받고, 설치한 소나큐브의 압축을 풀어줍니다. 설치 완료:)

sudo apt -y install unzip
sudo unzip sonarqube-8.9.9.56886.zip

소나큐브가 사용할 포트를 변경해줍니다.

conf/sonar.properties에서 다음과 같이 사용할 포트를 설정할 수 있습니다. 따로 설정을 하지 않으면 소나큐브는 9000 포트를 사용합니다.

# TCP port for incoming HTTP connections. Default value is 9000.
sonar.web.port=8080

SonarQube 실행하기

Screen Shot 2022-08-10 at 9 28 39 PM
~/sonarqube-8.9.9.56886/bin/linux-x86-64$ ./sonar.sh start
Starting SonarQube...
./sonar.sh: 1: eval: /home/ubuntu/sonarqube-8.9.9.56886/bin/linux-x86-64/./wrapper: Exec format error
Failed to start SonarQube.

1.8 버전 이상의 자바를 설치해야합니다.

sudo apt-get install openjdk-11-jdk
java -jar lib/sonar-application-8.9.9.56886.jar
Screen Shot 2022-08-10 at 9 43 59 PM
sudo chown -R ubuntu:ubuntu sonarqube-8.9.9.56886/
Screen Shot 2022-08-11 at 10 26 00 AM

'메모리가 부족한 것인가?'하는 의심이 들었습니다.

Screen Shot 2022-08-11 at 10 32 41 AM

실행한 SonarQube에는 http://[EC2 인스턴스 IP 주소]:[port 번호]로 접속할 수 있습니다. 첫 로그인 시 ID와 Password는 admin으로 이후 자유롭게 변경하면 됩니다.

SonarQube 접속 완료!!

image

SonarQube에서는 외부 데이터베이스를 사용하기를 권장합니다.

image

Jenkins 설정하기

SonarQube Scanner 플러그인 설치하기

Jenkins의 Manage Jenkins > Manage Plugins에서 SonarQube Scanner for Jenkins를 설치합니다.

SonarQube Server 설정하기

Manage Jenkins > Configure System > SonarQube Server에서 설정을 진행합니다.

SonarQube Scanner 설정하기

Manage Jenkins > Global Tool Configuration > SonarQube Scanner에서 설정을 진행합니다.

Jenkins 프로젝트에 SonarQube 적용하기

sonar.host.url=http://sonarqube.mo-rak.com:8080
sonar.login=a605c313e1754fe7dede2cea6f97a7db00085ed3
sonar.projectName=morak-backend
sonar.projectKey=morak-backend
sonar.sources=backend/src
sonar.lanquage=java
sonar.projectVersion=1.1.0-SNAPSHOT
sonar.sourceEncoding=UTF-8
sonar.java.binaries=classes
sonar.test.inclusions=**/*Test.java
sonar.exclusions=**/resources/**, **/test/**
image
RIANAEH commented 2 years ago

AWS Cloud Watch

RIANAEH commented 2 years ago

🏷 PR 라벨에 따라 Jenkin 빌드 유발 설정하기 (feat. GitHub Pull Request Builder Plugin)

기존 CI 방식의 문제점

모락팀은 모든 PR 시 Jenkins에서 빌드를 진행해 테스트의 성공/실패를 Github Check Status로 표시하고 Merge를 제한하도록 하고 있었습니다.

image

하지만 이 과정에는 살짝의 문제가 있었습니다. 현재 모락 서비스는 하나의 레포지토리에서 관리되고 있습니다. 프론트엔드와 백엔드의 소스 코드가 한 곳에서 관리되고있기 때문에 프론트엔드만의 코드를 변경 후 PR을 보내도 백엔드 코드까지 빌드하는 리소스 낭비가 일어나고 있었습니다.

image

모락팀에서는 다음과 같이 PR에 라벨을 붙여 구분하고 있기 때문에, 저희는 Jenkins에서 이 라벨을 인식해 프론트엔드에서의 PR과 백엔드에서의 PR을 구분하도록 설정을 추가하였습니다.

image

저희는 기존에 GitHub Pull Request Builder Plugin을 추가해 PR 시 CI 과정만 일어나도록 하는 아이템을 사용하고 있었습니다. 따라서 다음의 과정은 GitHub Pull Request Builder Plugin을 사용하고 있었다는 상황을 전제로 작성된 내용임을 알려드립니다.

GitHub Pull Request Builder에 라벨 적용하기

GitHub Pull Request Builder Plugin 문서를 살펴보면 라벨에 따라 트리거를 설정할 수 있는 기능을 제공한다는 것을 알 수 있습니다.

Untitled

Jenkins 아이템의 구성에 들어가 조금 더 세부적으로 살펴볼까요?

GitHub Pull Request Builder의 고급 버튼을 클릭하면 여러가지 추가 설정을 할 수 있습니다. 그 중 특정 라벨이 붙어 있으면 트리거되지 않도록 설정하는 부분특정 라벨이 붙어 있을 때만 트리거되도록 설정하는 부분이 있는 데, 저희는 두번째 설정에 다음과 같이 💻 backend 라벨을 넣어줬습니다.

여러개의 라벨을 넣고 싶을 경우 개행으로 구분해 입력하면 된다고 합니다.

image

Next

이 다음에는 탈도 많고 에러도 많았던 Generic Webhook Trigger에 라벨을 적용하는 과정을 소개해드리려고 합니다.

RIANAEH commented 2 years ago

Request Body 로깅 하기

HttpMessageNotReadableException

모락팀에서는 500 에러가 나는 상황에 대해 에러에 대한 스택 트레이스 뿐만 아니라 요청 정보도 로깅한다면 에러 상황을 재현해봄으로써 디버깅을 수월하게 할 수 있을 것이라고 생각했다. 하지만 우리는 요청 정보에 대한 출력은 커녕 HttpMessageNotReadableException을 만나게됐다. 먼저 해당 예외는 HttpServletRequest의 Body 정보를 2번 이상 읽으려고 시도할 때 발생한다.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CacheBodyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
                new CachedBodyHttpServletRequest(httpServletRequest);

        filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
    }
}

⛈ ContentCachingRequestWrapper

ContentCashingRequestWrapper의 docs를 읽어보면, 이 클래스는 input stream이나 reader를 읽을 때만 캐싱을 진행하고 캐싱 된 데이터는 getContentAsByteArray() 메서드를 통해 byte array로 제공한다고 합니다. 즉, 여전히 getInputStream() 메서드와 getReader() 메서드는 단 한번만 사용가능하며, 둘 중 하나의 메서드가 호출 된 이후에야 getContentAsByteArray() 메서드로 캐싱된 데이터를 불러와 사용할 수 있습니다.

image

image image image

다음과 같이 로깅 데이터에 body가 잘 담겨있는 모습을 확인할 수 있습니다.

image

참고 자료

RIANAEH commented 2 years ago
image

park algorithm 따라잡기!!

Let's Encrypt

certbot 설치하기

sudo apt update
sudo apt upgrade
sudo apt-get install letsencrypt -y
sudo add-apt-repository ppa:certbot/certbot
sudo apt install python3-certbot-nginx

SSL 인증서 받기

sudo certbot --nginx -d dev.mo-rak.com
image

certbot 자동 갱신하기

sudo certbot renew --dry-run

image

참고 자료

RIANAEH commented 2 years ago

📋 develop 환경에서만 API 문서에 접근할 수 있게 하기

고려했던 방법들

  1. API 문서를 반환하는 API를 새로 만든다.
  2. static/docs/index.html에 파일을 위치시키고, ws-dev의 nginx 설정에서 /docs로 요청 시 was-dev로 포워딩 되도록 추가 설정한다.

index.html 경로 설정하기

task createDocument(type: Copy) {
    dependsOn asciidoctor

    from file("build/docs/asciidoc")
    into file("src/main/resources/static/docs") // index.html 경로를 /docs로 설정합니다.
}

Nginx에서 location 설정하기

/etc/nginx/sites-available/default에서 location을 설정할 때 정규표현식을 사용할 수 있습니다.

location ~ ^/(api|docs) {
    ...
}

API 문서에 접근하기

dev.mo-rak.com/docs/index.html로 접근 시 API 문서가 잘 호스팅됩니다. 오예!!

Screen Shot 2022-08-10 at 2 19 49 PM
RIANAEH commented 2 years ago
server {
        listen 8081 default_server;
        listen [::]:8081 default_server;

        server_name dev.mo-rak.com;

        location ~ ^/(api|docs) {
                proxy_pass http://192.168.1.123:8081;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
        }
}

Nginx를 타고 서버까지 갔지만 해당 포트에 서버가 열려있지 않은 경우 다음 에러가 발생합니다.

image image
RIANAEH commented 2 years ago

EC2 Swap Memory 설정하기

메모리 확인 명령어

top, watch -n 5 free -m

image

1. dd 명령을 사용하여 루트 파일 시스템에 빈 스왑 파일을 생성합니다.

of에는 스왑 파일의 위치와 이름을 지정할 수 있습니다. 저는 / 디렉토리에 swapfile이라는 이름으로 생성하였습니다. bs는 블록의 크기, count는 블록의 개수를 의미하며, 스왑 파일의 총 크기는 bs * count가 됩니다. 저는 총 2.1GB의 스왑 파일을 생성했습니다.

$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
16+0 records in
16+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 14.3367 s, 150 MB/s

2. 스왑 파일의 읽기/쓰기 권한을 업데이트합니다.

$ sudo chmod 600 /swapfile

3. 앞서 만든 빈 파일(/swapfile)을 스왑파일로 설정합니다.

$ sudo mkswap /swapfile
Setting up swapspace version 1, size = 2 GiB (2147479552 bytes)
no label, UUID=...

4. 스왑 공간에 스왑 파일을 추가해 스왑 파일을 즉시 활성화합니다.

이제 물리적 메모리 1G + 가상 메모리 2G를 가지게 되었습니다😎

$ sudo swapon /swapfile

5. 스왑이 활성화된 파티션과 사이즈를 확인해봅니다.

$ sudo swapon -s
Filename                Type        Size        Used        Priority
/swapfile                               file        2097148     0       -2

6. /etc/fstab 파일 마지막 줄에 다음 내용을 추가해, 인스턴스가 재부팅되더라도 스왑 파일을 사용하도록 설정해줍니다.

/swapfile swap swap defaults 0 0
image

참고 자료