'build.gradle'에 해당하는 글 5건

@Value 대신에 속성들의 타입을 명시하여 아래와 같이 @ConfigurationProperties 를 선언할 수 있다.

아래는 @ConfigurationProperties 로부터 사용자 메타데이터 파일을 쉽게 생성할 수 있도록 spring-boot-configuration-processor 종속성을 가져와서 properties 작성, getter/setter 정의, getter 를 사용하는 순으로 properties 의 속성값을 가져오는 예이다.


- properties

acme.enabled=false
acme.remote-address=192.168.1.234
...
acme.security.username=abc
acme.security.password=efg
...
cs


- build.gradle

dependencies {
   compile "org.springframework.boot:spring-boot-configuration-processor"
}
cs


- AcmeProperties.java

package com.example;
 
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@Component
@ConfigurationProperties(prefix="acme")
public class AcmeProperties {
 
    private boolean enabled;
    private InetAddress remoteAddress;
    private final Security security = new Security();
    public boolean isEnabled() { ... }
    public void setEnabled(boolean enabled) { ... }
    public InetAddress getRemoteAddress() { ... }
    public void setRemoteAddress(InetAddress remoteAddress) { ... }
    public Security getSecurity() { ... }
 
    public static class Security {
        private String username;
        private String password;
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        public String getUsername() { ... }
        public void setUsername(String username) { ... }
        public String getPassword() { ... }
        public void setPassword(String password) { ... }
        public List<String> getRoles() { ... }
        public void setRoles(List<String> roles) { ... }
    }
}
cs


- MyService

@Service
public class MyService {
 
    private final AcmeProperties properties;
 
    @Autowired
    public MyService(AcmeProperties properties) {
        this.properties = properties;
    }
 
     //...
 
    @PostConstruct
    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }
}
cs


위 예에서 getter와 setter는 Spring MVC 와 마찬가지로 표준 Java Beans 프로퍼티 디스크립터를 통해 바인딩되기 때문에 일반적으로 필수이지만 setter 는 생략될 수도 있다. Project Lombok 을 사용하여 getter 와 setter 를 자동으로 추가하기도 한다.


Spring Boot 는 properties 정보를 @ConfigurationProperties Bean 에 바인딩할 때 다소 완화된 규칙을 사용하므로 properties 속성 이름과 Bean 속성 이름이 정확히 일치하지 않아도 된다. 일반적으로 properties 속성 이름의 단어 사이에 camel/Kebab case 를 사용하거나 언더바(_)로 이어주곤 하는데 이러한 이름들을 모두 동일하게 바인딩 시켜준다. @Value 는 엄격한 규칙 때문에 속성 이름이 일치하지 않으면 값을 불러올 수 없다.


  • acme.my-project.person.first-name (Kebab case)
  • acme.myProject.person.firstName (camel case)
  • acme.my_project.person.first_name (underscore)



@Validated


@ConfigurationProperties 클래스에 @Validated 어노테이션을 사용하여 유효성 검사를 시도할 수 있다.

해당 Properties 를 사용할 @Bean 메서드에 @Validated 를 사용하여 검증을 트리거 할 수도 있다.


@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
 
    @NotNull
    private InetAddress remoteAddress;
 
    @Valid
    private final Security security = new Security();
 
    // ... getters and setters
 
    public static class Security {
 
        @NotEmpty
        public String username;
 
        // ... getters and setters
    }
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Spring Boot


Spring Boot 는 production 급의 독립형 Spring 어플리케이션을 쉽게 만들 수 있으며, spring 설정이 거의 필요없다.(고 하지만 이거슨 거짓말! ㅜ)

아래는 간략한 Spring Boot 의 특징이다.


  • 독립 실행형 Spring 어플리케이션 생성.
  • Tomcat, Jetty, Undertow 등의 WAS 포함. (WAR 파일 배포 불필요)
  • 자동으로 Spring 설정.
  • 상태 확인, 측정 및 외부 구성과 같은 production 기능을 제공.
  • 코드를 생성하거나, XML 설정 파일이 불필요.



Spring Boot Gradle Plugin


Spring Boot 2.0 이상에서 gradle 로 시작하려 할 때, Spring Boot Gradle Plugin 을 사용하려면 Gradle 4.0 이상이 필요하며 다음과 같은 잇점이 있다.


  • Spring-boot-dependencies 가 제공하는 종속성 관리 가능
  • 실행 가능한 jar 또는 war 아카이브를 패키지화
  • Spring Boot 어플리케이션 실행



1. Spring-boot-gradle-plugin 시작


Spring Boot Gradle Plugin 은 간단하게 plugins 블럭을 사용하여 프로젝트에 적용할 수 있다.


plugins {
    id 'org.springframework.boot' version '2.0.6.RELEASE'
}
cs


buildscript 블럭을 사용할 경우 다음과 같이 사용한다.


buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
    }
 
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.6.RELEASE'
    }
}
apply plugin: 'org.springframework.boot'
cs



2. Spring-boot-dependencies 가 제공하는 종속성 관리


dependency-management 플러그인을 사용하면 특정 Spring boot 버전에 호환되는 종속성을 자동으로 가져오며 각 버전을 관리해 주기 때문에, 종속성 마다 버전을 기입할 필요가 없다. 아래처럼 최소한의 플러그인을 적용한다.


apply plugin: 'java'
apply plugin: 'io.spring.dependency-management'
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
cs


implementation 에는 종속성만 기입할 뿐 버전을 정의하지 않았다.


종속성의 다른 버전을 사용하고 싶은 경우 properties 를 지정하여 사용자 정의할 수 있으며, 호환성 문제를 꼭 체크하도록 한다.


ext['slf4j.version'= '1.7.20'
cs



3. 실행 가능한 jar 또는 war 아카이브 패키지화


Spring Boot Gradle Plugin 은 어플리케이션의 모든 종속성을 포함하는 실행 가능한 jar / war 파일을 생성하고 실행할 수 있다.


  • 실행 가능한 jar : java 플러그인이 적용될 때 bootJar task 가 생성되며 이를 사용하여 jar 파일을 빌드할 수 있다. build task 를 실행하면 bootJar task 도 실행된다.
  • 실행 가능한 war : war 플러그인이 적용될 때 bootWar task 가 생성되며 이를 사용하여 war 파일을 빌드할 수 있다. build task 를 실행하면 bootWar task 도 실행된다.


BootJar / BootWar task 는 Jar / War task 의 하위 클래스이다.


외부 컨테이너에서 java -jar 를 사용하여 war 파일을 실행하고 배포할 수 있도록 패키지화 하려면 서블릿 컨테이너 종속성을 providedRuntime 에 추가해야 한다.


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
cs


기본적으로 bootJar / bootWar task 가 구성되면, jar / war task 는 비활성화 되는데 이를 활성화 시켜 실행 가능한 아카이브와 일반 아카이브를 동시에 빌드하도록 구성할 수 있다. 두 아카이브가 겹치지 않도록 둘 중 하나의 분류자(classfier) 를 구성할 수 있다.


jar {
    enabled = true
}
 
 
bootJar {
    classifier = 'boot'
}
cs


실행 가능한 아카이브의 main 클래스는 task 의 classpath 에서 public static void main(String[]) 메소드를 가진 클래스를 찾아 자동으로 구성되는데 mainClassName 속성을 사용하여 명시적으로 구성할 수도 있다.


bootJar {
    mainClassName = 'com.example.ExampleApplication'
}
cs


처음에 아카이브를 빌드하지 않고도 bootRun task 를 사용하여 어플리케이션을 실행할 수 있다.


bootRun {
    main = 'com.example.ExampleApplication'
}
cs


또는 Spring Boot DSL 의 mainClassName 속성을 사용하여 프로젝트 전체에서 기본 클래스 이름을 구성할 수도 있다.


springBoot {
    mainClassName = 'com.example.ExampleApplication'
}
cs


application 플러그인이 적용된 경우 mainClassName 프로젝트 속성을 사용하여 동일한 목적으로 사용할 수도 있다.


mainClassName = 'com.example.ExampleApplication'
cs


task 의 manifest 에 Start-Class 속성을 구성할 수 있다.


bootJar {
    manifest {
        attributes 'Start-Class''com.example.ExampleApplication'
    }
}
cs



Spring Boot 프로젝트에 java plugin 이 함께 쓰이면, Spring boot plugin 은 다음과 같이 동작한다.

  • bootJar task 생성. jar 파일은 main 소스셑의 런타임 클래스패스 상의 모든 소스를 포함하며, 클래스는 BOOT-INF/classes 에 패키징되고 jar 파일들은 BOOT-INF/lib 에 패키징 됨.
  • jar task 비활성화.
  • 어플리케이션을 실행하는 bootRun task 생성.
  • bootJar task 에 의해 생성된 아티팩트를 포함하도록 bootArchives 를 생성.
  • JavaCompile task 가 UTF-8 을 사용하도록 구성.
  • JavaCompile task 가 파라미터(-parameters) 를 사용하도록 구성.


Spring Boot 프로젝트에 war plugin 이 함께 쓰이면, Spring boot plugin 은 다음과 같이 동작한다.

  • bootWar task 생성. 표준 구성에 더하여 providedRuntime 구성이 WEB-INF/lib-provided 에 패키징 됨
  • war task 비활성화.
  • bootArchives 구성에 bootWar task 에 의해 생성된 아티팩트를 포함하도록 구성.


Spring Boot 프로젝트에 application plugin 이 함께 쓰이면, Spring boot plugin 은 다음과 같이 동작한다.

  • main 속성 규칙으로 mainClassName 속성을 사용하도록 booRun task 구성.
  • manifest 의 Start-Class 규칙으로 mainClassName 속성을 사용하도록 bootJar / bootWar task 구성.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret
// https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
provided group: 'javax.servlet'name'javax.servlet-api', version: '4.0.1'
cs


build.gradle 에 복붙하니 에러난다.


Could not find method provided() for arguments [{group=javax.servlet, name=javax.servlet-api, version=4.0.1}] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.


provided() 메소드가 없단다.

컴파일에만 사용(런타임 안사용) 하기 위한 provided 범위를 지정하는 방법은 세가지.


  1. configurations 와 sourceSets 에 provided 설정.
  2. war plugin 으로 providedCompile 설정.
  3. java plugin 의 compileOnly 설정.


mvn repository 에서 아무 설정없이 사용할 수 있는 compileOnly 를 사용하지 않고, provided 를 사용한 이유를 모르겠다.

구버전 gradle 를 사용하는 자들에 대한 배려인지, 아니면 compileOnly 와 provided 의 기능에 어떤 보이지 않는 차이가 있는건지...




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

ECS with gradle

Server/AWS 2018. 8. 28. 23:51

개발 중에는 Java + Gradle 프로젝트에서 결과물을 확인하기 위해, ssh 플러그인을 사용해 개발 서버에 배포하고 있다. 실서버 ECS 배포를 위해 이미지를 만들고 ECR 에 푸시하고 Task 를 배포하는 플러그인이 있나 열심히 검색해 보았으나, 음... 생각만큼 지원이 덜 되어 있는거 같아 그냥 AWS Cli 명령으로 도배했다. 난 포기했지만 생각 있으면 써보시길...


'com.bmuschko.docker-remote-api'

'com.patdouble.awsecr'


build.gradle 로 처리하려는 것들을 나열해 봤다.


  1. 인스턴스 수량 증가
  2. Docker 이미지 빌드
  3. 기존 ECR latest 이미지 태그 변경 -> backup
  4. 이미지 ECR 로 푸시
  5. 서비스 업데이트 (배포)
  6. 인스턴스 수량 감소


이 모든걸 원클릭으로 끝내버리고 싶었는데, 배포 완료 시점을 알아야 했고 그로 인한 서브 작업들이 꼴도 보기 싫어서 그만 두었다. (꽤 지긋지긋해졌다) 저 얼마되지도 않는 작업을 실행하는데 필요한 오라질 도구가 참 많다. Docker, AWS Cli, ECS-Cli, PowerShell, ... 물론 Linux 환경이라면 PowerShell 하나 정도는 줄일 수 있겠지만 그러자고 Linux 환경을 하나 더 구축하는 것 역시 오라질이다. 그리고 ECR 이미지 태그를 변경하는 것 역시 포기했다. PowerShell 에서 AWSPowerShell 을 설치해야 되는데 에러 투성이다. 포기하고 backup 은 push 를 두 번 하는 방향으로; AWS Cli 로 통합하지 못하고 AWS Cli, ECS-Cli, PowerShell 을 다 깔 수 밖에 없게끔 만들어 놓은 이유가 참으로 궁금하다. docker 가 PowerShell 을 사용한 원죄가 있긴 하지만... 그렇게 따지면 윈도우를 만든... ㅡ.ㅡ;


아무튼 내가 구축하려는 구성은 기본 인스턴스 2 대, 배포시 4 대, 배포후 다시 2 대.

Cluster, Service, Task, ECR 등은 이미 콘솔에 생성되어 있다. (구성 내용 생략)


그리하여 실행할 gradle Task 는 3 개 정도 되겠다.

1,2,3,4 번을 묶어 backup 이 어찌될지 모르니 푸시까지 하나 (createImageNpushToECR), 5 번 배포 하나(ECSDeploy), 6 번 인스턴스 감소 하나(ECSInstanceDown).


* build.gradle


ext {
    clusterName = 'clusterName'
    serviceName = 'serviceName'
    taskName = 'taskName'
    stackName = 'EC2ContainerService-now-web'
    accountId = '8707500123456'
    region = 'ap-northeast-1'
    tagName = '870750012345.dkr.ecr.ap-northeast-1.amazonaws.com/web:latest'
    instanceMinCnt = 2
    instanceMaxCnt = 4
    getInstanceCnt = 0
}
 
task createImageNpushToECR() {
    group = 'docker'
    dependsOn war
    doFirst {
        exec { commandLine 'cmd''/c''aws ecr get-login --no-include-email --profile profileName > temp.cmd' }
        exec { commandLine 'cmd''/c''call temp.cmd' }
        exec { commandLine 'cmd''/c''del temp.cmd' }
    }
    doLast {
        exec { commandLine 'cmd''/c''docker build -t '+tags+' .' }
        exec { commandLine 'docker''push', tags }
    }
}
 
task ECSDeploy {
    group = 'docker'
    doLast {
        exec { commandLine 'cmd''/c''aws ecs update-service --force-new-deployment --profile profileName --cluster ' + clusterName + '  --service ' + serviceName + ' --task-definition ' + taskName }
    }
}
 
task ECSInstanceCnt {
    group = 'docker'
    doLast {
        new ByteArrayOutputStream().withStream { cnt ->
            exec {
                commandLine 'cmd''/c''aws ecs list-container-instances --cluster ' + clusterName + ' | findstr /R /C:"[' + accountId + ']" | find /c /v ""'
                standardOutput = cnt
            }
            getInstanceCnt = cnt.toString().toInteger()
        }
        println '# Current Web Instance Cnt : ' + getInstanceCnt
    }
}
 
task ECSInstanceUp {
    group = 'docker'
    dependsOn ECSInstanceCnt
    doLast {
        if (instanceMaxCnt > getInstanceCnt) {
            exec { commandLine 'cmd''/c''aws ecs update-service --cluster ' + clusterName + '  --service ' + serviceName + ' --deployment-configuration minimumHealthyPercent=50,maximumPercent=200' }
            exec { commandLine 'cmd''/c''ecs-cli configure --cluster ' + clusterName + ' --default-launch-type EC2 --region ' + region + ' --cfn-stack-name ' + stackName + ' --config-name ' + clusterName }
            exec { commandLine 'cmd''/c''ecs-cli scale --capability-iam --size ' + instanceMaxCnt + ' --cluster ' + clusterName + ' --aws-profile profileName' }
        }
    }
}
 
task ECSInstanceDown {
    group = 'docker'
    dependsOn ECSInstanceCnt
    doLast {
        if (instanceMinCnt < getInstanceCnt) {
            exec { commandLine 'cmd''/c''aws ecs update-service --cluster ' + clusterName + '  --service ' + serviceName + ' --deployment-configuration minimumHealthyPercent=100,maximumPercent=200' }
            exec { commandLine 'cmd''/c''ecs-cli configure --cluster ' + clusterName + ' --default-launch-type EC2 --region ' + region + ' --cfn-stack-name ' +  stackName + ' --config-name ' + clusterName }
            exec { commandLine 'cmd''/c''ecs-cli scale --capability-iam --size ' + instanceMinCnt + ' --cluster ' + clusterName + ' --aws-profile profileName' }
        }
    }
}
cs


대충 이렇다. 지금 딱 필요한 만큼만 꾸역꾸역 만들어 놓긴 했는데... 맘엔 안들지만 그래도 그냥 쓸란다.

간단하게 설명하자면...

  • createImageNpushToECR : war 파일 만들고 ECR 에 로그인 후 Docker 이미지 만들고 푸시.
  • ECSDeploy : Service 업데이트로 최신 이미지(latest) 배포
  • ECSInstanceCnt : 현재 Cluster 의 인스턴스 수량 (2개 설정)
  • ECSInstanceUp : 인스턴스 증가시킬 수량인 4 개 보다 작으면 4 개로 증설
  • ECSInstanceDown : 인스턴스 감소시킬 수량인 2 개 보다 크면 2 개로 감소


ECR 로그인은 gradle 소스에 있고 ECS-Cli 자격 증명 역시 aws profile 로 통합했다. AWS Cli 를 위한 자격 증명은 잘 등록해 보자... aws configure...


명령어도 윈도우 명령어로 바꿔 넣느라 삽질 좀 했다. 후...

개발에만 집중할 수 있게 해준다더니, 빡이쳐서 개발을 할 수가 없다. 아오...




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Jenkins build

Tool/Jenkins 2017. 2. 15. 23:19

Freestyle project 의 UI 에서 설정한 빌드/배포 등을 pipeline script 로도 작성할 수 있다.

또한 Multibranch Pipeline item 을 사용하여 Jenkinsfile 과 함께 작성할 수도 있다.


하지만 결론부터 얘기하자면... 난 script 로 작성하는 것을 실패했다 ㅜ

빌드까지는 문제 없었지만 Publish Over SSH 플러그인을 사용하여 배포하는 부분을 script 로 변환하는데 실패했다. 내공 부족 ㅜ

일단 Freestyle project UI 만으로 Product/Development 코드 분리와 모듈별 web/api/cms 부분을 빌드하는데 별 문제가 없으니 일단은 그냥 진행하는걸로...;



Build


기존에는 Product/Development 코드를 build.gradle 에 변수 값을(dev/prod)를 바꿔가며 해당 properties 를 가져와 배포했었다.

이부분의 변수를 gradle task 에서 전달하는 것으로 변경했다.


- From build.gradle


environment = 'dev'
//environment = 'prod'
cs


- To build.gradle


def deployType
if (project.hasProperty("deployTypeParam")) {
    deployType = deployTypeParam
else {
    deployType = "dev"
}
environment = deployType
cs


이러면 빌드 파라미터에 deployTypeParam 가 있다면 그 값을 적용할 것이고, 없다면 dev 값으로 설정 파일들을 가져올 것이다.

그래고 product 서버에 배포할 때는 [Build] - [Tasks] 에 다음과 같이 설정한다. (ex. web module)


:web:build -PdeployTypeParam='prod'



Configuration


그리고 Jenkins 기본 설정으로 포트와 타임존을 변경했다.

포트는 기본 8080 을 iptables 에서 80 으로 변경했고,

타임존은 /etc/sysconfig/jenkins 파일에 다음 줄을 추가했다.


JENKINS_JAVA_OPTIONS="-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Seoul"
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret