'gradle'에 해당하는 글 19건

FAILURE: Build failed with an exception.


* What went wrong:

Execution failed for task ':Project:bootRun'.

> A problem occurred starting process 'command 'C:\Java\jdk1.8.0_65\bin\java.exe''


* Try:

Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.


BUILD FAILED


Total time: 2.332 secs

CreateProcess error=206, The filename or extension is too long. (파일 이름이나 확장명이 너무 깁니다.)

오전 10:58:49: Task execution finished 'bootRun'.


gradle library 추가 후 bootrun 실행시 발생한 오류. 뭔 파일 이름이나 확장명이 길다는건지; --debug 는 봐도 모르겠고...

검색 결과 클래스패스 종족성이 너무 길다는거 같은데, 클래스패스를 찍어 봤더니 약 33000자가 넘었다.ㅋㅋ;

Windows 에 CreateProcess 의 limit 가 32767 인거 같음.

Bootrun 시작 명령이 java -jar MyApp.jar -classpath a.jar,b.jar,c.jar,... 처럼 나열되는데 그럼 라이브러리를 몇 개 빼야하나;



해결책은 하나의 jar 파일에 저 종속성들을 나열하는 manifest 파일을 만들고 명령문에는 그 jar 파일 하나만을 호출하면 끝!

Gradle.build 코드는 다음과 같다.


task pathingJar(type: Jar) {
    dependsOn configurations.runtime
    appendix = 'pathing'
 
    doFirst {
        manifest {
            attributes "Class-Path": configurations.runtime.files.collect {
                it.toURI().toString().replaceFirst(/file:\/+/, '/')
            }.join(' ')
        }
    }
}
 
bootRun {
    dependsOn pathingJar
    doFirst {
        classpath = files("$buildDir/classes/main", "$buildDir/resources/main", pathingJar.archivePath)
    }
}
cs


이제 bootrun 을 실행하면 아래와 같이 manifest 파일이 담긴 pathing.jar 파일로 모든 종속성을 실행하게 된다.


> java -jar MyApp.jar -classpath pathing.jar
cs




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

,
// 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
손가락귀신
정신 못차리면, 벌 받는다.

,

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
손가락귀신
정신 못차리면, 벌 받는다.

,

Maven vs Gradle

Tool/Gradle 2018. 3. 20. 23:55



빌드 관리 툴로써 이 둘 중 하나를 고민한다면 어느 것이 나을까.

정답이 있는 건 아니지만, 처음 빌드 관리 툴을 사용하는 입장이라면 gradle 을 추천하고 싶다.

일단 maven 보다 나은 점을 꼽아보자면, 빠른 성능과 간결한 설정을 들 수 있다. https://gradle.org/maven-vs-gradle/ 참고.

아무래도 maven 의 단점을 보완한 최신 툴이고, 계속해서 많은 버전 업그레이드를 진행하고 있어 그 격차가 계속 벌어지고 있는 듯 하다.

동일한 역할을 하는 툴이므로 maven 에 익숙한 분들은 굳이 약간의 성능을 느끼기 위해 gradle 로 갈아타려 하지 않을 수도 있다.

빌드만 잘되면 그 뿐이지만... gradle 의 엄청난 기능들과 확장성을 공부한다면 마음이 바뀔 수도 있을지 모른다.


아래는 Maven 과 Gradle 의 간단한 비교이다.


gradle 의 빌드 스크립트는 groovy 라는 언어로 작성해야 하므로 maven 의 xml 에 비하면 친숙하진 않지만 확장성이 뛰어나다.

maven 은 프로젝트가 커질수록 빌드 스크립트의 내용이 길어지고 가독성이 떨어지는 반면, gradle 은 훨씬 적은 양의 스크립트로 짧고 간결하게 작성할 수 있다.

maven 의 경우 멀티 프로젝트에서 특정 설정을 다른 모듈에서 사용하려면 상속을 받아야 하지만 gradle 은 설정 주입 방식으로 이를 해결한다.

그래서 gradle 은 멀티 프로젝트에 매우 적합하며, 빌드 속도는 다양한 시나리오 상에서 10~100배 가량이 빠르다.


선택은 자유.


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

,

log4j 를 만든 Ceki Gülcü 는 log4j 를 포함한 다른 logging 시스템들 보다 더 가볍고 빠르게 동작하는 Logback 을 개발하였다.

log4j 와 logback 은 개념적으로 매우 유사하고, log4j 를 사용했던 사용자라면 logback 역시 쉽게 다가갈 수 있다.

System.out.println 와 비교하자면 빠르고, 레벨, 환경 등에 따라 출력 여부를 표시할 수 있다는 점?



Logback 모듈


Logback 은 logback-core, logback-classic, logback-access 세 가지 모듈로 나뉜다.


  • logback-classic 은 log4j 의 향상된 버전에 해당하며, SLF4J API를 기본적으로 구현하여 log4j 이나 JUL 과 같은 다른 로깅 시스템과 쉽게 전환이 가능하다.
  • logback-access 는 Servlet 컨테이너와 통합되어 HTTP 액세스 로그 기능을 제공한다.
  • logback-core 은 위 두 모듈의 기반이 되며, logback 은 이 모듈의 일부인 Joran 이라는 구성 라이브러리에 의존한다. 



Logback 특징


  • logback-classic 모듈의 Logger 클래스가 SLF4J API 를 기본적으로 구현하므로 SLF4J 로거를 호출 할 때 오버 헤드가 발생하지 않는다. 
  • logback-classic 은 설정 파일이 수정되면 자동으로 reload 할 수 있다. 
  • FileAppender 를 비롯한 하위 클래스는 I/O 오류를 정상적으로 복구할 수 있다.
  • 아카이브 된 로그 파일을 자동으로 비동기 압축하며, maxHistory 프로퍼티를 설정하여 오래된 아카이브 로그 파일을 자동으로 삭제할 수 있다.
  • Prudent 모드로 여러 FileAppender 인스턴스로부터 동일한 로그 파일에 안전하게 기록할 수 있다.
  • development, testing, production 같은 다른 환경에 대하여 구성 파일을 중복 구성할 필요없이, 단일 구성 파일에서 조건부 처리를 사용할 수 있다.
    (조건부 처리시 Janino 라이브러리 필요)
  • MDCFilter 를 사용하여 특정 사용자에 대한 Logging Level 지정이 가능하다.
  • SiftingAppender 는 사용자 세션에 따라 로그 파일을 분리할 수 있다.
  • logback 이 exception 을 출력할 때, stack trace 에서 exception 에 개입하는 클래스, 패키지, 패키지 버전을 알 수 있다.
  • logback-Access 모듈은 Jetty, Tomcat 같은 Servlet 컨테이너와 통합되어 풍부하고 강력한 HTTP 액세스 로그 기능을 제공한다.



Logback 요구사항


logback-classic 모듈을 사용하려면, classpath 에 logback-classic.jar 외에도 slf4j-api.jar 와 logback-core.jar 파일이 있어야 한다.

maven 이나 gradle 같은 빌드 툴을 사용할 때는, logback-classic 모듈만 가져오면 slf4j-api.jar, logback-core.jar 파일도 딸려온다.


gradle 예)


dependencies {
    // logback-classic (slf4j-api, logback-core 포함)
    compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
}
cs



Logback 구현


package chapters.introduction;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class HelloWorld1 {
 
    public static void main(String[] args) {
 
        Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
        logger.debug("Hello world.");  // 20:49:07.962 [main] DEBUG chapters.introduction.HelloWorld1 - Hello world.
 
    }
}
cs


"Hello world." 라는 debug 레벨의 로그를 출력하는 예제이다.

SLF4J API 에 정의된 Logger 와 LoggerFactory 를 import 하고, Logger 인스턴스를 생성해서 사용했다.

이 예제처럼, Logging 이 필요한 대부분의 경우 logback 클래스를 참조하지 않고 SLF4J API 만으로 구현해야 한다.


만약 logback 수명주기 동안의 로그가 필요하다면 StatusManager 라는 컴포넌트를 이용할 수 있으며, Logback 관련 문제 진단에 매우 유용할 수 있다.

StatusPrinter 클래스의 print() 메소드를 호출하여 logback 의 내부 상태를 출력할 수 있다.


import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
 
...
 
// print internal state
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
 
/*
12:49:22.203 [main] DEBUG chapters.introduction.HelloWorld2 - Hello world.
12:49:22,076 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
12:49:22,078 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
12:49:22,093 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
12:49:22,093 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Setting up default configuration.
*/
cs


위 로그에서 logback 이 구성 파일을 찾지 못하여 기본 정책(ConsoleAppender) 으로 구성되었음을 알 수 있다.

Appender 는 로그를 남길 목적지로, console, files, Syslog, TCP Sockets, JMS 등 다양하게 설정할 수 있다.

에러가 발생하면 logback은 자동으로 내부 상태를 콘솔에 출력한다.



Logback 사용 정리


  1. logback.xml 이나 logback.groovy 같은 구성 설정.
  2. Logging 이 필요한 모든 클래스에 org.slf4j.Logger 인스턴스 생성.
  3. debug(), info(), warn() 및 error() 등 적절한 출력 메소드 호출로 구성된 appender 에 Logging.




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

,