Elastic Beanstalk - (1) Java Jar 파일을 통한 Web Application Deploy
Spring Boot로 개발한 Application을 AWS의 Elastic Beanstalk으로 배포하는 내용을 아래의 3개의 포스팅에 걸쳐 정리해 보았습니다.
-
Jar 파일을 이용한 배포 및 내부 구조 이해
Sample Application
여기서 Sample로 사용한 Application은 제가 Demo용으로 사용하는 게시판 Web Application이고, 개발 환경 및 주요 code들은 아래와 같습니다.
-
Spring Boot 2.2.6
-
Open JDK 1.8 (1.8.0_222)
-
Gradle 5.6.2
-
주요 Dependency : Swagger / H2 Database (File Write Mode)
build.gradle
Spring Boot Project 생성시 Spring Web, JPA dependency, H2 databse를 Dependency 항목으로 추가하고 Swaager UI를 위한 spring-fox와 Json parameter 처리를 위한 Jackson library도 추가했습니다.
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.browndwarf'
version = '0.1-SNAPSHOT'
sourceCompatibility = '1.8'
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.springfox:springfox-swagger2:2.9.2'
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
implementation 'com.fasterxml.jackson.core:jackson-core:2.10.3'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
application.yml
application.yml에서는 h2 database 설정, jpa 설정, server port에 관련된 설정을 아래와 같이 했습니다.
# Database
spring:
profiles: default
datasource:
hikari:
jdbc-url: jdbc:h2:~/browndwarfboard
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
database: H2
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: create-drop
h2:
console:
enabled: true
#Connection
server:
port: 5000
설정 사항 중 2가지만 살펴 보자면,
-
spring.datasource.hikari.jdbc-url : jdbc:h2:~/browndwarfboard ; DB data를 Memory 보관 대신 File에 보관하는 것으로 설정.(File write 위치 확인 用)
-
server.port : 5000 ; port를 5000으로 설정. 이는 Elastic Beanstalk를 통해 Java Application을 Web Application으로 배포하는 과정 中 NGINX가 Resverse Proxy 역할로 자동으로 실행되는데, <pic 1>과 같이 NGINX는 80 port로 받은 외부의 Request들을 Java Application의 5000 port로 Forwarding 해주기 때문에 이를 수신하기 위해 5000 port를 통해 사용하는 것으로 설정해야 한다. (참고)
Controller
게시판 기능을 위해 제공하는 Rest API들은 아래의 Code로 구성되었다.
...
@RestController
@RequestMapping(value="/board")
public class BoardController {
...
@ApiOperation(tags={"Board"}, value="Register new post")
@PostMapping(produces="text/plain")
@CrossOrigin
public ResponseEntity<String> registerPost(@RequestParam String writer,
@RequestBody PostReqParam reqParam) {
...
}
@ApiOperation(tags={"Board"}, value="Update the exist post")
@PutMapping(path="/{id}", produces="text/plain")
@CrossOrigin
public ResponseEntity<String> updatePost(@PathVariable("id") String id,
@RequestParam String writer,
@RequestBody PostReqParam reqParam) {
...
}
@ApiOperation(tags={"Board"}, value="Delete a post")
@DeleteMapping(path="/{id}", produces="text/plain")
public ResponseEntity<String> deletePost(@PathVariable("id") String id,
...
}
@ApiOperation(tags={"Board"}, value="Retrieve a post")
@GetMapping(path="/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PostResParam> retriveSinglePost(@PathVariable("id") String id) {
...
}
@ApiOperation(tags={"Board"}, value="Retrieve a post")
@GetMapping(path="/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<PostResParam>> retriveAllPost() {
...
}
}
배포
Step 1. Application Compile.
제일 먼저 아래와 같이 gradle build를 통해 jar 파일을 생성했다.
[browndwarf@localhost boardservice]$ ./gradlew clean build
> Task :test
..
BUILD SUCCESSFUL in 8s
6 actionable tasks: 5 executed, 1 up-to-date
...
[browndwarf@localhost boardservice]$ ls target/
boardservice-0.1-SNAPSHOT.jar
Step 2. Elastic Beanstalk 진입
AWS에 Login한 후 Elastic Beanstalk로 가면 화면 우측에 <pic 2>와 같이 'Create Application'이라는 버튼이 있고, 이 버튼을 Click하면 Web Application 생성하는 화면으로 진입하게 된다. (예전 학습 영상들을 보면 '환경' menu로 진입해서 웹서버 환경 외에 다른 구성도 할 수 있었던 것으로 보이는데 2020년 6월 현재 기준으로는 Web Application 생성으로만 진입이 가능한 것으로 보인다.)
Step 3. 환경 설정
배포할 Web Application의 실행 환경을 설정하는 단계이다. 아래와 같이 설정하였다. (<pic 3>, <pic 4>, <pic 5>참조)
-
애플리케이션 이름 : BoardService
-
(Optional) Tag
-
STAGE : TEST
-
-
플랫폼
-
플랫폼 : Java
-
플랫폼 브랜치 : Java 8 running on 64 bit Amazon Linux
-
플랫폼 버전 : 2.10.8
-
-
소스코드 오리진
-
로컬 파일 업로드 : jar 파일
-
그리고 '애플리케이션 생성' 버튼을 클릭('추가 옵션 구성'은 제외)하면 아래와 같이 애플리케이션 구성 Log가 보여진 후 <pic 6>와 같이 애플리케이션 생성 결과를 보여준다.
12: 30오후 Environment health has transitioned from Pending to Ok. Initialization completed 26 seconds ago and took 2 minutes.
12: 30오후 Successfully launched environment: Boardservice-test
12: 30오후 Application available at Boardservice-test.eba-jrrhuepk.ap-northeast-2.elasticbeanstalk.com.
12: 29오후 Added instance [i-0ec3c4a737bfc117a] to your environment.
12: 29오후 Waiting for EC2 instances to launch. This may take a few minutes.
12: 28오후 Created EIP: 15.165.27.63
12: 28오후 Environment health has transitioned to Pending. Initialization in progress (running for 15 seconds). There are no instances.
12: 28오후 Created security group named:awseb-e-fypqpfpqej-stack-AWSEBSecurityGroup-90IJY509CSJ5
12: 28오후 Using elasticbeanstalk-ap-northeast-2-812577045739 as Amazon S3 storage bucket for environment data.
12: 28오후 createEnvironment is starting.
서비스 확인
사용했던 예제에 Root Page가 설정되지 않아 <pic 6>에서 나온 Endpoint URL로 접속했을 때는 <pic 7>과 같이 404 오류가 발생하게 된다.
Root page대신 swagger-ui.html로 접속하면<pic 7>와 같이 Elastic Beanstalk에 의해 만들어진 Web Application이 배포되었음을 확인할 수 있다.
그리고 아래와 같이 curl command을 통해 동작이 정상적으로 이루어지고 있음도 확인할 수 있다.
# 게시물 등록
[browndwarf@browndwarf02 ~]$ curl -X POST 'http://boardservice-env.eba-m2qhkaw4.ap-northeast-2.elasticbeanstalk.com/board?writer=browndwarf' \
> -H 'accept: text/plain' \
> -H 'Content-Type: application/json' \
> -d '{ "contents": "Hello. World!", "title":"WELCOME"}'
SUCCESS TO REGISER NEW POST
# 등록된 게시물 확인
[browndwarf@browndwarf02 ~]$ curl -X GET -H 'accept: application/json' 'http://boardservice-env.eba-m2qhkaw4.ap-northeast-2.elasticbeanstalk.com/board/'
[{"id":1,"title":"WELCOME","writer":"browndwarf","contents":"Hello. World!","updateTime":"2020-06-26 06:46:15","createdTime":"2020-06-26 06:46:15"}]
배포 구성 Deep Dive
위에서 Web Application이 정상적으로 동작하는 것은 확인하였다. 그렇다면 Elastic Beanstalk는 어떤 식으로 환경을 자동으로 구성해 주는 지 확인해 보자. Elastic Beanstalk를 통해 Application배포가 이루어지면, Application을 실행할 VM으로 EC2 Instance를 생성하게 된다. EC2 Dashboard에서 생성된 EC2 Instance를 <pic 8>과 같이 확인할 수 있다.
하지만 해당 EC2 Instance로 기존에 만들어진 키페어를 사용해 ssh 접속을 시도하면 아래와 같이 Permission Denied가 발생하면서 접속할 수 없다.
[browndwarf@browndwarf01 Downloads]$ ssh -i ./aws-browndwarf-keypair.pem ec2-user@ec2-52-78-41-104.ap-northeast-2.compute.amazonaws.com
The authenticity of host 'ec2-52-78-41-104.ap-northeast-2.compute.amazonaws.com (52.78.41.104)' can't be established.
ECDSA key fingerprint is SHA256:q0EpVIG/WylmOnG1hAmaBLaeEA41e7KqXqO4wkOM3fE.
ECDSA key fingerprint is MD5:39:33:ef:c3:98:0a:83:4c:ca:d1:18:6f:fd:37:14:46.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-52-78-41-104.ap-northeast-2.compute.amazonaws.com,52.78.41.104' (ECDSA) to the list of known hosts.
Permission denied (publickey).
이는 기존에 만들어진 키페어가 Elastic Beanstalk에 의해 만들어진 EC2 Instance에 적용되지 않아서 생기는 문제이다. 이를 해결하기 위해 EC2 > 키페어로 진입해서 신규 키페어를 생성해 적용해 보았다.(<pic 9 참조) 물론 기존에 존재하는 키페어를 사용해도 된다.
<pic 9>에서 '키페어 생성' 버튼을 클릭한 후, 신규 키페어로 'browndwarf-eb-key'를 생성했다. 키페어가 정상적으로 생성되면 <pic 10>과 같이 새로 추가된 키페어를 확인할 수 있다.
다시 Elastic Beanstalk 메뉴로 돌아와 <pic 11>와 같이 Elastic Beanstalk > 환경 > {생성된 환경} > 구성으로 진입하면 오른쪽에 표기된 환경 구성 요소 중에 '보안' 항목을 확인할 수 있다. '편집' 버튼을 클릭해서 진입해보자.
보안 구성 항목들 중에 <pic 12>과 같이 가상 머신 권한을 설정하는 항목을 확인할 수 있을 것이다. 여기에 위에서 생성한 키페어를 선택한다.
<pic 12>에서 '적용' 버튼을 클릭하면 <pic 13>과 같이 EC2 Instance가 교체된다는 경고 메시지가 나오는데 여기서는 별 상관없기 때문에 '확인' 버튼을 클릭해서 넘어갔다. 기존 EC2 Instance가 삭제되고 새로운 EC2 Instance가 만들어 지기까지 수 분이 소요된 후 <pic 4>와 같이 정상적으로 만들어졌다는 결과가 나왔다.
위에서 생성한 'browndwarf-eb-key' 키 페어를 다운 받은 후, 이 키 페어를 이용해서 ssh 연결을 시도하면 아래와 같이 Elastic Beanstalk에 의해 만들어진 EC2 Instance에 접속된 것을 확인할 수 있다. (Remark. 키 파일의 permission 수정 필요 )
...
[browndwarf@browndwarf01 Downloads]$ sudo chmod 400 browndwarf-eb-key.pem
...
[browndwarf@browndwarf01 Downloads]$ ssh -i ./browndwarf-eb-key.pem ec2-user@ec2-52-78-41-104.ap-northeast-2.compute.amazonaws.com
Last login: Fri Jun 5 04:00:56 2020 from 121.136.189.225
_____ _ _ _ ____ _ _ _
| ____| | __ _ ___| |_(_) ___| __ ) ___ __ _ _ __ ___| |_ __ _| | | __
| _| | |/ _` / __| __| |/ __| _ \ / _ \/ _` | '_ \/ __| __/ _` | | |/ /
| |___| | (_| \__ \ |_| | (__| |_) | __/ (_| | | | \__ \ || (_| | | <
|_____|_|\__,_|___/\__|_|\___|____/ \___|\__,_|_| |_|___/\__\__,_|_|_|\_\
Amazon Linux AMI
This EC2 instance is managed by AWS Elastic Beanstalk. Changes made via SSH
WILL BE LOST if the instance is replaced by auto-scaling. For more information
on customizing your Elastic Beanstalk environment, see our documentation here:
http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html
[ec2-user@ip-172-31-11-28 ~]$
아래와 같이 'top' command를 통해 Process를 확인하고, 그 중에서 'java' command로 실행할 항목을 검색해 보았다. Upload한 jar 파일이 'application.jar'라는 이름으로 변경되어 있고, 이를 'java -jar' command로 실행한 것으로 추정할 수 있다.
[ec2-user@ip-172-31-11-28 /]$ top
top - 04:31:09 up 32 min, 1 user, load average: 0.00, 0.02, 0.00
Tasks: 83 total, 1 running, 58 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.7%us, 0.0%sy, 0.0%ni, 99.0%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1009148k total, 757412k used, 251736k free, 44824k buffers
Swap: 0k total, 0k used, 0k free, 338712k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3142 webapp 20 0 2185m 194m 15m S 0.7 19.7 0:17.88 java
...
[ec2-user@ip-172-31-11-28 /]$ ps 3142
PID TTY STAT TIME COMMAND
3142 ? Sl 0:17 java -jar application.jar
위에서 command를 실행한 user가 'webapp' 이라, 'webapp' user의 home directory를 검사해보니 H2 DB의 파일이 여기에 생성된 것으로 확인됐다. 즉 Application의 Root Path는 '/home/webapp'으로 설정되어 있다.
# 'webapp' User의 Home Directory 조회
[ec2-user@ip-172-31-11-28 /]$ sudo ls -la home/webapp/
total 44
drwx------ 2 webapp webapp 4096 6월 5 03:58 .
drwxr-xr-x 5 root root 4096 6월 5 03:58 ..
-rw-r--r-- 1 webapp webapp 18 8월 30 2017 .bash_logout
-rw-r--r-- 1 webapp webapp 193 8월 30 2017 .bash_profile
-rw-r--r-- 1 webapp webapp 124 8월 30 2017 .bashrc
-rw-r--r-- 1 webapp webapp 24576 6월 5 04:28 browndwarfboard.mv.db
upload한 jar 파일의 위치를 확인해 보니 아래와 같이 '/var/app/Current' Path에 위치해 있다.
[ec2-user@ip-172-31-11-28 /]$ sudo find -name 'application.jar'
./var/app/current/application.jar