개요
이번에 Koala 자동화 출석부를 만들게 되면서 서버 단은 어떤 형태로 서비스할지 고민해보게 되었다.
기능이 추가될 수는 있으나, 많은 트래픽이 들어오거나 데이터가 압도적으로 많아지는 서비스가 아니기 때문에 Serverless 형태를 고민하게 되었다. 크롤링 또한 Serverless 작업이지만, 서버 자체도 Serverless로 구현하여, 서버 비용을 최소화하여 구현하기로 하였다 !
JSP + Java/Spring 배포하기
우선 CI/CD를 구현하기 전에 꼭 ! "그냥 배포"를 먼저 시도해보길 바란다. 오히려 시간을 단축시켜줄 것이다.. 오류 뜨면 골치가 너무 아파요ㅠㅠ 그리고 CI/CD는 꼭 코드 구현 전에 미리미리 해두자..
우선 내가 만든 프로젝트 툴은 JSP + Java/Spring 이었다.
프론트는 1학년 때 했던 나의 잼민 수준의 프로젝트 기억을 더듬으며 열심히 작업해야 했기에 빠르고 쉽게 구현하기 위해 다른 툴보다는 Java spring과 MVC 패턴으로 바로 호환되는 JSP를 선택하였다.
하지만,, 청천벽력으로 Java Spring에서는 JSP 프로젝트를 JAR로 배포하지 못하도록 제한을 걸어뒀다는 것을 알게되었다..
Spring Boot의 JAR 패키징은 JSP를 지원하지 않는데, JAR은 Apache Tomcat(서블릿 컨테이너)의 사전 정의된 파일 패턴에 따르지 않기 때문에 작동하지 않는다고 한다. JAR 파일은 내장된 서버를 통해 실행되므로 Tomcat에 배포하는 것을 지원하지 않는다. J로 시작하는 것부터 마음에 안든다..
반면 WAR(web application archive)는 HTML, CSS, JavaScript, JSP, 서블릿 클래스 등을 모두 포함할 수 있어 주로 Apache tomcat ( WAS ) 에 요청을 전달하여 배포하고 실행할 수 있다.
아주 단순히 말하면, JAR은 단독 실행(그러나 웹 템플릿 지원 안됨), WAR은 톰캣 같은 was 서버(이걸로 웹 템플릿 배포 가능)가 필요하다.
따라서 WAR로 배포해서 톰캣 등 WAS 서버를 사용하면 된다.
배포 과정
우선 Test 코드가 있었기에 build.gradle 파일에 아래와 같이 build 시 test 태스크는 무시하도록 설정하였다.
test {
enabled = false
}
이후 war 배포를 위한 plugin(id 'war' 또는 apply plugin:'war') 과 런타임 의존성을 추가해준다.
war 배포 파일명을 커스텀하고 싶다면 아래와 같이 바꾸어주면 된다.
plugins {
id 'war' // 추가
}
java {
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
bootWar{ // war 배포 파일명
archiveBaseName = 'koala'
archiveFileName = 'koala.war'
archiveVersion = "0.0.0"
}
결과적으로 아래와 같이 추가하였다.
Dockerfile 또한 아래와 같이 war 파일로 빌드되도록 변경해준다. 아래 PORT 는 GCR에서 PORT 번호를 외부 변수로 할당해 넣는 것을 권장하여 아래와 같이 작성하였다. (어차피 8082이다.)
FROM openjdk:17-jdk
ARG WAR_FILE=build/libs/*.war
COPY ${WAR_FILE} app.war
ENV PORT=${PORT:-8082}
CMD ["java", "-jar", "/app.war"]
그리고 WAR 파일로 배포할 때에는 아래와 같이 SpringBootServletInitializer 를 상속받아 오버라이딩 해야 한다.
이유에 대해 자세히 알고 싶다면 여기를 참고하자. 간단하게 말하자면, 기존에는 web.xml에 스프링 웹 애플리케이션 컨텍스트(DispatcherServlet)을 등록했었는데 이 xml 파일없이 프로그래밍적으로 Servlet Context를 다룰 수 있도록 인터페이스(WebApplicationInitializer)를 만들었고, SpringBootServletInitializer 는 이 인터페이스의 구현체이다.
그리고 이 어플리케이션 컨텍스트를 구성하면 Tomcat 같은 Servlet Container 환경에서 Springboot 애플리케이션이 동작 가능하다.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder){
return super.configure(builder);
}
}
이후 아래의 과정을 따랐다.
1. war 파일로 빌드한다.(위의 변경사항들을 잘 저장해주자. gradle 코끼리 클릭!)
# War 파일로 빌드한다.
./gradlew bootWar
2. war 파일을 jar 방식으로 배포하여 실행한다.
(본래 이런 배포 방식은 권장하지 않는다. tomcat 에 war 파일을 내장시켜 배포하는 것이 (참고) 좋은 구현 방법이나, 일단 이런 식으로 구현해도 문제는 없었다. 개이득)
# War 파일을 java -jar 명령으로 실행한다.
java -jar build/libs/koala.war
글 업데이트 -- JSP+Java+Springboot 외장 톰캣 배포에 대한 내용은 해당 주소에 포스트하였습니다.!!
3. 그리고 나서 docker 이미지를 빌드해준다.
⚠ 이 때, 빌드 후 만약 "Cloud Run – Failed to start and then listen on the port defined by the PORT" 오류가 난다면, mac M1/M2 사용자의 경우 아래 docker buildx build 명령어를 사용하면 해결된다. (참고)
docker build -t {사용자명}/{이미지 이름}:0.1 .
# M1/M2 MAC은 아래 명령어를 사용하자
docker buildx build --platform linux/amd64 -t {사용자명}/{이미지 이름}:0.1 .
# GCR에 연동하기 위해 도커 이미지에 tag를 붙인다.
docker tag {사용자명}/{이미지 이름}:0.1 [gcr.io/{GCR프로젝트ID}/{이미지 이름}](http://gcr.io/{GCR프로젝트ID}/{이미지 이름})
# GCR 프로젝트에 자동으로 올라가게 된다.
docker push [gcr.io/{GCR프로젝트ID}/{이미지 이름}](http://gcr.io/{GCR프로젝트ID}/{이미지 이름})
GCR
해당 프로젝트에 gcr을 검색하여 Container Registry에 들어가보면 {이미지명}의 폴더가 생겼다.
아래와 같이 이미지명 폴더 아래 내가 만든 도커 이미지가 올라온 것을 볼 수 있다.
Cloud Run에 배포한다.
Cloud Run은 기본적으로 Docker Container 이미지 기반 배포 방식이다. (EC2 배포보다 10배 쉬워요..)
아래와 같이 서비스 이름을 만들고 도쿄로 리전을 할당해준다. (이미 만들고 이 글을 쓰는 거라 이름이 동일한 서비스가 있다.)
이후 인그레스와 인증도 아래와 같이 설정하여 누구나 서비스를 사용할 수 있도록 해주고, 나는 기본 포트를 8082로 해주었기 때문에 포트를 아래와 같이 할당해주었다.
만약 application.yml 파일에 ${DB_USER} 등의 외부 주입 환경변수를 할당해주었다면 왼쪽과 같이 환경변수를 적어준다.
또한 콜드 부팅 시간이 오래 걸리는 것을 방지하기 위해 요청 시간 제한을 1000초로 충분히 늘려주었다.
Cloud Run에 들어가보면 아래와 같이 잘 실행되고 있는 것을 볼 수 있다.
잘 실행되지 않는 사람들은 로그, 버전 등에 오류 메시지나 경고 표시⚠를 잘 보자 !
결과
아래와 같이 JSP 페이지까지 아주 잘 도출되는 것을 볼 수 있다 !
간단한 앱이다보니 서버리스라 하더라도 인스턴스에 올리는 시간이 몇 초 안 걸리는 것 같아 다행이다 -vV
다음에는 Github에 코드가 변경되었을 때 GCR에 CD(지속적 배포)되는 과정을 구현해보자 !
'BackEnd > DEVOPS' 카테고리의 다른 글
[ELK] 텍스트를 이용해 키워드 추출 (0) | 2024.01.09 |
---|---|
[CD|Cloud RUN] Google Cloud Run으로 지속적 배포 구현하기 (2) | 2024.01.03 |
[DEVOPS] Linux 환경에서 ELK metricbeat를 사용한 시스템 모니터링 (0) | 2023.09.02 |
[Kafka] Kafka 이론 및 실습(Window10 Local 환경) (0) | 2023.08.11 |
[DEVOPOS | AWS | SPRING] application.yml 파일 환경변수 외부주입 - aws env 파일/intellij configuration (1) | 2023.06.09 |