원문 - Deploying Spring Boot Applications
- 14 스프링 부트 애플리케이션 배포(Deploying Spring Boot Applications)
- 14.1. 클라우드에 배포(Deploying to the Cloud)
- 14.1.1. 클라우드 파운드리(Cloud Foundry)
- 서비스에 바인딩(Binding to Services)
- 14.1.2. 쿠버네티스(Kubernetes)
- 쿠버네티스 컨테이너 생명주기(Kubernetes Container Lifecycle)
- 14.1.3. 헤로쿠(Heroku)
- 14.1.4. 오픈시프트(OpenShift)
- 14.1.5. 아마존 웹 서비스(Amazon Web Services (AWS))
- AWS 엘라스틱 빈스토크(AWS Elastic Beanstalk)
- 요약(Summary)
- 14.1.6. 클라우드캡틴과 아마존 웹 서비스(CloudCaptain and Amazon Web Services)
- 14.1.7. 애저(Azure)
- 14.1.8. 구글 클라우드(Google Cloud)
- 14.1.1. 클라우드 파운드리(Cloud Foundry)
- 14.2. 스프링 부트 애플리케이션 설치(Installing Spring Boot Applications)
- 14.2.1. 지원되는 운영 체제(Supported Operating Systems)
- 14.2.2. 유닉스/리눅스 서비스(Unix/Linux Services)
- init.d 서비스로 설치(System V)(Installation as an init.d Service (System V))
- systemd 서비스로 설치(Installation as a systemd Service)
- 스타트업 스크립트 커스텀(Customizing the Startup Script)
- 14.2.3. 마이크로서비스 윈도우 서비스(Microsoft Windows Services)
- 14.3. 효율적인 배포(Efficient deployments)
- 14.3.1. 실행 가능한 JAR 압축 풀기(Unpacking the Executable JAR)
- 14.3.2. JVM에서 사전 처리 사용(Using Ahead-of-time Processing With the JVM)
- 14.4. 다음에 읽을 내용(What to Read Next)
- 14.1. 클라우드에 배포(Deploying to the Cloud)
14. 스프링 부트 애플리케이션 배포(Deploying Spring Boot Applications)
스프링 부트의 유연한 패키징 옵션은 애플리케이션 배포 시 다양한 선택사항을 제공한다. 스프링 부트 애플리케이션을 다양한 클라우드 플랫폼, 가상/실제 머신에 배포하거나 유닉스 시스템에서 완전히 실행 가능하도록 만들 수 있다.
이 섹션에서는 보다 일반적인 배포 시나리오 중 일부를 다룬다.
14.1. 클라우드에 배포(Deploying to the Cloud)
스프링 부트의 실행 가능한(executable) jar는 가장 널리 사용되는 클라우드 PaaS(Platform-as-a-Service) 제공업체를 위해 미리 만들어졌다. 이러한 공급자(provider)는 “컨테이너를를 가져오라”고 요구하는 경향이 있다. 애플리케이션 프로세스(특히 자바 애플리케이션 아닌)를 관리하므로 애플리케이션을 실행 중인 프로세스에 대한 클라우드의 개념에 맞게 조정하는 중간 계층이 필요하다.
인기 있는 두 클라우드 제공업체인 헤로쿠(Heroku)와 클라우드 파운드리(Cloud Foundry)는 “빌드팩” 접근 방식을 사용한다. 빌드팩은 애플리케이션을 시작하는 데 필요한 모든 항목에 배포된 코드를 래핑한다. 이는 JDK 및 자바 호출, 임베디드 웹 서버 또는 완전한 애플리케이션 서버일 수 있다. 빌드팩은 플러그인화가 가능하지만, 이상적으로 가능한 한 적은 수의 커스텀만으로 사용할 수 있어야 한다. 이렇게 하면 사용자가 제어할 수 없는 기능의 공간이 줄어듭니다. 개발 환경과 프로덕션 환경 간의 차이를 최소화한다.
이상적으로는, 스프링 부트의 실행 가능 jar과 같은 애플리케이션에 실행하는 데 필요한 모든 것이 패키지로 포함되어 있다.
이 장에서는 “시작하기” 장에서 개발한 애플리케이션을 클라우드에서 실행하는 데 필요한 사항을 살펴보자.
14.1.1. 클라우드 파운드리(Cloud Foundry)
클라우드 파운드리(Cloud Foundry)는 다른 빌드팩이 지정되지 않은 경우 작동하는 기본 빌드팩을 제공한다. 클라우드 파운드리 자바 빌드팩은 스프링 부트를 포함하여 스프링 애플리케이션을 훌륭하게 지원한다. 독립형 실행 가능 jar 애플리케이션은 물론 기존 .war 패키지 애플리케이션도 배포할 수 있다.
애플리케이션을 빌드하고(예: mvn clean 패키지 사용) cf 명령줄 도구를 설치한 후 cf push
명령을 사용하여 애플리케이션을 배포하고 경로를 컴파일된 .jar
로 대체한다. 애플리케이션을 푸시하기 전에 cf
명령줄 클라이언트로 로그인했는지 확인하자. 다음 줄은 cf push
명령을 사용하여 애플리케이션을 배포하는 방법을 보여준다.
$ cf push acloudyspringtime -p target/demo-0.0.1-SNAPSHOT.jar
앞의 예제에서, 애플리케이션명으로 cf
에 지정한 값을 acloudyspringtime
으로 대체한다.
더 많은 옵션을 보려면 cf push 문서를 참고하자. 동일한 디렉토리에 클라우드 파운드리 manifest.yml 파일이 있으면 이를 고려하자.
이 시점에서, cf는 애플리케이션 업로드를 시작하고 다음 예와 유사한 출력을 생성한다.
Uploading acloudyspringtime... OK
Preparing to start acloudyspringtime... OK
-----> Downloaded app package (8.9M)
-----> Java Buildpack Version: v3.12 (offline) | https://github.com/cloudfoundry/java- buildpack.git#6f25b7e
-----> Downloading Open Jdk JRE
Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.6s)
-----> Downloading Open JDK Like Memory Calculator 2.0.2_RELEASE from https://java-
buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-
2.0.2_RELEASE.tar.gz (found in cache)
Memory Settings: -Xss349K -Xmx681574K -XX:MaxMetaspaceSize=104857K -Xms681574K
-XX:MetaspaceSize=104857K
-----> Downloading Container Certificate Trust Store 1.0.0_RELEASE from https://java-
buildpack.cloudfoundry.org/container-certificate-trust-store/container-certificate-
trust-store-1.0.0_RELEASE.jar (found in cache)
Adding certificates to .java-
buildpack/container_certificate_trust_store/truststore.jks (0.6s)
-----> Downloading Spring Auto Reconfiguration 1.10.0_RELEASE from https://java-
buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-
1.10.0_RELEASE.jar (found in cache)
Checking status of app 'acloudyspringtime'...
0 of 1 instances running (1 starting)
...
0 of 1 instances running (1 starting)
...
0 of 1 instances running (1 starting)
...
1 of 1 instances running (1 running)
App started
축하한다! 이제 애플리케이션이 활성화됐다!
애플리케이션이 활성화되면 다음 예와 같이 cf apps
명령을 사용하여 배포된 애플리케이션의 상태를 확인할 수 있다.
$ cf apps
Getting applications in ...
OK
name requested state instances memory disk urls
...
acloudyspringtime started 1/1 512M 1G
acloudyspringtime.cfapps.io
...
클라우드 파운드리(Cloud Foundry)가 애플리케이션이 배포되었음을 확인하면 지정된 URI에서 애플리케이션을 찾을 수 있다. 앞의 예제에서 https://acloudyspringtime.cfapps.io/
에서 찾을 수 있다.
서비스에 바인딩(Binding to Services)
기본적으로 실행 중인 애플리케이션에 대한 메타데이터와 서비스 연결 정보는 애플리케이션에 환경 변수(예: $VCAP_SERVICES)로 노출된다. 이 아키텍처 결정은 클라우드 파운드리(Cloud Foundry)의 다중 언어(모든 언어 및 플랫폼이 빌드팩으로 지원될 수 있음) 특성 때문이다. 프로세스 범위 환경 변수는 언어에 구애받지 않는다.
환경 변수가 항상 가장 쉬운 API를 제공하는 것은 아니므로 스프링 부트는 다음 예제와 같이 자동으로 이를 추출하고 스프링의 환경 추상화를 통해 접근할 수 있는 프로퍼티스로 데이터를 평면화한다.
자바
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class MyBean implements EnvironmentAware {
private String instanceId;
@Override
public void setEnvironment(Environment environment) {
this.instanceId = environment.getProperty("vcap.application.instance_id");
}
// ...
}
코틀린
import org.springframework.context.EnvironmentAware
import org.springframework.core.env.Environment
import org.springframework.stereotype.Component
@Component
class MyBean : EnvironmentAware {
private var instanceId: String? = null
override fun setEnvironment(environment: Environment) {
instanceId = environment.getProperty("vcap.application.instance_id")
}
// ...
}
모든 클라우드 파운드리(Cloud Foundry) 프로퍼티에는 vcap
접두사가 붙는다. vcap
프로퍼티들을 사용하여 애플리케이션 정보(예: 애플리케이션의 공개 URL) 및 서비스 정보(예: 데이터베이스 자격 증명)에 접근할 수 있다. 자세한 내용은 CloudFoundryVcapEnvironmentPostProcessor
자바독(Javadoc)을 참고하자.
자바 CFEnv
프로젝트는 DataSource
구성과 같은 작업에 더 적합하다.
14.1.2. 쿠버네티스
스프링 부트는 “*_SERVICE_HOST
” 및 “*_SERVICE_PORT
” 변수에 대한 환경을 확인하여 쿠버네티스 배포 환경을 자동 감지한다. spring.main.cloud-platform
구성 프로퍼티를 사용하여 이 감지를 오바리이드할 수 있다.
스프링 부트를 사용하면 애플리케이션 상태를 관리하고 액추에이터를 사용하여 HTTP 쿠버네티스 프로브로 내보낼 수 있다.
쿠버네티스 컨테이너 생명주기(Kubernetes Container Lifecycle)
쿠버네티스가 애플리케이션 인스턴스를 삭제할 때, 종료 프로세스에는 종료 후크, 서비스 등록 취소, 로드 밸런서에서 인스턴스 제거 등 여러 하위 시스템이 동시에 포함된다. 왜냐하면 분산 시스템의 특성으로 인해 이 종료 처리는 병렬로 발생하기 때문이다. 종료 처리를 시작한 파드로 트래픽을 라우팅할 수 있는 기간이 있다.
이미 종료가 시작된 파드로 요청이 라우팅되는 것을 방지하기 위해 preStop
핸들러에서 절전(sleep)을 구성할 수 있다. 이 절전 모드는 새 요청이 파드로 라우팅되는 것을 중지할 만큼 충분히 길어야 하며 해당 기간은 배포마다 다르다. preStop
핸들러는 다음과 같이 파드 구성 파일의 PodSpec
을 사용하여 구성할 수 있다.
spec:
containers:
- name: "example-container"
image: "example-image"
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
사전 중지(pre-stop) 후크가 완료되면 SIGTERM
이 컨테이너로 전송되고 정상적인 종료가 시작되어 나머지 진행 중인 요청이 완료될 수 있다.
쿠버네티스가 SIGTERM
신호를 파드에 보내면 종료 유예 기간(기본값은 30초)이라는 지정된 시간 동안 기다린다. 유예 기간 후에도 컨테이너가 계속 실행 중인 경우 SIGKILL
신호가 전송되고 강제로 제거된다. spring.lifecycle.timeout-per-shutdown-phase
를 늘렸기 때문에 파드를 종료하는 데 30초 이상 걸리는 경우 파드 YAML에서 shutdownGracePeriodSeconds
옵션을 설정하여 종료 유예 기간을 늘려야 한다.
14.1.3. 헤로쿠(Heroku)
헤로쿠는 또 다른 인기 있는 PaaS 플랫폼이다. 헤로쿠(Heroku) 빌드를 커스텀하려면 애플리케이션을 배포하는 데 필요한 명령을 제공하는 Procfile
을 제공한다. 헤로쿠(Heroku)는 자바 애플리케이션이 사용할 포트를 할당한 다음 외부 URI로의 라우팅이 작동하는지 확인한다.
올바른 포트에서 수신하도록 애플리케이션을 구성해야 한다. 다음 예제는 스타터 REST 애플리케이션에 대한 Procfile
을 보여준다.
web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar
스프링 부트에서는 -D
아규먼트를 스프링 환경 인스턴스에서 접근할 수 있는 프로퍼티로 사용할 수 있다. server.port
프로퍼티는 임베디드 톰캣, 제티 또는 언더토우 인스턴스에 제공되며, 시작 시 해당 포트를 사용한다. $PORT
환경 변수는 헤로쿠 PaaS에 의해 우리에게 할당된다.
이것이 필요한 전부다. 헤로쿠 배포의 가장 일반적인 배포 워크플로는 다음 예제와 같이 코드를 프로덕션에 git push
하는 것이다.
$ git push heroku main
결과는 다음과 같다.
Initializing repository, done.
Counting objects: 95, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (78/78), done.
Writing objects: 100% (95/95), 8.66 MiB | 606.00 KiB/s, done. Total 95 (delta 31), reused 0 (delta 0)
-----> Java app detected
-----> Installing OpenJDK... done
-----> Installing Maven... done
-----> Installing settings.xml... done
-----> Executing: mvn -B -DskipTests=true clean install
[INFO] Scanning for projects...
Downloading: https://repo.spring.io/...
Downloaded: https://repo.spring.io/... (818 B at 1.8 KB/sec)
....
Downloaded: https://s3pository.heroku.com/jvm/... (152 KB at 595.3 KB/sec) [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/target/... [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/pom.xml ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------ [INFO] Total time: 59.358s
[INFO] Finished at: Fri Mar 07 07:28:25 UTC 2014
[INFO] Final Memory: 20M/493M
[INFO] ------------------------------------------------------------------------
-----> Discovering process types Procfile declares types -> web
-----> Compressing... done, 70.4MB -----> Launching... done, v6
https://agile-sierra-1405.herokuapp.com/ deployed to Heroku To git@heroku.com:agile-sierra-1405.git
* [new branch] main -> main
이제 귀하의 애플리케이션이 헤로쿠에서 실행될 것이다. 자세한 내용은 [헤로쿠에 스프링 부트 애플리케이션 배포]를(https://devcenter.heroku.com/articles/deploying-spring-boot-apps-to-heroku) 참고하자.
14.1.4. 오픈시프트(OpenShift)
OpenShift has many resources describing how to deploy Spring Boot applications, including:
- S2I 빌더 사용(Using the S2I builder)
- 아키텍처 가이드(Architecture guide)
- 와일드플라이에서 기존 웹 애플리케이션으로 실행(Running as a traditional web application on Wildfly)
- 오픈시프트 커먼 브리핑(OpenShift Commons Briefing)
14.1.5. 아마존 웹 서비스(Amazon Web Services (AWS))
아마존 웹 서비스는 기존 웹 애플리케이션(war) 또는 임베디드 웹 서버가 있는 실행 가능한 jar 파일 등 스프링 부트 기반 애플리케이션을 설치하는 다양한 방법을 제공한다. 옵션은 다음과 같다.
- AWS 엘라스틱 빈스토크(AWS Elastic Beanstalk)
- AWS 코드 디플로이(AWS Code Deploy)
- AWS OPS 워크(AWS OPS Works)
- AWS 클라우드 포메이션(AWS Cloud Formation)
- AWS 컨테이너 레지스트리(AWS Container Registry)
각각은 서로 다른 기능과 가격 모델을 가지고 있다. 이 문서에서는 AWS 엘라스틱 빈스토크를 사용하여 접근하는 방법을 설명한다.
AWS 엘라스틱 빈스토크(AWS Elastic Beanstalk)
공식 엘라스틱 빈스토크 자바 가이드에 설명된 대로 자바 애플리케이션을 배포하는 데는 두 가지 주요 옵션이 있다. “톰캣 플랫폼” 또는 “자바 SE 플랫폼”을 사용할 수 있다.
톰캣 플랫폼 사용
이 옵션은 war
파일을 생성하는 스프링 부트 프로젝트에 적용된다. 특별한 구성이 필요하지 않다. 공식 가이드를 따르기만 하면 된다.
자바 SE 플랫폼 사용
이 옵션은 jar
파일을 생성하고 내장된 웹 컨테이너를 실행하는 스프링 부트 프로젝트에 적용된다. 엘라스틱 빈스토크 환경은 포트 5000에서 실행되는 실제 애플리케이션을 프록시하기 위해 포트 80에서 엔진엑스(nginx) 인스턴스를 실행한다. 이를 구성하려면 application.properties
파일에 다음 줄을 추가한다.
server.port=5000
팁
소스 대신 바이너리 업로드를 기본으로 사용하며, 엘라스틱 빈스토크에 소스를 업로드하면 AWS에서 컴파일한다. 그러나 바이너리를 업로드하는 것이 가장 좋다. 이렇게 하려면 .elasticbeanstalk/config.yml
파일에 다음내용을 추가하자.
deploy:
artifact: target/demo-0.0.1-SNAPSHOT.jar
환경 타입 설정을 통한 비용 절감
기본적으로 엘라스틱 빈스토크 환경은 로드 밸런싱된다. 로드 밸런서는 상당한 비용이 든다. 이러한 비용을 피하려면 아마존 문서에 설명된 대로 환경 타입을 “단일 인스턴스”로 설정하자. CLI와 다음 명령을 사용하여 단일 인스턴스 환경을 생성할 수도 있다.
eb create -s
요약(Summary)
이는 AWS에 접근하는 가장 쉬운 방법 중 하나이지만 엘라스틱 빈스토크를 CI/CD 도구에 통합하는 방법, CLI 대신 엘라스틱 빈스토크 메이븐 플러그인을 사용하는 방법 등 다루어야 할 사항이 더 많다. 이러한 주제를 더 자세히 다루는 블로그 게시물이 있다.
14.1.6. 클라우드캡틴과 아마존 웹 서비스(CloudCaptain and Amazon Web Services)
클라우드캡틴(CloudCaptain)은 스프링 부트 실행 가능 jar 또는 war를 버추얼박스 또는 AWS에서 변경 없이 배포할 수 있는 최소 VM 이미지로 전환하여 작동한다. 클라우드캡틴(CloudCaptain)은 스프링 부트와 긴밀하게 통합되어 제공되며 스프링 부트 구성 파일의 정보를 사용하여 포트 및 상태 확인 URL을 자동 구성한다. 클라우드캡틴은 생성하는 이미지는 물론 프로비저닝하는 모든 리소스(인스턴스, 보안 그룹, 탄력적 로드 밸런서 등)에 대해 이 정보를 활용한다.
클라우드캡틴 계정을 생성하고, 이를 AWS 계정에 연결하고, 최신 버전의 클라우드캡틴 클라이언트를 설치하고, 애플리케이션이 메이븐 또는 그레이들에서 빌드되었는지 확인했다면(예: mvn clean 패키지 사용) 다음 내용을 수행할 수 있다. 다음과 유사한 명령을 사용하여 스프링 부트 애플리케이션을 AWS에 배포한다.
$ boxfuse run myapp-1.0.jar -env=prod
더 많은 옵션을 보려면 boxfuse run
문서를 참고하자. 현재 디렉토리에 boxfuse.conf
파일이 있으면 이를 고려한다.
기본적으로, 클라우드캡틴(CloudCaptain)은 시작 시 boxfuse
라는 스프링 프로필을 활성화한다. 실행 가능한 jar 또는 war에 application-boxfuse.properties
파일이 포함된 경우 클라우드캡틴은 포함된 프로퍼티를 기반으로 구성한다.
이 시점에서 클라우드캡틴은 애플리케이션용 이미지를 생성하여 업로드하고 AWS에서 필요한 리소스를 구성 및 시작하여 다음 예제와 유사한 출력을 생성한다.
Fusing Image for myapp-1.0.jar ...
Image fused in 00:06.838s (53937 K) -> axelfontaine/myapp:1.0
Creating axelfontaine/myapp ...
Pushing axelfontaine/myapp:1.0 ...
Verifying axelfontaine/myapp:1.0 ...
Creating Elastic IP ...
Mapping myapp-axelfontaine.boxfuse.io to 52.28.233.167 ...
Waiting for AWS to create an AMI for axelfontaine/myapp:1.0 in eu-central-1 (this may
take up to 50 seconds) ...
AMI created in 00:23.557s -> ami-d23f38cf
Creating security group boxfuse-sg_axelfontaine/myapp:1.0 ...
Launching t2.micro instance of axelfontaine/myapp:1.0 (ami-d23f38cf) in eu-central-1
...
Instance launched in 00:30.306s -> i-92ef9f53
Waiting for AWS to boot Instance i-92ef9f53 and Payload to start at
https://52.28.235.61/ ...
Payload started in 00:29.266s -> https://52.28.235.61/
Remapping Elastic IP 52.28.233.167 to i-92ef9f53 ...
Waiting 15s for AWS to complete Elastic IP Zero Downtime transition ...
Deployment completed successfully. axelfontaine/myapp:1.0 is up and running at
https://myapp-axelfontaine.boxfuse.io/
이제 애플리케이션이 AWS에서 실행될 것이다.
앱을 실행하기 위해 메이븐 빌드를 시작하려면 EC2에 스프링 부트 앱 배포에 대한 블로그 게시물과 클라우드캡틴 스프링 부트 인테그레이션에 대한 설명서를 참고하자.
14.1.7. 애저(Azure)
이 시작 가이드에서는 스프링 부트 애플리케이션을 에저 스프링 클라우드 또는 애저 앱 서비스에 배포하는 과정을 안내한다.
14.1.8. 구글 클라우드(Google Cloud)
GCP에는 스프링 부트 애플리케이션을 시작하는 데 사용할 수 있는 여러 옵션이 있다. 시작하기 가장 쉬운 방법은 아마도 앱 엔진(App Engine)일 것이다. 그러나 컨테이너 엔진이 있는 컨테이너나 컴퓨트 엔진이 있는 가상 머신에서 스프링 부트를 실행하는 방법을 찾을 수도 있다.
앱 엔진에서 실행하려면 먼저 UI에서 프로젝트를 생성하면 된다. 그러면 고유 식별자가 설정되고 HTTP 경로도 설정된다. 프로젝트에 자바 앱을 추가하고 비워 둔 다음 구글 클라우드 SDK를 사용하여 커맨드라인 또는 CI 빌드에서 해당 슬롯에 스프링 부트 앱을 푸시한다.
앱 엔진 표준에서는 WAR 패키징을 사용해야 한다. 앱 엔진 표준 애플리케이션을 구글 클라우드에 배포하려면 다음 단계를 따르자.
또는, 앱 엔진 플럭스(App Engine Flex)를 사용하려면 앱에 필요한 리소스를 설명하는 app.yaml
파일을 만들어야 한다. 일반적으로 이 파일은 src/main/appengine
에 저장되며 다음 파일과 유사해야 한다.
service: "default"
runtime: "java"
env: "flex"
runtime_config:
jdk: "openjdk8"
handlers:
- url: "/.*"
script: "this field is required, but ignored"
manual_scaling:
instances: 1
health_check:
enable_health_check: false
env_variables:
ENCRYPT_KEY: "your_encryption_key_here"
다음 예제와 같이 빌드 구성에 프로젝트 ID를 추가하여 앱을 배포할 수 있다(예: 메이븐 플러그인 사용).
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<project>myproject</project>
</configuration>
</plugin>
그런 다음 mvn appengine:deploy
를 사용하여 배포한다(먼저 인증해야 합니다. 그렇지 않으면 빌드가 실패한다).
14.2. 스프링 부트 애플리케이션 설치(Installing Spring Boot Applications)
java -jar
를 사용하여 스프링 부트 애플리케이션을 실행하는 것 외에도 유닉스 시스템에서 실행 가능한 애플리케이션을 만드는 것도 가능하다. 실행 가능한 jar는 실행 가능한 바이너리처럼 실행되거나 init.d
또는 systemd
에 등록될 수 있다. 이는 일반적인 프로덕션 환경에서 스프링 부트 애플리케이션을 설치하고 관리할 때 도움이 된다.
실행 가능한 jar는 파일 앞에 추가 스크립트를 삽입하여 작동한다. 현재 일부 도구는 이 형식을 허용하지 않으므로, 이 기술을 사용하지 못할 수도 있다. 예를 들어,
jar -xf
는 실행 가능한 jar 또는 war를 추출하는 데 실패할 수 있다.java -jar
를 사용하여 실행하거나 서블릿 컨테이너에 배포하는 대신 직접 실행하려는 경우에만 jar 또는 war를 완전히 실행 가능하게 만드는 것이 좋다.
zip64 타입의 jar 파일은 완전히 실행 가능하게 만들 수 없다. 그렇게 하려면 직접 실행하거나
java -jar
를 사용하여 실행할 때 jar 파일이 손상된 것으로 보고한다. 하나 이상의 zip64 형식 중첩 jar을 포함하는 표준 형식 jar 파일은 완전히 실행 가능하다.
메이븐으로 ‘완전히 실행 가능한’ jar을 생성하려면 다음 플러그인 구성을 사용하자.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
다음 예제에서는 동일한 기능의 그레이들 구성을 보여준다.
tasks.named('bootJar') {
launchScript()
}
그런 다음 ./my-application.jar
(여기서 my-application
은 아티팩트의 이름)을 입력하여 애플리케이션을 실행할 수 있다. jar가 포함된 디렉토리는 애플리케이션의 작업 디렉토리로 사용된다.
14.2.1. 지원되는 운영 체제(Supported Operating Systems)
기본 스크립트는 대부분의 리눅스 배포판을 지원하며 센트OS 및 우분투에서 테스트됐다. OS X 및 FreeBSD와 같은 다른 플랫폼에서는 커스텀 EmbeddedLaunchScript
를 사용해야 한다.
14.2.2. 유닉스/리눅스 서비스(Unix/Linux Services)
스프링 부트 애플리케이션은 init.d
또는 systemd
를 사용하여 유닉스/리눅스 서비스로 쉽게 시작할 수 있다.
init.d 서비스로 설치(System V)(Installation as an init.d Service (System V))
완전히 실행 가능한 jar를 생성하도록 스프링 부트의 메이븐 또는 그레이들 플러그인을 구성하고 커스텀 EmbeddedLaunchScript
를 사용하지 않는 경우 애플리케이션을 init.d
서비스로 사용할 수 있다. 이렇게 하려면 jar를 init.d
에 심볼릭 링크하여 표준 시작, 중지, 다시 시작 및 상태 명령을 지원하도록 해야한다.
스크립트는 다음 기능을 지원한다.
- jar 파일을 소유한 사용자로 서비스를 시작한다.
/var/run/<appname>/<appname>.pid
를 사용하여 애플리케이션의 PID를 추적한다./var/log/<appname>.log
에 콘솔 로그를 기록한다.
/var/myapp
에 스프링 부트 애플리케이션이 설치되어 있다고 가정하고, 스프링 부트 애플리케이션을 init.d
서비스로 설치하려면 다음과 같이 심볼릭 링크를 생성하자.
$ sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp
일단 설치되면, 일반적인 방법으로 서비스를 시작하고 중지할 수 있다. 예를 들어 데비안(Debian) 기반 시스템에서는 다음 명령을 사용하여 시작할 수 있다.
$ service myapp start
애플리케이션이 시작되지 않으면 /var/log/<appname>.log
에 기록된 로그 파일에서 오류를 확인하자.
표준 운영 체제 도구를 사용하여 자동으로 시작되도록 애플리케이션에 플래그를 지정할 수도 있다. 예를 들어 데비안에서는 다음 명령을 사용할 수 있다.
$ update-rc.d myapp defaults <priority>
init.d 서비스 보안(Securing an init.d Service)
다음은 init.d
서비스로 실행되는 스프링 부트 애플리케이션을 보호하는 방법에 대한 가이드라인이다. 애플리케이션과 애플리케이션이 실행되는 환경을 강화하기 위해 수행해야 하는 모든 작업을 포괄적으로 나열하려는 의도는 아니다.
루트(root)를 사용하여 init.d
서비스를 시작하는 경우와 마찬가지로 루트로 실행하면 기본 실행 스크립트는 RUN_AS_USER
환경 변수에 지정된 사용자로 애플리케이션을 실행한다. 환경 변수가 설정되지 않은 경우 jar
파일을 소유한 사용자가 대신 사용된다. 스프링 부트 애플리케이션을 루트로 실행해서는 안 된다. 따라서 RUN_AS_USER
는 루트가 되어서는 안 되며 애플리케이션의 jar 파일을 루트가 소유해서는 안 된다. 대신 다음 예제와 같이 특정 사용자를 생성하여 애플리케이션을 실행하고 RUN_AS_USER
환경 변수를 설정하거나 chown
을 사용하여 이를 jar 파일의 소유자로 만든다.
$ chown bootapp:bootapp your-app.jar
이 경우, 기본 실행 가능 스크립트는 bootapp
사용자로 애플리케이션을 실행한다.
애플리케이션의 사용자 계정이 손상될 가능성을 줄이려면 로그인 셸 사용을 방지하는 것을 고려해야 한다. 예를 들어 계정의 셸을 /usr/sbin/nologin
으로 설정할 수 있다.
또한 애플리케이션의 jar 파일 수정을 방지하기 위한 조치를 취해야 한다. 먼저 다음 예제와 같이 소유자가 쓸 수 없고 읽기 또는 실행할 수만 있도록 권한을 구성한다.
$ chmod 500 your-app.jar
둘째, 애플리케이션이나 애플리케이션을 실행하는 계정이 손상된 경우 피해를 제한하기 위한 조치도 취해야 한다. 공격자가 접근 권한을 얻으면 jar 파일을 쓰기 가능하게 만들고 해당 내용을 변경할 수 있다. 이를 방지하는 한 가지 방법은 다음 예제와 같이 chattr
을 사용하여 변경할 수 없도록 만드는 것이다.
$ sudo chattr +i your-app.jar
이렇게 하면 루트를 포함한 모든 사용자가, jar를 수정하는 것을 방지할 수 있다.
루트를 사용하여 애플리케이션 서비스를 제어하고 .conf
파일을 사용하여 시작을 커스텀하는 경우 루트 사용자는 .conf
파일을 읽고 평가한다. 그에 따라 보안이 유지되어야 한다. 다음 예제와 같이 소유자만 파일을 읽을 수 있도록 chmod를 사용하고 chown을 사용하여 루트를 소유자로 만들어야한다.
$ chmod 400 your-app.conf
$ sudo chown root:root your-app.conf
systemd 서비스로 설치(Installation as a systemd Service)
systemd
는 System V init 시스템의 후속 제품이며 현재 많은 최신 리눅스 배포판에서 사용되고 있다. systemd
와 함께 init.d
스크립트를 계속 사용할 수 있지만 systemd ‘service’ 스크립트를 사용하여 스프링 부트 애플리케이션을 시작할 수도 있다.
/var/myapp
에 스프링 부트 애플리케이션이 설치되어 있다고 가정하고 스프링 부트 애플리케이션을 systemd
서비스로 설치하려면 myapp.service
라는 스크립트를 생성하여 /etc/systemd/system
디렉토리에 배치한다. 다음은 예제 스크립트다.
[Unit]
Description=myapp
After=syslog.target
[Service]
User=myapp
ExecStart=/var/myapp/myapp.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
애플리케이션에 대한 설명, 사용자 및 ExecStart 필드를 변경하는 것을 잊지 말자.
ExecStart 필드는 스크립트 작업 명령을 선언하지 않는다. 즉, 기본적 으로 실행 명령이 사용된다.
참고로, init.d 서비스로 실행할 때와 달리 애플리케이션을 실행하는 사용자, PID 파일, 콘솔 로그 파일은 systemd 자체에서 관리되므로 ‘service’ 스크립트에서 적절한 필드를 사용하여 구성해야 한다. 자세한 내용은 서비스 단위 구성 매뉴얼 페이지를 참고하자.
시스템 부팅 시 자동으로 시작되도록 애플리케이션에 플래그를 지정하려면 다음 명령을 사용한다.
$ systemctl enable myapp.service
자세한 내용을 보려면 man systemctl
을 실행하자.
스타트업 스크립트 커스텀(Customizing the Startup Script)
메이븐 또는 그레이들 플러그인으로 작성된 기본 임베디드 스타트업 스크립트는 다양한 방법으로 커스텀할 수 있다. 대부분의 사람들에게는 일반적으로 몇 가지 커스텀과 함께 기본 스크립트를 사용하는 것으로 충분하다. 필요한 것을 커스텀할 수 없는 경우, EmbeddedLaunchScript
옵션을 사용하여 파일 전체를 작성하자.
기록 시 스타트업 스크립트 커스텀
jar 파일에 기록될 때, 시작 스크립트의 엘리먼트를 커스텀하는 것이 의미 있는 경우가 많다. 예를 들어, init.d 스크립트는 “description”을 제공할 수 있다. description을 미리 알고 있으므로(변경할 필요가 없음) jar가 생성될 때 제공할 수도 있다.
기록된 엘리먼트를 커스텀하려면 스프링 부트 메이븐 플러그인의 EmbeddedLaunchScriptProperties
옵션이나 스프링 부트 그레이들 플러그인 launchScript
의 properties
프로퍼티를 사용하자.
기본 스크립트에서는 다음 프로퍼티가 지원된다.
명칭 | 설명 | 그레이들 기본값 | 메이븐 기본값 | |
---|---|---|---|---|
mode | 스크립트 모드. | auto | auto | |
initInfoProvides | “INIT INFO”의 ‘Provides’ 섹션 | ${task.baseName} | ${project.artifactId} | |
initInfoRequired | “INIT INFO”의 Required-Start 섹션을 시작한다. | $remote_fs $syslog $network | $remote_fs $syslog $network | |
initInfoRequiredStop | “INIT INFO”의 Required-Stop 섹션을 시작한다. | $remote_fs $syslog $network | $remote_fs $syslog $network | |
initInfoDefaultStart | “INIT INFO”의 Default-Start 섹션을 시작한다. | 2 3 4 5 | 2 3 4 5 | |
initInfoDefaultStop | “INIT INFO”의 Default-Stop 섹션을 시작한다. | 0 1 6 | 0 1 6 | |
initInfoShortDescription | “INIT INFO”의 Short-Description 섹션을 시작한다. | ${project.description} 의 한 줄 버전(${task.baseName} 으로 대체) | ${project.name} | |
initInfoDescription | “INIT INFO”의 Description 섹션을 시작한다. | ${project.description} (${task.baseName} 으로 대체) | ${project.description} (${project.name} 으로 대체) | |
initInfoChkconfig | “INIT INFO”의 chkconfig 섹션 | 2345 99 01 | 2345 99 01 | |
confFolder | CONF_FOLDER 의 기본값 | jar가 포함된 폴더 | jar가 포함된 폴더 | |
inlinedConfScript | 기본 스타트업 스크립트에 인라인되어야 하는 파일 스크립트에 대한 레퍼런스다. 외부 구성 파일이 로드되기 전에 JAVA_OPTS 와 같은 환경 변수를 설정하는 데 사용할 수 있다. | |||
logFolder | LOG_FOLDER 의 기본값이다. init.d 서비스에만 유효하다. | |||
logFilename | LOG_FILENAME 의 기본값이다. init.d 서비스에만 유효하다. | |||
pidFolder | PID_FOLDER 의 기본값이다. init.d 서비스에만 유효하다. | |||
pidFilename | PID_FOLDER 에 있는 PID 파일 이름의 기본값이다. init.d 서비스에만 유효하다. | |||
useStartStopDaemon | start-stop-daemon 명령이 사용 가능한 경우, 프로세스를 제어하는 데 사용해야 하는지 여부를 나타낸다. | true | true | |
stopWaitTime | STOP_WAIT_TIME 의 기본값(초)다. init.d 서비스에만 유효하다. | 60 | 60 |
실행 시 스크립트 커스텀
jar 작성 후 커스텀해야 하는 스크립트 항목의 경우 환경 변수나 구성 파일을 사용할 수 있다.
기본 스크립트에서는 다음 프로퍼티가 지원된다.
변수 | 설명 | |||
---|---|---|---|---|
MODE | 작동 “모드(mode)”. 기본값은 jar가 빌드된 방식에 따라 다르지만 일반적으로 auto 다(init.d 라는 디렉토리에 있는 심볼릭 링크인지 확인하여 초기화 스크립트인지 추측하려고 시도함을 의미한다). stop | start | status | restart 명령이 동작하도록 service 로 명시적으로 설정하거나 포그라운드에서 스크립트를 실행하려는 경우 run 으로 설정할 수 있다. |
RUN_AS_USER | 애플리케이션을 실행하는 데 사용될 사용자다. 설정하지 않으면 jar 파일을 소유한 사용자가 사용된다. | |||
USE_START_STOP_DAEMON | start-stop-daemon 명령이 사용 가능한 경우 프로세스를 제어하는 데 사용해야 하는지 여부다. 기본값은 true 다. | |||
PID_FOLDER | pid 폴더의 루트 이름(기본적으로 /var/run ) | |||
LOG_FOLDER | 로그 파일을 저장할 폴더명이다(기본적으로 /var/log ). | |||
CONF_FOLDER | .conf 파일을 읽을 폴더명이다(기본적으로 jar 파일과 동일한 폴더). | |||
LOG_FILENAME | LOG_FOLDER 에 있는 로그 파일명이다(기본적으로 <appname>.log ). | |||
APP_NAME | 앱의 이름이다. jar가 심볼릭 링크에서 실행되면 스크립트는 앱명을 추측한다. 심볼릭 링크가 아니거나 앱명을 명시적으로 설정하려는 경우 유용할 수 있다. | |||
RUN_ARGS | 프로그램(스프링 부트 앱)에 전달할 아규먼트다. | |||
JAVA_HOME | java 실행 파일의 위치는 기본적으로 PATH 를 사용하여 검색되지만, $JAVA_HOME/bin/java 에 실행 파일이 있는 경우 명시적으로 설정할 수 있다. | |||
JAVA_OPTS | JVM이 시작될 때 JVM에 전달되는 옵션이다. | |||
JARFILE | 실제로 포함되지 않은 jar를 실행하기 위해 스크립트를 사용하는 경우, jar 파일의 명시적인 위치다. | |||
DEBUG | 비어 있지 않으면, 쉘 프로세스에 -x 플래그를 설정하여 스크립트에서 로직를 볼 수 있도록 한다. | |||
STOP_WAIT_TIME | 강제 종료 전 애플리케이션 중지 시 대기하는 시간(기본값은 60 )이다. |
PID_FOLDER
, LOG_FOLDER
및 LOG_FILENAME
변수는 init.d
서비스에만 유효하다. systemd
의 경우 ‘service’ 스크립트를 사용하여 동등한 커스텀이 이루어진다. 자세한 내용은 서비스 단위 구성 매뉴얼 페이지를 참고하자.
JARFILE
및 APP_NAME
을 제외하고 이전 절에 나열된 설정은 .conf
파일을 사용하여 구성할 수 있다. 파일은 jar 파일 옆에 있을 것으로 예상되며 이름은 동일하지만 .jar 대신 .conf
가 접미사로 붙는다. 예를 들어, /var/myapp/myapp.jar
이라는 jar는 다음 예제와 같이 /var/myapp/myapp.conf
라는 구성 파일을 사용한다.
myapp.conf
JAVA_OPTS=-Xmx1024M
LOG_FOLDER=/custom/log/folder
jar 파일 옆에 구성 파일을 두는 것이 마음에 들지 않으면 CONF_FOLDER
환경 변수를 설정하여 구성 파일의 위치를 커스텀할 수 있다.
이 파일을 적절하게 보호하는 방법을 알아보려면 init.d
서비스 보안 가이드라인을 참고하자.
14.2.3. 마이크로소프트 윈도우 서비스(Microsoft Windows Services)
winsw를 사용하여 스프링 부트 애플리케이션을 Windows 서비스로 시작할 수 있다.
(별도 유지 관리 샘플)에서는 스프링 부트 애플리케이션용 Windows 서비스를 만드는 방법을 단계별로 설명한다.
14.3. 효율적인 배포(Efficient deployments)
14.3.1. 실행 가능한 JAR 압축 풀기(Unpacking the Executable JAR)
컨테이너에서 애플리케이션을 실행하는 경우 실행 가능한 jar을 사용할 수 있지만, 이를 다른 방식으로 압축을 풀고 실행하는 방식에도 장점이 있다. 특정 PaaS 구현체는 실행하기 전에 압축파일의 압축을 풀도록 선택할 수도 있다. 예를 들어 클라우드 파운드리(Cloud Foundry)는 이런 방식으로 작동한다. 압축이 풀린 압축파일를 실행하는 한 가지 방법은 다음과 같이 적절한 애플리케이션을 시작하는 것이다.
$ jar -xf myapp.jar
$ java org.springframework.boot.loader.JarLauncher
이는 실제로 압축을 풀지않은 압축파일에서 실행하는 것보다 시작 시(jar 크기에 따라) 약간 더 빠르다. 시작한 후에는 어떠한 차이도 기대해서는 안 된다.
jar 파일의 압축을 푼 후에는 JarLauncher
대신 “자연스러운” 기본 메서드로 앱을 실행하여 시작 시간을 추가로 늘릴 수도 있다. 다음은 예제이다.
$ jar -xf myapp.jar
$ java -cp "BOOT-INF/classes:BOOT-INF/lib/*" com.example.MyApplication
애플리케이션의 메인 메소드 대신 JarLauncher
를 사용하면 예측 가능한 클래스패스 순서라는 추가 이점이 있다. jar에는 클래스패스를 구성할 때 JarLauncher
에서 사용하는 classpath.idx
파일이 포함되어 있다.
14.3.2. JVM에서 사전 처리 사용(Using Ahead-of-time Processing With the JVM)
AOT
에서 생성된 초기화 코드를 사용하여 애플리케이션을 실행하는 것은 시작 시간이 빠르다. 먼저, 빌드 중인 jar에 AOT 생성 코드가 포함되어 있는지 확인해야 한다.
메이븐의 경우, 이는 기본 프로필을 활성화하려면 -Pnative
를 사용하여 빌드해야 함을 의미한다.
$ mvn -Pnative package
그레이들의 경우 빌드에 org.springframework.boot.aot
플러그인이 포함되어 있는지 확인해야 한다.
JAR이 빌드되면 spring.aot.enabled
시스템 프로퍼티를 true
로 설정하여 실행하자. 다음 예제를 확인하자.
$ java -Dspring.aot.enabled=true -jar myapplication.jar
........ Starting AOT-processed MyApplication ...
사전 처리(ahead-of-time processing)를 사용하면 단점이 있다는 점에 유의하자. 이는 다음과 같은 제한 사항이 있다.
- 클래스패스는 고정되어 있으며 빌드 시 완전히 정의된다.
- 애플리케이션에 정의된 빈은 런타임 시 변경될 수 없다. 즉, 다음과 같다.
- 스프링
@Profile
어노테이션과 프로필별 구성에 제한이 있다. - 빈이 생성되면 변경되는 프로퍼티(예:
@ConditionalOnProperty
및.enable
프로퍼티)는 지원되지 않는다.
- 스프링
사전 처리에 대해 자세히 알아보려면 스프링 사전 처리 이해 장을 참고하자.
14.4. 다음에 읽을 내용(What to Read Next)
PaaS가 제공할 수 있는 기능 종류에 대한 자세한 내용은 클라우드 파운드리(Cloud Foundry), 헤로쿠(Heroku), 오픈시프트(OpenShift) 및 박스퓨즈(Boxfuse) 웹 사이트를 참고하자. 이는 가장 인기 있는 자바 PaaS 제공업체 중 4개에 불과하다. 스프링 부트는 클라우드 기반 배포에 매우 적합하므로 다른 공급자도 자유롭게 고려할 수 있다.
다음 장에서는 계속해서 GraalVM 네이티브 이미지를 다루거나, 먼저 스프링 부트 CLI 또는 빌드 도구 플러그인에 대해 읽어볼 수도 있다.