티스토리 뷰

개요

  • CentOS(RHEL)에서 특정 기능을 백그라운드에서 24시간 수행하는 데몬 서비스를 만드려면 start(), stop()이 정의된 Bash 스크립트를 /etc/init.d 디렉토리에 작성하거나 심볼릭 링크를 생성하면 된다. Java 애플리케이션 또한 이 방식을 따르면 nohup java -jar {some_project} &으로 직접 실행하지 않아도 서비스로 작동하게 만들 수 있다.
  • Spring Boot는 이러한 서비스를 실행하기 위한 Bash 스크립트를 빌드 옵션을 통해 제공하고 있다. 개발자는 따로 서비스 스크립트를 작성하지 않아도 편리하게 이용할 수 있다. 다만, Spring Boot버전마다 다른 설정이 필요하다.

Spring Boot 1.x

  • Spring Boot 1.x에서는 /build.gradle 파일에 아래 내용을 추가하면 된다.
bootRepackage {
    executable = true
}

Spring Boot 2.x

  • Spring Boot 2.x에서는 /build.gradle 파일에 아래 내용을 추가하면 된다.
bootJar {
    launchScript()
}

서비스 스크립트 등록

  • 이제 프로젝트를 빌드하고 등록할 차례이다.
# 빌드된 프로젝트를 서비스로 등록
$ sudo ln -s /home/some-project/some-project.jar /etc/init.d/some-project

# 등록한 서비스에 실행 권한 부여
$ sudo chmod 0755 /etc/init.d/some-project

# 서비스 실행시 적용될 JVM 옵션 설정
$ nano /home/some-project/some-project.conf
PID_FOLDER='/home/some-project'
LOG_FOLDER='/dev'
LOG_FILENAME='null'
JAVA_OPTS='-server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=512m -Dspring.profiles.active=prod'
  • /etc/init.d/ 경로에 빌드된 프로젝트의 .jar 파일의 심볼릭 링크를 생성하고 파일 실행 권한을 부여하는 것 만으로 운영체제 내의 정식 서비스로 작동하게 된다. 이 작업은 최초 1번만 필요하며, 추후 빌드된 파일의 경로와 이름이 변경되지 않는한 자동으로 적용된다.
  • 추가적으로 빌드된 프로젝트의 .jar 파일과 동일한 디렉토리에 동일한 파일 이름으로 .conf 파일을 작성하면 서비스 실행시 적용될 다양한 옵션을 자동으로 적용할 수 있다.
  • .conf 파일에서 LOG_FOLDERLOG_FILENAME 옵션을 사용하면 기본적으로 콘솔에 출력되는 로그를 실시간으로 파일에 남길 수 있다. 하지만 Spring Boot에 기본 내장된 Logback을 이용한 파일 로깅(logback.xml에 로그 처리 정책을 정의하는 방법)이 훨씬 강력하고 다양한 기능을 제공하기 때문에 위와 같이 /dev/null을 지정하여 서비스 레벨에서의 로그 기능을 비활성화하는 방법을 추천한다.
  • 다시 정리하면, 최종적으로 빌드된 some-project.jar, 이와 파일 이름이 동일한 some-project.conf 2개 파일이 존재해야 하고, 해당 some-project.jar에 대한 심볼릭 링크를 /etc/init.d/some-project로 생성하면 해당 애플리케이션의 서비스로서의 실행 준비가 완료되는 것이다.

서비스 실행

  • 등록된 서비스는 아래와 같이 실행, 종료, 재시작 등의 제어가 가능하다.
# 애플리케이션 시작
$ sudo service some-project start

# 애플리케이션 종료
$ sudo service some-project stop

# 애플리케이션 재시작
$ sudo service some-project restart

# 애플리케이션 상태 확인
$ sudo service some-project status

# 운영체제 재시작시 자동 시작
$ sudo chkconfig some-project on 
  • chkconfig on 명령어를 사용하면 서버 재시작시 애플리케이션을 자동으로 실행되도록 설정할 수 있다. 서버 전원을 껐다 켜는 상황 등에 유용하게 대처할 수 있다.

배포 스크립트 작성

  • 위를 응용하면 원격지의 CI/CD 파이프라인에서 사용 가능한 배포 스크립트를 작성할 수 있다. 아래는 kscript를 사용하여 스크립트를 작성한 예이다. (kscript의 자세한 사용법은 본 블로그의 이 글을 참고한다.)
# 배포 스크립트 작성
$ nano some-project.kts
#!/usr/bin/env kscript
import java.io.File

val serviceName = "some-project"
val servicePort = "8080"

println("[$serviceName] Stopping...")

"dos2unix $serviceName.conf".exec()

val stopResult = "sudo service $serviceName stop".exec()
when (stopResult.contains("Stopped") || stopResult.contains("Not running")) {
    true -> println("[$serviceName] Stopped.")
    false -> {
        println("[$serviceName] Stop Failed.")
        kotlin.system.exitProcess(1)
    }
}

Thread.sleep(10 * 1000)

val startResult = "sudo service $serviceName start".exec()
when (startResult.contains("Started")) {
    true -> println("[$serviceName] Starting...")
    false -> {
        println("[$serviceName] Start Failed.")
        kotlin.system.exitProcess(1)
    }
}

Thread.sleep(60 * 1000)

do {
    val monitorResult = "nmap localhost".exec()
} while (!monitorResult.contains("$servicePort"))

println("[$serviceName] Started.")

fun String.exec(cwd: File? = null): String {
    val parts = this.split("\\s".toRegex())
    val proc = ProcessBuilder(*parts.toTypedArray())
            .directory(cwd)
            .redirectOutput(ProcessBuilder.Redirect.PIPE)
            .redirectError(ProcessBuilder.Redirect.PIPE)
            .start()
    proc.waitFor()
    return proc.inputStream.bufferedReader().readText()
}

# 배포 스크립트 실행
$ kscript some-project.kts

Gradle 배포 작업으로 등록하기

  • 위 내용을 응용하면 Gradle을 이용하여 배포 작업(Task)을 자동화하는 것도 가능하다. 먼저 프로젝트 내 /build.gradle 파일에 아래 내용을 추가한다.
buildscript {
    dependencies {
        classpath 'org.hidetake:gradle-ssh-plugin:2.10.1'
    }
}

apply plugin: 'org.hidetake.ssh'

remotes {
    prod1 {
        host = "some-project-1.com"
        user = "{user}"
        # password = "{password}"
        identity = file("D:/.ssh/prod1")
    }
    prod2 {
        host = "some-project-2.com"
        user = "{user}"
        # password = "{password}"
        identity = file("D:/.ssh/prod2")
    }
}

ext.deploy = { remote, jarSource, jarDestination, confSource, confDestination ->
    ssh.run {
        session(remote) {
            if (file(jarSource).exists()) {
                put from: jarSource, into: jarDestination
                put from: confSource, into: confDestination
                execute 'sudo service some-project status'
                execute 'sudo service some-project restart'
            }
        }
    }
}

task deployToProd() {
    doLast {
        ssh.run {
            deploy(remotes.prod1, "$buildDir/libs/some-project.jar", '/home/json/some-project.jar', "$projectDir/src/main/resources/some-project-prod.conf", '/home/{user}/some-project.conf')
            deploy(remotes.prod2, "$buildDir/libs/some-project.jar", '/home/json/some-project.jar', "$projectDir/src/main/resources/some-project-prod.conf", '/home/{user}/some-project.conf')
        }
    }
}
  • 원격 배포 작업을 가능하게 해주는 Gradle SSH Plugin 플러그인을 추가하였다. [관련 링크]
  • remotes에는 배포 대상이 되는 원격지의 SSH 계정 정보를 작성한다. 보는 바와 같이 복수개의 원격지 설정이 가능하다 identity에는 SSH Key 로그인을 위한 Private Key 파일 경로를 작성한다. 원격지에 저장된 Public Key와 맵핑된 키 정보를 지정하면 되며, Windows의 경우 puttygen.exe에서 키 생성 후 Conversions → Export OpenSSH Key를 실행하면 적합한 형식의 파일로 저장이 가능하다.
  • 위 예제에는 deployToProd라는 이름의 운영 서버에 대한 배포 작업을 작성하였다. (필요에 따라 deployToDev, deployToStaging, deployToProd 등 각 환경에 맞게 추가 작업을 작성하면 된다.) doLast 블록을 명시하여 해당 배포 작업이 빌드 작업과 독립적으로 실행될 수 있도록 제한했다. [관련 링크]

트러블슈팅

  • .conf 파일 작성 후 저장시 개행(라인 전환) 방법은 반드시 리눅스 표준의 LF 기호만 사용해야 한다. 이를 고려하지 않고, Windows 환경에서 무심코 CR, LF 기호로 저장하면 서비스 스크립트 실행시 이를 인식하지 못하여 아래와 같은 오류가 발생한다.
$ sudo service some-project start
 does not exist. Falling back to /tmp
 does not exist. Falling back to /tmp 
  • 해결책은 아래와 같다.
# dos2unix를 설치
$ sudo yum install dos2unix

# Windows 표준의 CR LF 기호를 LF 기호로 변경
$ dos2unix some-project.conf

참고 글

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함