본문 바로가기

Public Cloud/AWS

Elastic Beanstalk - (2) War 파일 + Tomcat을 통한 Web Application Deploy

반응형

이전 포스팅에 이어 Spring Boot Application을 War Packge로 배포하는 과정을 정리해 보았다.

  1. Jar 파일을 이용한 배포 및 내부 구조 이해
  2. Tomcat + War 파일을 이용한 배포 및  내부 구조 이해
  3. .extension 폴더 및 각종 환경 설정을 더한 Spring Boot Application 배포 (by Jar 파일)

Application 수정

Spring Boot Application을 War Package로 생성하기 위해, 이전에 작성했던 Code중에서 일부를 아래와 같이 수정하였다.

 

gradle.build 수정

  • 'war' plugin 추가
  • Version 변경 : 0.1-SNAPSHOT ==> 0.2-SNAPSHOT
  • Dependency 추가
    • spring-boot-starter-tomcat : 실행시 Tomcat 적용 (jar build시에는 Dependency 설정없이 자동으로 포함되게 된다.)
    • spring-boot-starter-thymeleaf(Optional) : Root page의 thymeleaf template 사용을 위해 추가
plugins {
...
	id 'war'
}

group = 'com.browndwarf'
version = '0.2-SNAPSHOT'
sourceCompatibility = '1.8'
...

dependencies {
	...
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	
	...	
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	
}
<code 1> 수정된 build.gradle

 

SpringBootServevletInitializer 구현체 추가

SpringBootServeletInitializer를 상속받아 main 함수가 포함된 Class를 source로 지정해야 한다.

...
public class ServletInitializer extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(BoardserviceApplication.class);
	}

}
<code 2> SpringBootServerletInitializer 구현 Code

 

Default Controller & index.html

그리고 root page 활성화를 위해 index.html과 contoller를 추가하였다.

...
@Controller
@RequestMapping("/")
public class DefaultController {

	@GetMapping
	public	String checkHeartBeat(Model model){
		String	hostName;
		String	memorySize;
		
		try {
			hostName = InetAddress.getLocalHost().getHostName();
		}catch(Exception e) {
			hostName = "FAIL TO GET HOSTNAME";
		}
		
		memorySize = String.format("%d mb", (Runtime.getRuntime().totalMemory() / (1024 * 1024)));
		
		model.addAttribute("hostName", hostName);
		model.addAttribute("memory", memorySize);
				
		return "index";
	}
}
...
<code 3> 기본 페이지를 위해 추가한 Controller code

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Hello. Browndwarf World.</h1>
	<p th:text="'Host Name : ' + ${hostName}"/>
	<p th:text="'Memory : ' + ${memory}"/>	
</body>
</html>
<code 4> index.html

Application 생성

Application 생성 과정은 이전 Posting에서와 유사하지만, 환경 설정 시 <pic 1>과 같이 플랫폼을 Tomcat으로 설정 해야한다. Tomcat 플랫폼에는 몇 개의 브랜치가 있는데 이 포스팅에서는 Java 8을 기반으로 하는 브랜치를 선택했다.

 

<pic 1> Tomcat 플랫폼 설정

 

그리고 build 후에 만들어진 version 0.2의 war file을 upload 했다.

 

<pic 2> war file upload

 

정상적으로 launching이 되면 <pic 3>와 같은 결과 화면이 나오지만, 접속을 해도 <pic 4>와 같이 Root Page가 정상적으로 연결되지 않았다.

 

<pic 3> Elastic Beanstalk Application 배포 결과

 

<pic 4> Root Page 접근 시 오류

Debug

Launching한 Application의 상태를 확인하기 위해 Log를 확인하기로 했다. Elastic Beanstalk > 환경 > {생성된 환경} > 로그로 진입하면 <pic 5>와 같은 화면이 나온다. 여기에서 우측 상단의 '로그 요청'버튼을 클릭한다. 마지막 100줄이나 전체 Log를 가져올 수 있는데, 이 예에서는 마지막 100줄만을 가져왔다.

 

<pic 5> Elastic Beanstalk Application의 로그 요청 페이지

 

로그가 정상적으로 가져오게 되면 아래 List에 로그 조회 이력이 추가되고, 제일 왼쪽 칼럼에 '다운로드'를 클릭하면 Log를 Download해서 확인할 수 있다. 아래는 Download 받은 Log에서 catalina log 부분이며, 내용을 확인해보면 Application 초기화 작업 중에 h2 databse Data File을 생성하는 과정에서 Permission denied 오류가 발생해 Application 구동에 실패했다고 유추할 수 있다.

-------------------------------------
/var/log/tomcat8/catalina.2020-06-08.log
-------------------------------------
...
	Caused by: java.io.FileNotFoundException: /usr/share/tomcat8/browndwarfboard.mv.db (Permission denied)
		at java.io.RandomAccessFile.open0(Native Method)
		at java.io.RandomAccessFile.open(RandomAccessFile.java:316)
		at java.io.RandomAccessFile.<init>(RandomAccessFile.java:243)
		at java.io.RandomAccessFile.<init>(RandomAccessFile.java:124)
		at org.h2.store.fs.FileNio.<init>(FilePathNio.java:43)
		at org.h2.store.fs.FilePathNio.open(FilePathNio.java:23)
		at org.h2.mvstore.FileStore.open(FileStore.java:153)
		... 76 more

 

일단 <code 5>와 같이 application.yml에 'alternative' profile을 추가해 H2 Database를 memory DB로 사용하게 설정을 수정했다.

# 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

---

spring:
  profiles: alternative
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem: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
<code 5> 수정된 application.yml 

 

그리고 다시 build한 후 새로 만들어진 war 파일을 적용하기 위해 <pic 6>과 같이 Elastic Beanstalk > {생성한 Application} > 애플리케이션 버젼으로 진입한다. 

<pic 6> Application 버전 관리 화면

 

상단의 '업로드' 버튼을 클릭하면 신규 버전 업로드를 위한<pic 7>과 같은 팝업이 보여진다. 버전 레이블은 각 버전을 구분할 수 있게 명명하면 된다. (이 예제에서는 'boardservice-source-modify'로 수정) 버전 레이블 명명과 war 파일 upload 후에 하단의 '업로드' 버튼을 클릭했다.

 

<pic 7> Application Version Upload 팝업

 

정상적으로 버전 업로드 작업이 끝나면 <pic 8>과 같은 결과를 확인할 수 있다. 아직은 신규 버전만 생성한 상태이며, 여기서 새로 추가한 'boardservice-source-modify' 버전을 선택한 후 '작업' > '배포'를 선택하면 <pic 9>와 같이 신규 버전 배포 여부를 결정하는 팝업이 보여진다.

 

<pic 8> Application Version Upload 결과

 

새로 Upload한 버전을 기존에 만들어진 'Boardservice-env' 환경에 배포하게 설정한 후 '배포' 버튼을 클릭하면 배포가 이루어진다.

 

<pic 9> Application Version 배포 팝업 화면

 

수 분이 지나면 <pic 10>과 같이 'boardservice-source-modify' 버전이 'Boardservice-env' 환경에 배포 되었고, 동시에 기존 버전은 Deprecated 된 것을 알 수 있다. Deprecated 된 버전을 선택한 후 '작업' > '삭제' 을 선택하면 <pic 11>과 같이 삭제하는 화면으로 진입하게 된다.

<pic 10> Application 신규 버전 배포 결과

 

<pic 11> Application Version 삭제 화면

 

배포가 성공적으로 끝났더라도 새로 추가한 Profile을 적용하기 위해 환경 변수를 추가해야 한다. 환경 변수를 추가하기 위해 Elastic Beanstalk > 환경 > {생성된 환경} > 구성을 선택한 후에 오른쪽 패널에서 '소프트웨어' 항목을 편집했다. (<pic 12> 참조)

 

<pic 12> 환경 변수 추가를 위한 진입 화면

 

'소프트웨어' 편집으로 진입한 후 <pic 13>과 같이 하단에 위치한 환경 속성 항목들 중에 spring.profiles.active 환경 변수를 추가한다. (Remark. Java 플랫폼 에서는 환경 변수 명을 대문자로 변경하고, '.'을 '_'로 변경해야 하지만 Tomcat 플랫폼해서는 변경 없이 사용해야 한다.) spring.profiles.active의 값은 위에서 추가한 profile인 'alternative'로 했다.

 

<pic 13> 소프트웨어 환경 속성 설정 화면

 

그리고 화면 최하단에 '설정'버튼을 클릭하면 EC2 Instance가 Replace되는 수 분이 지나고, <pic 3>과 같이 Application Launching됐다는 Message가 나타난다. 그리고 다시 새로 배포한 애플리케이션에 접속해 보면 새로 추가한 Root Page가 <pic 14>과 같이 보여진다.

 

<pic 14> 배포한 Application의 Root Page

추가

이제 만들어진 EC2 Instance에 진입(이전 포스팅 참조)해 최초에 발생했던 오류의 원인을 확인해보자. Permission Error가 발생했던 /usr/share/tomcat8 directroy로 접근해서 권한을 확인해 보면 'root' user만 write 권한을 가진 것을 확인할 수 있다.

[ec2-user@ip-172-31-49-11 ~]$ cd /usr/share/tomcat8/
[ec2-user@ip-172-31-49-11 tomcat8]$ ls -la
total 12
drwxr-xr-x  3 root tomcat 4096  6월  5 06:31 .
drwxr-xr-x 86 root root   4096  6월  5 06:31 ..
drwxr-xr-x  2 root root   4096  6월  5 06:31 bin
lrwxrwxrwx  1 root tomcat   12  6월  5 06:31 conf -> /etc/tomcat8
lrwxrwxrwx  1 root tomcat   23  6월  5 06:31 lib -> /usr/share/java/tomcat8
lrwxrwxrwx  1 root tomcat   16  6월  5 06:31 logs -> /var/log/tomcat8
lrwxrwxrwx  1 root tomcat   23  6월  5 06:31 temp -> /var/cache/tomcat8/temp
lrwxrwxrwx  1 root tomcat   24  6월  5 06:31 webapps -> /var/lib/tomcat8/webapps
lrwxrwxrwx  1 root tomcat   23  6월  5 06:31 work -> /var/cache/tomcat8/work

 

설정 변경을 위해 쓰기 권한이 있는 Directory를 점검해 보았다. 아래와 같이 /var/tmp 디렉토리가 write 권한에 제약이 없는 것을 확인하고 application.yml 파일에서 default Profile의 <code 6>과 같이 수정했다.

[ec2-user@ip-172-31-49-11 tomcat8]$ ls -la /var
...
drwxrwxrwt  2 root root 4096  6월  8 03:15 tmp
...

 

# Database
spring:
  profiles: default
  datasource:
    hikari:
      jdbc-url: jdbc:h2:/var/tmp/browndwarfboard
...

---

...
<code 6> default profile의 내용을 수정한 application.yml

 

Debug section과 동일한 과정을 거쳐 새로운 Application을 배포하고 spring.profiles.active에 'default'를 적용하면 <pic 14>와 동일한 화면으로 접속할 수 있다. 그리고 다시 Elastic Beanstalk에 의해 생성된 EC2 instance로 들어가 /var/tmp 디렉토리를 확인해 보면 아래와 같이 H2 Database의 Data File이 생성된 것을 확인할 수 있다.

...
[ec2-user@ip-172-31-4-127 ~]$ sudo ls /var/tmp
browndwarfboard.mv.db
...

 

Reference