CI/CD
이전 편에서 이어진다.
고통스러운 하루였다. 사람은 착한데 말은 안 듣는 꾸러기 nginx를 가지고 열심히 하루종일 고군분투 했다. 다 하고보니 하루나 걸릴 일인가 싶긴 한데..... 아무튼 해냈다. 당면한 문제는 언제나 스트레스이지만, 해결할 수 있기에 즐겁다. 오늘도 나의 행복 임계치가 한 꼬집 낮아졌다.
이전 포스트에선 몰랐는데, 이전 포스트에서 참조했던 글이 향로(이동욱 개발자님)님의 내용을 거의 그대로 붙인 것이었다. 그래서 이번엔 거슬러 올라가 원본 게시글을 보며 작업을 했다. 비교적 쉽게 구현할 수 있도록 해준 향로님께 무한한 감사의 말씀을 전한다. 인프런 방향으로 인사 한 번.
보면서 했지만, 그럼에도 오래걸렸다. 왜 오래걸렸냐면, 아무래도 글이 과거에 작성된 글이다 보니 버전이 달랐다. 게다가 첫 번째 글부터 차례대로 밟아온게 아니라 첫 번째 글에서 세팅했던 내용이 나에게 적용되어있지 않아 그 부분도 해결하느라 시간이 소모되었다. 그래서 이번엔 step by step이 아닌 게시글의 부분부분을 짚어가며 내가 겪었던 시행착오를 쓰고자 한다.
why?
먼저, 정리부터 해보겠다. 그래. 나는 왜 nginx를 골랐는가?
이 부분에 대한 교과서적인 답변을 하자면,
1. 간단한 세팅으로 구현할 수 있는 리버스 프록시
2. 가벼움
3. 동시접속 처리에 특화된 시스템은 로드밸런싱 시스템을 구축 시 트래픽이 많이 발생하는 커뮤니티 사이트에 적합
그 외에도 할 말이야 만들면 생기겠지만 결국 결론은 무료이기 때문이다.
내부에서 포트를 나눠 Blue-Green 배포를 시행하면 EC2를 추가로 개설할 필요도 사라지기에 사실상 50%만큼 비용이 절약된다. 살짝 맥이 빠지는 결론이긴 하지만, 비용적인 문제는 엄청나게 중요하다. 저렴한 t2.micro를 포기하기 싫어 Jenkins를 포기한 것처럼.
지금은 몇천 원 수준의 작은 비용 차이일 뿐이지만, 미래에 내가 취업할 회사에선 이 차이가 어마어마한 비용의 차이가 될 것이다. 비용의 지출을 줄이기 위해 주어진 상황 속에서 최적의 프로그램을 구축해야 하는 상황을 맞이하게 될 것을 대비한다고 생각하면 될 것이다.
그러면 이제 본론으로 들어가보겠다. 부분부분을 짚어가며 내가 겪었던 시행착오.. 이하생략.
위의 링크를 같이 보면 도움이 될 것 같다.
7-3-1. Nginx 설치
여기서 겪었던 문제는, nginx 버전이 게시글과 다르다는 점이었다.
sudo vi /etc/nginx/nginx.conf
위 파일의 내용이 나와 향로님이 아예 달랐다. 여기서 시간을 조금 빼앗겼다. 결국 해결했다.
nginx 설치를 마치고 위 파일에 들어가면 이런 내용이 있을 것이다.
...
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
주목해야할 점은 include /etc/nginx/sites-enabled/* 쪽이다. nginx의 버전이 바뀌면서 /etc/nginx/sites-enabled 디렉터리에 설정을 보관하도록 되어있었다.
위의 튜토리얼과는 다르게 다른 파일을 생성하거나 할 필요는 없다. default 파일을 조금 수정해주면 된다.
sudo vi /etc/nginx/sites-enabled/default
...
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url;
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;
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
...
내가 찾던 location / 여기 있네요! 이 곳에 값을 넣어주면 된다. 문제 해결!
7-3-2. set1, set2 Profile 설정
여기서 겪었던 문제는, /profile 실행 시 프로필값을 제대로 받아오지 못 하는 부분이었다.
다른 프로필이 함께 존재했기 때문에 findFirst에서 set1 또는 set2가 아닌 다른 프로필이 캐치, 문제가 발생했다.
이 부분은 set 값이 존재하는 곳의 index를 잡아주는 코드로 변경해서 해결했다.
@RestController
@RequiredArgsConstructor
public class WebRestController {
private final Environment env;
@GetMapping("/profile")
public String getProfile() {
String[] str = env.getActiveProfiles();
int idx = 0;
for(int i = 0; i < str.length; i++){
if(str[i].contains("set")){
idx = i;
break;
}
}
return str[idx];
}
}
추가로, 윈도우 환경이라 우분투와 같은 경로에 real-application.yml 파일을 위치시키지 못 하고, 이에 따라 로컬에서 빌드가 불가능한 문제였다. 이 부분은 Application을 수정 없이 실행시키고, real-application.yml은 프로젝트 내부에 위치, .gitignore에 추가 시켜 로컬에서 빌드 가능한 환경으로 만들고, EC2에서는 외부에서 설정 파일을 참조해 빌드하도록 만들었다.
@EnableCaching
@EnableScheduling
@EnableJpaAuditing
@SpringBootApplication
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
여긴 수정 없이 놔둔거다. 뭐가 바뀌었는지는 바로 다음에 나온다.
7-3-3. 배포 스크립트 작성
여기서 겪었던 문제는, 파일명.jar 과 파일명-plain.jar 파일이 같이 생성되어서 파일을 파일명-plain.jar 로 실행하려고 했던 문제였다.
해결 방법은 두 가지다.
1. Gradle에서 plain.jar 생성 방지
/*
build.gradle
*/
jar {
enabled = false
}
2. 파일명 정보를 더욱 명확하게 명시
sudo vi /home/ec2-user/app/nonstop/deploy.sh
BUILD_PATH=$(ls $BASE_PATH/springboot-webservice/build/libs/*SNAPSHOT.jar)
다음으로 겪었던 문제는 7-3-2.의 문제와 연결된다.
런타임에 application 설정을 해주지 않았다보니 application 정보가 없어 실행이 불가능한 상태였다. 물론 deploy.sh도 정상 작동하지 않는다. 외부에서 config 파일을 참조할 수 있게 설정해 해결했다.
sudo vi /home/ec2-user/app/nonstop/deploy.sh
echo "> $IDLE_PROFILE 배포"
nohup java -jar -Dspring.profiles.active=$IDLE_PROFILE -Dspring.config.location=file:///home/ec2-user/app/nonstop/application.properties,file:///home/ec2-user/app/nonstop/real-application.yml $IDLE_APPLICATION_PATH &
파일은 로컬에서 EC2로 옮겨와 직접 이동시켜줬다. 더 좋은 방법을 모색해봐야겠다.
다음 문제는 /health 세팅 문제였다.
향로님의 관련 게시글 1번 글에서 세팅되어 있던 부분이 누락되어 /health가 작동하지 않는 문제였다. Gradle에 의존성을 추가해줘 해결했다.
/*
build.gradle
*/
dependencies {
// Health Check
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
이런데도 /health가 작동하지 않았다. 그래서 deploy.sh 파일에서 주소를 바꿔줬다.
sudo vi /home/ec2-user/app/nonstop/deploy.sh
response=$(curl -s http://localhost:$IDLE_PORT/actuator/health)
$IDLE_PORT/health -> $IDLE_PORT/actuator/health
수정하지 않아도 잘 작동하게 할 방법이 있는 것 같은데 나는 그냥 이렇게 해결했다.
추가로 로컬에서 작동시 Redis 관련 에러가 발생하기도 했고, 실제 기능을 작동시켰을 때 나는 Status : "UP" 하나만 보고싶은데 다른 상세 헬스체크 상태가 나타나기도 했다. 이 부분은 properties를 수정해 해결했다.
# application.properties
# Health check option
management.endpoint.health.show-details=never
management.health.redis.enabled=false
show-details를 always로 바꾸면 모든 상태에 대한 헬스체크가 이뤄진다.
여기까지 문제를 해결하고 나니 배포 과정에서는 문제 없이 잘 수행됐다. 아이좋아!
였는데, 권한 문제로 심볼릭 링크를 생성해주지 못 하는 문제가 발생했고, 이에 따라 빌드를 제대로 수행하지 못 하는 문제가 있었다.
# appspec.yml
hooks:
AfterInstall: # 배포가 끝나면 아래 명령어를 실행
- location: execute-deploy.sh
runas: root
AfterInstall에 runas: root을 부여하여 해결하였다.
추가로, switch.sh 파일이 프록시 포트가 바뀌었는지 체크가 되지 않는 상황이라 확인을 위해 문장을 조금 추가해줬다.
sudo vi /home/ec2-user/app/nonstop/switch.sh
PROXY_PORT=$(curl -s http://localhost/profile)
echo "> Nginx Current Proxy Port: $PROXY_PORT"
echo "> Nginx Reload"
sudo service nginx reload
sudo sleep 3
PROXY_PORT=$(curl -s http://localhost/profile)
echo "> Proxy Port After Nginx Reload: $PROXY_PORT"
포트는 바꿔주더라도 service nginx reload가 이뤄지기 전까진 실제로 반영되지 않는다. 그렇기 때문에 반영한 뒤 한 번 더 체크할 수 있도록 구문을 넣어주었다.
마지막으로, kill -15 명령어가 sleep 시간 내로 수행 완료되지 않아 포트가 겹쳐 충돌이 발생, 프로그램을 실행하지 못 하는 문제가 있었다.
Q) kill -15랑 kill -9는 무슨 차이가 있어요?
A) kill -15는 지정된 작업을 전부 완료하고 천천히 수행하는데, kill -9는 그 자리에서 강제종료 시켜버립니다. 어차피 유휴 상태의 jar를 종료하는 것이기 때문에 그냥 즉시 강제종료 해버려도 상관이 없을거라고 판단했습니다.
sudo vi /home/ec2-user/app/nonstop/deploy.sh
if [ -z $IDLE_PID ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
sleep 5
else
echo "> kill -9 $IDLE_PID"
kill -9 $IDLE_PID
sleep 10
fi
kill -15 -> kill -9로 바꿔줌으로써 해결했다. 느려터진 친구에겐 강제종료를 드렸다.
'기술 > Spring-Boot' 카테고리의 다른 글
[Redis] 초보자도 할 수 있다 DTO 매핑 (0) | 2022.01.16 |
---|---|
[SpringBoot] FetchJoin 없이 N+1 문제 제거하기 (0) | 2022.01.10 |
[Ubuntu/Travis-CI/CodeDeploy] SpringBoot 환경 배포 자동화 환경 구축 (0) | 2022.01.04 |
[SpringBoot/OAuth2] WebSecurity 없이 REST API 환경에서 OAuth2 인증 구현하기 - 2. 네이버 로그인 (0) | 2022.01.02 |
[SpringBoot/OAuth2] WebSecurity 없이 REST API 환경에서 OAuth2 인증 구현하기 - 1. 카카오 로그인 (0) | 2022.01.02 |
댓글