🔥 포스팅 계기
안트 돌리면 webapps 내 소스 파일이 모두 덮어씌워지는 게 일반적이지만
예외 파일을 설정할 수 있는데 그 방법을 설명하겠따!
📍 문제 상황
👉🏻 로컬에서 war을 말아 테스트 서버에 업로드 후 prepare, deploy 후 서비스 기동하니 오류가 남
👉🏻 SSL key 파일인 jks가 해당 경로에 없다는 뜻인데... 오류 경로를 보니 서버가 아닌 내 로컬 경로임
👉🏻 SSL key 파일은 /opt/puppy/work/webapps/workware1/WEB-INF/classes/profile/dev/default.properties에서 설정
👉🏻 이 파일은 war 말아진 로컬 파일로 덮어씌워지면 안 되고 서버 파일을 그대로 사용해야 함
26-Nov-2024 16:30:40.278 정보 [main] org.apache.coyote.AbstractProtocol.init 프로토콜 핸들러 ["http-nio-18074"]을(를) 초기화합니다.
26-Nov-2024 16:30:40.312 정보 [main] org.apache.coyote.AbstractProtocol.init 프로토콜 핸들러 ["ajp-nio-0.0.0.0-18075"]을(를) 초기화합니다.
26-Nov-2024 16:30:40.314 정보 [main] org.apache.catalina.startup.Catalina.load [2066] 밀리초 내에 서버가 초기화되었습니다.
26-Nov-2024 16:30:40.388 정보 [main] org.apache.catalina.core.StandardService.startInternal 서비스 [Catalina]을(를) 시작합니다.
26-Nov-2024 16:30:40.388 정보 [main] org.apache.catalina.core.StandardEngine.startInternal 서버 엔진을 시작합니다: [Apache Tomcat/9.0.64]
26-Nov-2024 16:30:40.398 정보 [main] org.apache.catalina.startup.HostConfig.deployDescriptor 배치 descriptor [/서버내톰캣경로/tomcat-9.0.64/instances/경로보호/conf/Catalina/localhost/work.xml]을(를) 배치합니다.
26-Nov-2024 16:30:51.997 정보 [main] org.apache.jasper.servlet.TldScanner.scanJars 적어도 하나의 JAR가 TLD들을 찾기 위해 스캔되었으나 아무 것도 찾지 못했습니다. 스캔했으나 TLD가 없는 JAR들의 전체 목록을 보시려면, 로그 레벨을 디버그 레벨로 설정하십시오. 스캔 과정에서 불필요한 JAR들을 건너뛰면, 시스템 시작 시간과 JSP 컴파일 시간을 단축시킬 수 있습니다.
26-Nov-2024 16:31:46.009 심각 [main] null.null Failed to create context
java.io.FileNotFoundException: C:/03.devUtil/ssl/이건내로컬경로/jks.com_jks.jks (그런 파일이나 디렉터리가 없습니다)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at org.vertx.java.core.net.impl.TCPSSLHelper.loadStore(TCPSSLHelper.java:365)
📍 ant 빌드 순서 확인
👉🏻 기본 ant.sh 순서는 다음과 같음 (서비스, 회사마다 ant 적용 방법 및 경로는 다르므로 참고)
1) ant.sh, build.xml, war 파일이 있는 경로 접근
$ ./ant.sh prepare
2) 서비스 sh 있는 경로 이동하고 서비스 내리기
$ ./stop.sh
3) ant.sh, build.xml, war 파일이 있는 경로 재접근
$ ./ant.sh deploy
4) 서비스 sh 있는 경로 재이동하고 서비스 올리기
$ ./start.sh
👉🏻 start.sh, stop.sh는 말 그대로 서비스 시작/중지를 뜻하는데 ant.sh를 prepare, deploy 하는 건 어떤 작업일까?
📍 ant.sh, build.xml의 관계
👉🏻 ant.sh를 열어 보면 큰 디렉토리들을 별칭으로 선언해 준 것 말고는 뭐 없어 보임
#!/bin/sh
#--------------------------------------------------------------------
# APACHE INSTALL SCRIPT
# Author : PUPPY KIM
# Version : 1.0
#--------------------------------------------------------------------
export PUPPY_BASE=/opt/puppy
export WORK_BASE=$PUPPY_BASE/work
export APP_BASE=$PUPPY_BASE/app
export GROOVY_HOME=$APP_BASE/tools/groovy-1.7.10
export JAVA_HOME=$APP_BASE/tools/jdk8
export CATALINA_HOME=$APP_BASE/tomcat9.0
export ANT_HOME=$APP_BASE/tools/apache-ant-1.8.2
CLASSPATH=$GROOVY_HOME/embeddable/groovy-all-1.7.10.jar
CLASSPATH=$CLASSPATH:$JAVA_HOME/lib/tools.jar
CLASSPATH=$CLASSPATH:.
echo $WORK_BASE
export CLASSPATH
$ANT_HOME/bin/ant $1 $2 $3 $
👉🏻 build.xml을 열어 보면 더욱 많은 정보가 있음
<?xml version="1.0" encoding="UTF-8"?>
<project name="git-working" default="default">
<!-- PROPERTIES & DEFINITION SECTIOn ========================================================== -->
<property environment="env" />
<property name="work.name" value="workware" />
<property name="work.base" value="${env.WORK_BASE}" />
<property name="work.tools" value="${env.APP_BASE}/tools" />
<property name="work.java.home" value="${env.JAVA_HOME}" />
<property name="work.tomcat.home" value="${env.CATALINA_HOME}" />
<property file="${work.base}/properties/webconfig.properties" />
<!-- must modify -->
<property name="work.todir1" value="${work.base}/webapps/workware1" />
<property name="work.todir2" value="${work.base}/webapps/workware2" />
<path id="classpath">
<fileset dir="${work.tomcat.home}/lib"><include name="*.jar" /></fileset>
<fileset dir="${work.tools}/groovy-1.7.10/lib"><include name="*.jar" /></fileset>
<fileset dir="${work.tools}/ant-contrib"><include name="*.jar" /></fileset>
</path>
<taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy" classpathref="classpath" />
<taskdef resource="net/sf/antcontrib/antcontrib.properties" classpathref="classpath" />
<!-- file checking -->
<target name="fileCheck">
<condition property="workFileCheck" value="false">
<available file="./work.war" type="file" />
</condition>
<condition property="noworkFile" value="false">
<not><available file="./work.war" type="file" /></not>
</condition>
</target>
<target name="message1" if="noworkFile">
<echo message="You need a 'work.war' file that compresses workware sources." />
</target>
<target name="work2Check">
<condition property="work2DirYes">
<available file="${work.base}/webapps/workware2" type="dir" />
</condition>
<condition property="work2DirNo">
<not><available file="${work.base}/webapps/workware2" type="dir" /></not>
</condition>
</target>
<target name="work2CheckMsg" if="work2DirNo">
<echo message="Proceed only with 'workware1'." />
</target>
<target name="readywork1DirClear">
<delete dir="${work.base}/webapps/workware1.ready" />
</target>
<target name="readywork2DirClear">
<delete dir="${work.base}/webapps/workware2.ready" />
</target>
<target name="prepare-work1" depends="readywork1DirClear" if="workFileCheck">
<echo message="Prepare for workware1 source." />
<mkdir dir="${work.base}/webapps/workware1.ready" />
<unwar src="work.war" dest="${work.base}/webapps/workware1.ready" />
<copy todir="${work.base}/webapps/workware1.ready" overwrite="true" verbose="true" preservelastmodified="true">
<fileset dir="${work.todir1}">
<include name="/WEB-INF/classes/profile/development/default.properties" />
</fileset>
</copy>
</target>
<target name="prepare-work2" depends="readywork2DirClear" if="work2DirYes">
<echo message="Prepare for workware2 source." />
<mkdir dir="${work.base}/webapps/workware2.ready" />
<unwar src="work.war" dest="${work.base}/webapps/workware2.ready" />
<copy todir="${work.base}/webapps/workware2.ready" overwrite="true" verbose="true" preservelastmodified="true">
<fileset dir="${work.todir2}">
<include name="resources/component/install/ko/**" />
</fileset>
</copy>
</target>
<target name="prepare" depends="work2Check,fileCheck,message1">
<antcall target="prepare-work1" />
<antcall target="prepare-work2" />
</target>
<target name="rdy1DirCheck">
<condition property="work1rdyDirYes">
<available file="${work.base}/webapps/workware1.ready" type="dir" />
</condition>
<condition property="work1rdyDirNo">
<not><available file="${work.base}/webapps/workware1.ready" type="dir" /></not>
</condition>
</target>
<target name="work1rdyCheckMsg" if="work1rdyDirNo">
<echo message="The workware1.ready directory does not exist." />
</target>
<target name="rdy2DirCheck">
<condition property="work2rdyDirYes">
<and>
<available file="${work.base}/webapps/workware2.ready" type="dir" />
<available file="${work.base}/webapps/workware2" type="dir" />
</and>
</condition>
<condition property="work2rdyDirNo">
<not><available file="${work.base}/webapps/workware2.ready" type="dir" /></not>
</condition>
</target>
<target name="work2rdyCheckMsg" if="work2rdyDirNo">
<echo message="The workware2.ready directory does not exist." />
</target>
<target name="deploy-work1" depends="rdy1DirCheck,work1rdyCheckMsg" if="work1rdyDirYes">
<echo message="workware1 source backup and deploy." />
<tstamp>
<format property="Dstamp" pattern="yyyyMMdd" />
<format property="Tstamp" pattern="HHmm" />
</tstamp>
<property name="workware1.backup.name" value="workware1.${Dstamp}.${Tstamp}" />
<copy todir="${source.backup.dir}/${workware1.backup.name}">
<fileset dir="${work.todir1}">
<exclude name="upload/**" />
</fileset>
</copy>
<delete includeemptydirs="true">
<fileset dir="${work.todir1}">
<exclude name="upload/**" />
<include name="**/*" />
</fileset>
</delete>
<delete file="${work.base}/webapps/workware1.ready/resources/component/spreadsheet/SpreadJS/samples/VueSample/.gitignore" />
<move file="${work.base}/webapps/workware1.ready" tofile="${work.todir1}" />
</target>
<target name="deploy-work2" depends="rdy2DirCheck" if="work2rdyDirYes">
<echo message="workware2 source backup and deploy." />
<tstamp>
<format property="Dstamp" pattern="yyyyMMdd" />
<format property="Tstamp" pattern="HHmm" />
</tstamp>
<property name="workware2.backup.name" value="workware2.${Dstamp}.${Tstamp}" />
<copy todir="${source.backup.dir}/${workware2.backup.name}">
<fileset dir="${work.todir2}">
<exclude name="upload/**" />
</fileset>
</copy>
<delete includeemptydirs="true">
<fileset dir="${work.todir2}">
<exclude name="upload/**" />
<include name="**/*" />
</fileset>
</delete>
<delete file="${work.base}/webapps/workware2.ready/resources/component/spreadsheet/SpreadJS/samples/VueSample/.gitignore" />
<move file="${work.base}/webapps/workware2.ready" tofile="${work.todir2}" />
</target>
<target name="deploy" depends="work2Check">
<antcall target="deploy-work1" />
<antcall target="deploy-work2" />
</target>
<!-- MAIN SECTION =============================================================== -->
<target name="default">
<groovy>
println "Running in Groovy"
</groovy>
</target>
</project>
👉🏻 무슨 말인지 아시겠나요? 네니오
👉🏻 나도다 그치만 쫄거 없다 내가 치는 명령어는 prepare, deploy 밖에 없으니 해당 부분만 발췌해서 다시 보자
📍 prepare
👉🏻 build.xml에서 prepare를 검색해 보니 target에 prepare-work1, prepare-work2를 부르도록 설정되어 있음
target name이 곧 명령어라고 생각해도 이해가 쉬울 듯함
👉🏻 prepare-work1, 2는 비슷한 구성 방식처럼 보이므로 하나씩 뜯어보자
📍 prepare 작업 상세
👉🏻 1) echo(출력 명령어)
→ Prepare for workware1 source. 라는 명령어를 출력
👉🏻 2) mkdir(디렉토리 생성 명령어)
→ 해당 경로에 workware1.ready이라는 디렉토리를 생성
👉🏻 3) unwar(압축 해제 명령어)
→ war 파일을 workware1.ready 경로에 풂
👉🏻 4) copy(복사 명령어)
✨ todir="${work.base}/webapps/workware1.ready"
→ 복사할 대상 디렉토리, ${work.base} 변수와 함께 복사된 파일이 webapps/workware1.ready 디렉토리에 저장
✨ overwrite="true"
→ 대상 디렉토리에 동일한 파일이 존재할 경우 덮어씌움
✨ verbose="true"
→ 복사 작업의 진행 상황을 상세하게 출력
✨ preservelastmodified="true"
→ 복사된 파일의 최종 수정 시간을 원본 파일과 동일하게 유지
✨ <fileset> 태그 > dir="${work.todir1}"
→ 복사할 파일이 위치한 기본 디렉토리를 지정함, ${work.todir1} 변수가 해당 디렉토리를 나타냄
✨ <include> 태그 > name="/WEB-INF/classes/profile/development/default.properties"
→ 복사할 파일의 경로를 지정함, dir 속성에서 지정한 디렉토리를 기준으로 이 경로의 파일이 복사됨
👉🏻 기존 디렉토리 아래에 있는 fileset 파일들을 war 푼 디렉토리로 복사
만약 해당 경로에 이미 같은 파일이 있으면 덮어쓰며, 파일의 최종 수정 시간도 유지 및 복사 과정을 출력
👉🏻 같은 파일이 있으면 덮어쓴다? 그건 곧 기존 파일을 유지하겠다는 의미
📍 deploy
👉🏻 이미 내가 원하는 과정은 나왔지만 deploy도 확인해 봄
👉🏻 prepare보다 간단한 작업으로 보여 간단히 정리하자면 아래와 같음
✨ 1) echo로 메시지 출력
✨ 2) tstamp 현재 타임스탬프(시간) 확인
✨ 3) 미리 지정한 백업 경로로 기존 파일 백업(upload 하위 파일 제외)
✨ 4) 기존 파일 삭제 (uplaod 하위 파일 제외)
✨ 5) 해당 경로의 파일 삭제
✨ 6) prepare로 만들어 둔 파일을 기존 경로로 이동
📍 깃 소스로 덮어씌우지 않을 파일 추가
👉🏻 /opt/puppy/work/webapps/workware1/WEB-INF/classes/profile/dev 경로이니 아래처럼 추가해 줌
👉🏻 문제 구간을 지나 서비스가 정상 구동 확인
📍 (주의) 파일 경로: 위 방법으로 했는데도 동일한 오류가 난다면?
👉🏻 나의 경우 설정한 값이 fileset의 기준 디렉토리(dir)는 결국 "/opt/puppy/work/webapps/workware1"로 고정되어 있는 셈
풀 경로 /opt/puppy/work/webapps/workware1/WEB-INF/classes/profile/dev 중에서 고정 경로를 뺀 값만 넣어도 정상 동작함
dir 경로: /opt/puppy/work/webapps/workware1
실제 경로: /opt/puppy/work/webapps/workware1/WEB-INF/classes/profile/dev
<include name="/WEB-INF/classes/profile/dev/default.properties" />
👉🏻 동일한 경우에도 정상적으로 덮어씌워지지 않거나 fileset의 기준 디렉토리가 더 상위 디렉토리로 설정된 경우라면?
👉🏻 무적의 ** 패턴을 써 줌
(상위 디렉토리 예: /opt/puppy/work)
<fileset dir="/opt/puppy/work">
<include name="**/profile/dev/**" />
</fileset>
📍 ** 패턴
👉🏻 모든 하위 디렉토리를 탐색하겠다는 의미
(상위 디렉토리 예: /opt/puppy/work)
<fileset dir="/opt/puppy/work">
<include name="**/profile/dev/**" />
</fileset>
👉🏻 **/profile/dev/**처럼 작성하면 /opt/puppy/work 하위의 모든 디렉토리에서 profile/dev 폴더를 찾게 됨
👉🏻 WEB-INF/classes 경로를 포함한 모든 경로에서 해당 폴더를 찾고자 할 때 유용