<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Troubleshooting on Chanyeol Dev</title>
    <link>https://chanyeols.com/tags/troubleshooting/</link>
    <description>Recent content in Troubleshooting on Chanyeol Dev</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Mon, 04 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://chanyeols.com/tags/troubleshooting/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>초보자도 쉽게 따라하는 Linux Tomcat WAR 배포 튜토리얼</title>
      <link>https://chanyeols.com/posts/linux-tomcat-war-deployment-guide/</link>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/linux-tomcat-war-deployment-guide/</guid>
      <description>리눅스 서버에 Tomcat 설치부터 WAR 배포까지의 전체 과정을 단계별로 설명하는 튜토리얼. 트러블슈팅 팁 포함</description>
      <content:encoded><![CDATA[<h2 id="초보자도-쉽게-따라하는-linux-tomcat-war-배포-튜토리얼">초보자도 쉽게 따라하는 Linux Tomcat WAR 배포 튜토리얼</h2>
<p><img alt="초보자도 쉽게 따라하는 Linux Tomcat WAR 배포 튜토리얼" loading="lazy" src="/images/linux-tomcat-war-deployment-guide-body.jpg"></p>
<p>리눅스 서버에 Apache Tomcat을 설치하고 WAR 파일을 배포하는 전체 과정을 단계별로 설명합니다. 이 튜토리얼은 Ubuntu 22.04와 CentOS 8 환경에서 Java 17과 Tomcat 10.1을 기준으로 작성되었으며, 실제 동작하는 코드 예제와 트러블슈팅 팁을 포함합니다. 초보 개발자도 쉽게 따라할 수 있도록 구성되었습니다.</p>
<h2 id="1-튜토리얼-개요">1. 튜토리얼 개요</h2>
<p>Apache Tomcat은 자바 기반의 웹 애플리케이션을 호스팅하기 위한 오픈소스 서블릿 컨테이너입니다. 이 가이드에서는 Tomcat 10.1 버전을 사용하여 WAR 파일을 배포하는 방법을 다룹니다. 지원 OS는 Ubuntu 22.04와 CentOS 8이며, 두 배포판 모두에서 테스트를 완료했습니다. 전체 프로세스는 다음과 같은 단계로 구성됩니다:</p>
<ol>
<li><strong>사전 준비</strong>: Java 설치 확인, 방화벽 설정, WAR 파일 준비</li>
<li><strong>Tomcat 설치</strong>: 패키지 관리자 사용 vs 바이너리 설치 방법 비교</li>
<li><strong>서비스 관리</strong>: systemd를 이용한 서비스 등록 및 자동 실행 설정</li>
<li><strong>WAR 배포</strong>: webapps 디렉토리에 WAR 파일 복사 및 자동 배포 확인</li>
<li><strong>문제 해결</strong>: 404 에러, Java 버전 문제, 보안 정책 조정</li>
<li><strong>고급 설정</strong>: 모니터링 및 추가 학습 자료 제공</li>
</ol>
<blockquote>
<p><strong>참고</strong>: 공식 문서에서는 <a href="https://tomcat.apache.org/download-10.cgi">Tomcat 10.1 다운로드 페이지</a>에서 최신 버전을 확인할 수 있습니다. 또한 <a href="https://docs.oracle.com/en/java/javase/17/install/">Oracle Java 17 설치 가이드</a>를 참고하면 Java 설치에 도움이 됩니다.</p>
</blockquote>
<h2 id="2-사전-준비-사항">2. 사전 준비 사항</h2>
<h3 id="21-java-17-설치-확인">2.1 Java 17+ 설치 확인</h3>
<p>Tomcat 10.1은 Java 17 이상을 필수로 요구합니다. 다음 명령어로 Java 설치 상태를 확인하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>java -version
</span></span></code></pre></div><p>출력 결과가 <code>17.x.x</code>로 시작하지 않으면 Java 17을 설치해야 합니다. Ubuntu에서는 다음 명령어로 설치할 수 있습니다:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt install openjdk-17-jdk -y
</span></span></code></pre></div><p>CentOS에서는:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo yum install java-17-openjdk-devel -y
</span></span></code></pre></div><p>설치 후 <code>JAVA_HOME</code> 환경 변수를 설정해야 합니다. Ubuntu/CentOS 공통 설정 방법:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo <span style="color:#a5d6ff">&#39;export JAVA_HOME=$(readlink -f /usr/bin/java | sed &#34;s:/bin/java::&#34;)&#39;</span> &gt;&gt; ~/.bashrc
</span></span><span style="display:flex;"><span>echo <span style="color:#a5d6ff">&#39;export PATH=$JAVA_HOME/bin:$PATH&#39;</span> &gt;&gt; ~/.bashrc
</span></span><span style="display:flex;"><span>source ~/.bashrc
</span></span></code></pre></div><h3 id="22-방화벽-설정">2.2 방화벽 설정</h3>
<p>Tomcat 기본 포트는 8080입니다. SSH(22)와 Tomcat(8080) 포트를 열어주세요:</p>
<ul>
<li>
<p><strong>Ubuntu</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw allow 22/tcp
</span></span><span style="display:flex;"><span>sudo ufw allow 8080/tcp
</span></span></code></pre></div></li>
<li>
<p><strong>CentOS</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo firewall-cmd --permanent --add-port<span style="color:#ff7b72;font-weight:bold">=</span>22/tcp
</span></span><span style="display:flex;"><span>sudo firewall-cmd --permanent --add-port<span style="color:#ff7b72;font-weight:bold">=</span>8080/tcp
</span></span><span style="display:flex;"><span>sudo firewall-cmd --reload
</span></span></code></pre></div></li>
</ul>
<h3 id="23-war-파일-준비">2.3 WAR 파일 준비</h3>
<p>샘플 WAR 파일이 없다면 Spring Boot 프로젝트를 생성해 테스트용 WAR를 만들 수 있습니다. 다음 <code>pom.xml</code>을 사용하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#7ee787">&lt;project</span> xmlns=<span style="color:#a5d6ff">&#34;http://maven.apache.org/POM/4.0.0&#34;</span><span style="color:#7ee787">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;modelVersion&gt;</span>4.0.0<span style="color:#7ee787">&lt;/modelVersion&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;groupId&gt;</span>com.example<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;artifactId&gt;</span>demo<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;version&gt;</span>0.0.1-SNAPSHOT<span style="color:#7ee787">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;packaging&gt;</span>war<span style="color:#7ee787">&lt;/packaging&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;parent&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;groupId&gt;</span>org.springframework.boot<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;artifactId&gt;</span>spring-boot-starter-parent<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;version&gt;</span>3.1.5<span style="color:#7ee787">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;relativePath/&gt;</span> <span style="color:#8b949e;font-style:italic">&lt;!-- lookup parent from repository --&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;/parent&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;dependencies&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;groupId&gt;</span>org.springframework.boot<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;artifactId&gt;</span>spring-boot-starter-web<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;exclusions&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&lt;exclusion&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#7ee787">&lt;groupId&gt;</span>org.springframework.boot<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#7ee787">&lt;artifactId&gt;</span>spring-boot-starter-tomcat<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&lt;/exclusion&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;/exclusions&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;groupId&gt;</span>org.springframework.boot<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;artifactId&gt;</span>spring-boot-starter-tomcat<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;scope&gt;</span>provided<span style="color:#7ee787">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;groupId&gt;</span>jakarta.servlet<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;artifactId&gt;</span>jakarta.servlet-api<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;version&gt;</span>6.0.0<span style="color:#7ee787">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;scope&gt;</span>provided<span style="color:#7ee787">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;/dependency&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;/dependencies&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;build&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;plugins&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;plugin&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&lt;groupId&gt;</span>org.springframework.boot<span style="color:#7ee787">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&lt;artifactId&gt;</span>spring-boot-maven-plugin<span style="color:#7ee787">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7ee787">&lt;/plugin&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&lt;/plugins&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&lt;/build&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#7ee787">&lt;/project&gt;</span>
</span></span></code></pre></div><p>그리고 간단한 컨트롤러 클래스를 작성하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff7b72">package</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">com.example.demo</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">org.springframework.boot.web.servlet.support.SpringBootServletInitializer</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">org.springframework.boot.builder.SpringApplicationBuilder</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">org.springframework.stereotype.Component</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">org.springframework.web.bind.annotation.*</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">@RestController</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">public</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">class</span> <span style="color:#f0883e;font-weight:bold">HelloController</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">extends</span><span style="color:#6e7681"> </span>SpringBootServletInitializer<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#d2a8ff;font-weight:bold">@Override</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">protected</span><span style="color:#6e7681"> </span>SpringApplicationBuilder<span style="color:#6e7681"> </span><span style="color:#d2a8ff;font-weight:bold">configure</span>(SpringApplicationBuilder<span style="color:#6e7681"> </span>application)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#ff7b72">return</span><span style="color:#6e7681"> </span>application.sources(HelloController.class);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#d2a8ff;font-weight:bold">@GetMapping</span>(<span style="color:#a5d6ff">&#34;/&#34;</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">public</span><span style="color:#6e7681"> </span>String<span style="color:#6e7681"> </span><span style="color:#d2a8ff;font-weight:bold">home</span>()<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#ff7b72">return</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#34;Hello from Tomcat!&#34;</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>}<span style="color:#6e7681">
</span></span></span></code></pre></div><p>Maven으로 WAR를 빌드하려면:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mvn clean package
</span></span></code></pre></div><p>생성된 WAR 파일은 <code>target/demo-0.0.1-SNAPSHOT.war</code>에 위치합니다.</p>
<h2 id="3-단계별-설치-및-배포">3. 단계별 설치 및 배포</h2>
<h3 id="31-tomcat-설치-방법-비교">3.1 Tomcat 설치 방법 비교</h3>
<p>Tomcat 설치에는 두 가지 주요 방법이 있습니다:</p>
<ol>
<li>
<p><strong>패키지 관리자 사용 (apt/yum)</strong></p>
<ul>
<li>장점: 시스템 통합이 용이</li>
<li>단점: 최신 버전 지원이 느림</li>
<li>Ubuntu:
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install tomcat10 -y
</span></span></code></pre></div></li>
<li>CentOS:
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo yum install tomcat -y
</span></span></code></pre></div></li>
</ul>
</li>
<li>
<p><strong>바이너리 설치 (권장)</strong></p>
<ul>
<li>장점: 최신 버전 설치 가능</li>
<li>단점: 수동 설정 필요</li>
<li>공식 사이트에서 압축 해제:
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd /opt
</span></span><span style="display:flex;"><span>sudo wget https://downloads.apache.org/tomcat/tomcat-10/v10.1.12/bin/apache-tomcat-10.1.12.tar.gz
</span></span><span style="display:flex;"><span>sudo tar -xzvf apache-tomcat-10.1.12.tar.gz
</span></span><span style="display:flex;"><span>sudo ln -s apache-tomcat-10.1.12 tomcat
</span></span></code></pre></div></li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>팁</strong>: 프로덕션 환경에서는 바이너리 설치가 권장됩니다. 패키지 관리자는 때로는 오래된 버전을 포함할 수 있기 때문입니다.</p>
</blockquote>
<h3 id="32-systemd-서비스-등록">3.2 systemd 서비스 등록</h3>
<p>Tomcat을 백그라운드 서비스로 실행하려면 systemd를 설정해야 합니다. <code>/etc/systemd/system/tomcat.service</code> 파일을 생성하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#ff7b72">[Unit]</span>
</span></span><span style="display:flex;"><span>Description<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">Apache Tomcat 10 Web Application Container</span>
</span></span><span style="display:flex;"><span>After<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">network.target</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">[Service]</span>
</span></span><span style="display:flex;"><span>Type<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">forking</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Environment<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">CATALINA_PID=/opt/tomcat/temp/tomcat.pid</span>
</span></span><span style="display:flex;"><span>Environment<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">CATALINA_HOME=/opt/tomcat</span>
</span></span><span style="display:flex;"><span>Environment<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">CATALINA_BASE=/opt/tomcat</span>
</span></span><span style="display:flex;"><span>Environment<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;JAVA_HOME=/usr/lib/jvm/java-17-openjdk&#39;</span>
</span></span><span style="display:flex;"><span>Environment<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;JAVA_OPTS=-Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ExecStart<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">/opt/tomcat/bin/startup.sh</span>
</span></span><span style="display:flex;"><span>ExecStop<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">/opt/tomcat/bin/shutdown.sh</span>
</span></span><span style="display:flex;"><span>ExecReload<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">/opt/tomcat/bin/shutdown.sh ; /opt/tomcat/bin/startup.sh</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>User<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">tomcat</span>
</span></span><span style="display:flex;"><span>Group<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">tomcat</span>
</span></span><span style="display:flex;"><span>UMask<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0007</span>
</span></span><span style="display:flex;"><span>RestartSec<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">10</span>
</span></span><span style="display:flex;"><span>Restart<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">always</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">[Install]</span>
</span></span><span style="display:flex;"><span>WantedBy<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">multi-user.target</span>
</span></span></code></pre></div><p>서비스 활성화 및 시작:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl daemon-reload
</span></span><span style="display:flex;"><span>sudo systemctl enable tomcat
</span></span><span style="display:flex;"><span>sudo systemctl start tomcat
</span></span></code></pre></div><h3 id="33-war-파일-배포">3.3 WAR 파일 배포</h3>
<p>WAR 파일은 Tomcat의 <code>webapps</code> 디렉토리에 복사하면 자동으로 배포됩니다:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cp demo-0.0.1-SNAPSHOT.war /opt/tomcat/webapps/
</span></span></code></pre></div><p>배포 상태를 확인하려면 로그 파일을 확인하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat /opt/tomcat/logs/catalina.out
</span></span></code></pre></div><p>브라우저에서 <code>http://[서버IP]:8080/demo</code>로 접속하여 &ldquo;Hello from Tomcat!&rdquo; 메시지가 표시되는지 확인하세요.</p>
<h2 id="4-트러블슈팅">4. 트러블슈팅</h2>
<h3 id="41-404-에러-해결">4.1 404 에러 해결</h3>
<p>WAR 파일이 배포되었지만 404 에러가 발생한다면 다음을 확인하세요:</p>
<ol>
<li>
<p><strong>로그 확인</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tail -f /opt/tomcat/logs/catalina.out
</span></span></code></pre></div><p>에러 메시지가 있는지 확인합니다.</p>
</li>
<li>
<p><strong>컨텍스트 패스 확인</strong>: WAR 파일 이름이 <code>demo.war</code>라면 접근 주소는 <code>http://[서버IP]:8080/demo</code>입니다.</p>
</li>
<li>
<p><strong>서버 재시작</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl restart tomcat
</span></span></code></pre></div></li>
</ol>
<h3 id="42-java-버전-불일치-문제">4.2 Java 버전 불일치 문제</h3>
<p>Tomcat 10.1은 Java 8 이상을 지원하지만, 최신 기능을 사용하려면 Java 17이 필요합니다. 다음 명령어로 사용 중인 Java 버전을 확인하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>/opt/tomcat/bin/version.sh
</span></span></code></pre></div><p>만약 잘못된 Java 버전이 사용된다면 <code>CATALINA_OPTS</code>에 Java 경로를 명시적으로 지정하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export <span style="color:#79c0ff">CATALINA_OPTS</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">$CATALINA_OPTS</span><span style="color:#a5d6ff"> -Djava.home=/usr/lib/jvm/java-17-openjdk&#34;</span>
</span></span></code></pre></div><h3 id="43-selinuxapparmor-보안-정책">4.3 SELinux/AppArmor 보안 정책</h3>
<p>SELinux나 AppArmor가 활성화된 환경에서는 Tomcat이 파일에 접근하지 못할 수 있습니다. Ubuntu에서는 AppArmor를 일시적으로 비활성화해 테스트할 수 있습니다:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl stop apparmor
</span></span></code></pre></div><p>CentOS에서 SELinux 모드 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>getenforce
</span></span></code></pre></div><p>SELinux를 일시적으로 Permissive 모드로 전환:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo setenforce <span style="color:#a5d6ff">0</span>
</span></span></code></pre></div><p>영구적으로 비활성화하려면 <code>/etc/selinux/config</code> 파일을 편집하세요:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span>SELINUX<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">disabled</span>
</span></span></code></pre></div><h2 id="5-마치며">5. 마치며</h2>
<p>이 튜토리얼에서는 Linux 서버에 Tomcat을 설치하고 WAR 파일을 배포하는 전체 과정을 다뤘습니다. 핵심 내용을 요약하면 다음과 같습니다:</p>
<ol>
<li>Tomcat 10.1 설치에는 바이너리 방식이 최신 버전 사용에 유리합니다.</li>
<li>WAR 파일은 webapps 디렉토리에 복사만으로 자동 배포됩니다.</li>
<li>트러블슈팅 시 로그 파일과 보안 정책을 반드시 확인해야 합니다.</li>
</ol>
<p>추가로 학습할 자료:</p>
<ul>
<li><a href="https://tomcat.apache.org/tomcat-10.0-doc/index.html">Tomcat 공식 문서</a></li>
<li><a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#building-an-executable-war-file">Spring Boot WAR 배포 가이드</a></li>
</ul>
<p>모니터링 시스템 구축을 원한다면 JMXTrans와 Grafana를 연동하는 방법을 고려해보세요. 이는 서버 리소스 사용량과 애플리케이션 성능을 실시간으로 모니터링할 수 있게 해줍니다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>SpringBoot &#43; GitHub Actions CI/CD 완벽 구성 &amp; 문제 해결</title>
      <link>https://chanyeols.com/posts/springboot-gh-actions-cicd-troubleshooting-guide/</link>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/springboot-gh-actions-cicd-troubleshooting-guide/</guid>
      <description>SpringBoot 프로젝트에 GitHub Actions CI/CD 파이프라인을 구성하고, 빌드/배포 과정에서 발생하는 문제를 해결하는 단계별 가이드입니다. JDK 설정부터 Docker 이미지 푸시, 트러블슈팅 기법까지 실전 예제를 포함합니다.</description>
      <content:encoded><![CDATA[<h2 id="springboot--github-actions-cicd-완벽-구성--문제-해결">SpringBoot + GitHub Actions CI/CD 완벽 구성 &amp; 문제 해결</h2>
<p>SpringBoot 애플리케이션에 GitHub Actions를 활용한 CI/CD 파이프라인을 구축하고, 실제 운영 환경에서 발생하는 문제를 해결하는 방법을 단계별로 설명합니다. 이 가이드는 JDK 설치부터 클라우드 배포까지 전체 워크플로우를 다루며, 실패 사례별 트러블슈팅 기법을 포함합니다.</p>
<h2 id="튜토리얼-springboot-cicd-기본-구성">튜토리얼: SpringBoot CI/CD 기본 구성</h2>
<h3 id="1-github-리포지토리-연결-및-기본-workflow-생성">1. GitHub 리포지토리 연결 및 기본 Workflow 생성</h3>
<ol>
<li>SpringBoot 프로젝트를 GitHub에 업로드합니다.
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git init
</span></span><span style="display:flex;"><span>git add .
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#a5d6ff">&#34;Initial commit&#34;</span>
</span></span><span style="display:flex;"><span>git branch -M main
</span></span><span style="display:flex;"><span>git remote add origin https://github.com/&lt;your-id&gt;/&lt;repo-name&gt;.git
</span></span><span style="display:flex;"><span>git push -u origin main
</span></span></code></pre></div></li>
<li><code>.github/workflows/springboot-ci-cd.yml</code> 파일을 생성합니다.</li>
</ol>
<h3 id="2-기본-workflow-yaml-구성">2. 기본 Workflow YAML 구성</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">SpringBoot CI/CD</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">on</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">push</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">branches</span>:<span style="color:#6e7681"> </span>[<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#34;main&#34;</span><span style="color:#6e7681"> </span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">pull_request</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">branches</span>:<span style="color:#6e7681"> </span>[<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#34;main&#34;</span><span style="color:#6e7681"> </span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">jobs</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">build</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">runs-on</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">ubuntu-latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">steps</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">actions/checkout@v4</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Set up JDK 17</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">actions/setup-java@v4</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">with</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">java-version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;17&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">distribution</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;temurin&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Build with Gradle</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">./gradlew build</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Build Docker image</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        docker build -t my-springboot-app:latest .
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        docker tag my-springboot-app:latest \
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">          $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/my-springboot-app:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Push to ECR</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        aws ecr get-login-password --region $AWS_REGION | \
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">          docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/my-springboot-app:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">env</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">AWS_ACCOUNT_ID</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${{ secrets.AWS_ACCOUNT_ID }}</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">AWS_REGION</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${{ secrets.AWS_REGION }}</span><span style="color:#6e7681">
</span></span></span></code></pre></div><blockquote>
<p><strong>팁</strong>: Maven 사용 시 <code>./mvnw clean package</code>로 변경합니다. Dockerfile이 프로젝트 루트에 위치해야 합니다.</p>
</blockquote>
<h3 id="3-github-secrets-설정">3. GitHub Secrets 설정</h3>
<ul>
<li>AWS 계정의 <code>AWS_ACCOUNT_ID</code>와 <code>AWS_REGION</code>을 Secrets에 등록</li>
<li>Azure App Service 사용 시 <code>AZURE_WEBAPP_NAME</code>과 <code>AZURE_CREDENTIALS</code> 추가</li>
</ul>
<h2 id="트러블슈팅-cicd-실패-주요-사례">트러블슈팅: CI/CD 실패 주요 사례</h2>
<h3 id="1-빌드-실패-사례">1. 빌드 실패 사례</h3>
<ul>
<li><strong>Java 버전 불일치</strong>:
<pre tabindex="0"><code>Error: Java version &#34;1.8.0_292&#34; is not supported by Gradle
</code></pre>→ <code>setup-java</code> 액션의 <code>java-version</code>을 프로젝트 요구사항에 맞춰 수정</li>
<li><strong>Gradle wrapper 미포함</strong>:
<pre tabindex="0"><code>/bin/sh: ./gradlew: No such file or directory
</code></pre>→ <code>gradlew</code> 바이너리 포함 여부 확인 (Gradle wrapper 사용 필수)</li>
<li><strong>의존성 다운로드 실패</strong>:
<pre tabindex="0"><code>Could not transfer artifact com.example:library:jar:1.0.0
</code></pre>→ 방화벽 설정 확인 또는 <code>--no-daemon</code> 플래그 추가</li>
</ul>
<h3 id="2-배포-실패-사례">2. 배포 실패 사례</h3>
<ul>
<li><strong>IAM 권한 문제</strong>:
<pre tabindex="0"><code>User is not authorized to perform ecr:PutImage
</code></pre>→ AWS ECR 접근 권한이 있는 IAM 역할 생성 후 Secrets에 자격 증명 등록</li>
<li><strong>Docker 태그 충돌</strong>:
<pre tabindex="0"><code>manifest for my-springboot-app:latest not found
</code></pre>→ <code>docker pull</code>로 기존 이미지 삭제 후 재시도</li>
<li><strong>Helm chart 오류</strong>:
<pre tabindex="0"><code>failed: Invalid value: : invalid type for io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
</code></pre>→ Helm 차트 버전 호환성 확인 및 <code>helm dependency update</code> 실행</li>
</ul>
<h3 id="3-캐시-미적용-문제">3. 캐시 미적용 문제</h3>
<pre tabindex="0"><code>GET https://jcenter.bintray.com/...
</code></pre><p>→ Gradle/Maven 의존성 캐시 추가:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Cache Gradle packages</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">actions/cache@v3</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">with</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">path</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">~/.gradle/caches</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">key</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;) }}</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">restore-keys</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">      ${{ runner.os }}-gradle-</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h2 id="문제-해결-단계별-검증-방법">문제 해결: 단계별 검증 방법</h2>
<h3 id="1-로컬에서-workflow-테스트">1. 로컬에서 Workflow 테스트</h3>
<p><code>act</code> CLI로 로컬에서 워크플로우를 시뮬레이션합니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install nektos/act/act
</span></span><span style="display:flex;"><span>act --pull-request --pull-request-head-sha<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#ff7b72">$(</span>git rev-parse HEAD<span style="color:#ff7b72">)</span>
</span></span></code></pre></div><h3 id="2-디버그-로그-활성화">2. 디버그 로그 활성화</h3>
<p>워크플로우 단계에 <code>set -x</code> 추가:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Debug Docker build</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    set -x
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    docker build -t my-springboot-app:latest .</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="3-self-hosted-runner-로그-분석">3. Self-hosted Runner 로그 분석</h3>
<p>SSH로 러너 서버에 접속해 직접 로그 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>journalctl -u github-actions-runner.service -f
</span></span></code></pre></div><h3 id="4-품질-검증-도구-연동">4. 품질 검증 도구 연동</h3>
<ul>
<li><strong>JUnit 테스트 결과</strong>: <code>gradle test jacocoTestReport</code> 추가</li>
<li><strong>SonarQube 분석</strong>:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Run SonarQube Scanner</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">SonarSource/sonarqube-scan-action@master</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">with</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">projectKey</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">my-springboot-project</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">projectName</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">SpringBoot Demo</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h2 id="참고-문서">참고 문서</h2>
<ul>
<li><a href="https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven">GitHub Actions Java with Gradle 문서</a></li>
<li><a href="https://docs.aws.amazon.com/AmazonECR/latest/developerguide/docker-push-ecr-image.html">AWS ECR Docker 푸시 가이드</a></li>
</ul>
<blockquote>
<p><strong>주의사항</strong>: 프로덕션 환경에서는 <code>main</code> 브랜치 외에 <code>develop</code> 브랜치에 대한 별도 워크플로우를 구성하는 것이 안전합니다. 이미지 태그 전략으로는 <code>SHA</code> 기반 태그를 권장합니다.</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>CodePipeline 실패 해결법: CI/CD 파이프라인 문제 분석부터 복구까지</title>
      <link>https://chanyeols.com/posts/aws-codepipeline-error-handling-guide/</link>
      <pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/aws-codepipeline-error-handling-guide/</guid>
      <description>aws-codepipeline-error-handling-guide-for-ci-cd-pipeline-failure-analysis-and-solutions</description>
      <content:encoded><![CDATA[<h2 id="aws-codepipeline-실패-해결법-cicd-파이프라인-문제-분석부터-복구까지">AWS CodePipeline 실패 해결법: CI/CD 파이프라인 문제 분석부터 복구까지</h2>
<p><img alt="CodePipeline 실패 해결법: CI/CD 파이프라인 문제 분석부터 복구까지" loading="lazy" src="https://pixabay.com/get/gfb2bb5ab925ed6495973d8f9b1c5b98d884d4231bce43ede872c59ce3175521019502b8e5f35658a3bcb10094ae807a4d6faa79c01c99a5a53dbf1a08b4eb39f_1280.png"></p>
<p>AWS CodePipeline은 서버리스 CI/CD 서비스로, 코드 변경부터 배포까지 자동화된 워크플로우를 제공합니다. 하지만 복잡한 인프라 환경에서 <strong>권한 오류</strong>, <strong>아티팩트 누락</strong>, <strong>빌드 스크립트 실패</strong> 등 다양한 문제가 발생할 수 있습니다. 이 가이드에서는 CodePipeline의 기본 구성부터 모니터링, 트러블슈팅, 예방 조치까지 단계별로 설명합니다. 실제 동작하는 코드 예제와 AWS 공식 문서를 참고해 문제를 체계적으로 해결하는 방법을 제시합니다.</p>
<hr>
<h2 id="1-튜토리얼-codepipeline-기본-구성-및-모니터링">1. 튜토리얼: CodePipeline 기본 구성 및 모니터링</h2>
<h3 id="11-codepipeline-구조-이해">1.1 CodePipeline 구조 이해</h3>
<p>CodePipeline은 <strong>Source → Build → Deploy</strong> 3단계로 구성됩니다. 각 단계는 독립적인 액션으로 관리되며, 실패 시 롤백이 가능합니다. 예를 들어, GitHub에서 코드를 가져온 후 CodeBuild로 빌드하고, CodeDeploy로 EC2에 배포하는 파이프라인을 구성할 수 있습니다.</p>
<blockquote>
<p><strong>팁</strong>: 파이프라인 설계 시 각 단계의 출력 아티팩트가 다음 단계로 전달되는지 확인해야 합니다. S3 버킷 권한 문제로 아티팩트가 누락되면 파이프라인이 중단됩니다.</p>
</blockquote>
<h3 id="12-cloudwatch-로그-연동-방법">1.2 CloudWatch 로그 연동 방법</h3>
<p>CodePipeline 자체의 로그는 제한적이므로, 각 단계에서 CloudWatch Logs를 활성화해야 합니다. 예를 들어 CodeBuild 프로젝트의 로그 그룹을 생성하려면 다음 AWS CLI 명령어를 사용합니다:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># CodeBuild 로그 그룹 생성</span>
</span></span><span style="display:flex;"><span>aws logs create-log-group <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --log-group-name /aws/codebuild/your-project-name <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --retention-in-days <span style="color:#a5d6ff">14</span>
</span></span></code></pre></div><h3 id="13-파이프라인-상태-확인">1.3 파이프라인 상태 확인</h3>
<ul>
<li><strong>AWS Management Console</strong>: CodePipeline 대시보드에서 각 액션의 상태(Success/Failed)를 확인할 수 있습니다. 실패 시 상세 오류 메시지를 클릭하면 원인(예: <code>IAM 역할 권한 부족</code>)이 표시됩니다.</li>
<li><strong>AWS CLI</strong>: 다음 명령어로 파이프라인 실행 기록을 조회할 수 있습니다:
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"></code></pre></div></li>
</ul>
<p>aws codepipeline get-pipeline-execution-history <br>
&ndash;pipeline-name my-pipeline <br>
&ndash;max-results 10</p>
<pre tabindex="0"><code>
&gt; **주의**: CLI 사용 시 `AWSCodePipelineRole` 정책이 IAM 사용자/역할에 부여되어 있어야 합니다.

---

## 2. 트러블슈팅: 주요 오류 유형 및 해결 전략

### 2.1 권한 오류 (IAM Role/Policy)
**증상**: `User is not authorized to perform` 오류 발생. **원인**: IAM 역할이나 정책이 특정 AWS 서비스(예: S3, CodeBuild) 접근 권한을 누락한 경우.

**해결 방법**:
1. IAM 콘솔에서 `CodePipeline-Service-Role` 또는 `CodeBuild-Service-Role`을 선택합니다.
2. `AWSCodePipelineRole` 및 `AWSCodeBuildServiceRole` 정책이 연결되었는지 확인합니다.
3. S3 버킷 접근 권한이 필요한 경우 `AmazonS3ReadOnlyAccess`를 추가합니다.

```json
// IAM Policy 예시: S3 접근 권한 추가
{
&#34;Version&#34;: &#34;2012-10-17&#34;,
&#34;Statement&#34;: [
  {
    &#34;Effect&#34;: &#34;Allow&#34;,
    &#34;Action&#34;: [
      &#34;s3:GetObject&#34;,
      &#34;s3:PutObject&#34;
    ],
    &#34;Resource&#34;: &#34;arn:aws:s3:::your-bucket-name/*&#34;
  }
]
}
</code></pre><h3 id="22-아티팩트-누락-s3-버킷-권한">2.2 아티팩트 누락 (S3 버킷 권한)</h3>
<p><strong>증상</strong>: <code>Artifact not found</code> 오류 발생. <strong>원인</strong>: S3 버킷 정책 또는 CodePipeline의 아티팩트 스토어 설정이 잘못된 경우.</p>
<p><strong>해결 방법</strong>:</p>
<ol>
<li>S3 버킷 정책에서 CodePipeline 서비스 프린시팔(<code>codepipeline.amazonaws.com</code>)에 <code>s3:GetObject</code> 및 <code>s3:PutObject</code> 권한을 부여합니다.</li>
<li>CodePipeline 콘솔에서 아티팩트 스토어 위치를 확인하고, 필요시 버킷을 변경합니다.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># CloudFormation 템플릿 예시: S3 버킷 정책</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">Resources</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">PipelineBucketPolicy</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">Type</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">AWS::S3::BucketPolicy</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">Properties</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">Bucket</span>:<span style="color:#6e7681"> </span>!<span style="color:#a5d6ff">Ref PipelineBucket</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">PolicyDocument</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">Statement</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">          </span>- <span style="color:#7ee787">Effect</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Allow</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">            </span><span style="color:#7ee787">Principal</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span><span style="color:#7ee787">Service</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">codepipeline.amazonaws.com</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">            </span><span style="color:#7ee787">Action</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span>- <span style="color:#a5d6ff">s3:GetObject</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span>- <span style="color:#a5d6ff">s3:PutObject</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">            </span><span style="color:#7ee787">Resource</span>:<span style="color:#6e7681"> </span>!<span style="color:#a5d6ff">Join [&#39;&#39;, [!GetAtt PipelineBucket.Arn, &#39;/*&#39;]]</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="23-빌드-스크립트-오류">2.3 빌드 스크립트 오류</h3>
<p><strong>증상</strong>: CodeBuild 단계에서 <code>Command failed</code> 오류 발생. <strong>원인</strong>: 빌드 스크립트(예: <code>buildspec.yml</code>)의 문법 오류 또는 의존성 누락.</p>
<p><strong>해결 방법</strong>:</p>
<ol>
<li><code>buildspec.yml</code> 파일을 로컬에서 테스트합니다.</li>
<li>CodeBuild 로그에서 실패한 단계를 확인합니다.</li>
<li>필요한 경우 <code>pip install</code> 또는 <code>npm install</code> 명령어를 추가합니다.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># buildspec.yml 예시</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">0.2</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">phases</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">install</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">runtime-versions</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">python</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">3.9</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">commands</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">pip install -r requirements.txt</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">build</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">commands</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">python app.py test</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">artifacts</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">files</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#a5d6ff">dist/*</span><span style="color:#6e7681">
</span></span></span></code></pre></div><hr>
<h2 id="3-검증-및-예방-조치">3. 검증 및 예방 조치</h2>
<h3 id="31-테스트-파이프라인-구축">3.1 테스트 파이프라인 구축</h3>
<p>실제 파이프라인을 변경하기 전, <strong>테스트 브랜치</strong>를 사용해 검증 환경을 구축합니다. 예를 들어, <code>dev</code> 브랜치에서 파이프라인을 실행한 후 성공 시 <code>main</code> 브랜치로 병합하는 전략을 적용할 수 있습니다.</p>
<h3 id="32-sns-연동으로-실시간-알림-설정">3.2 SNS 연동으로 실시간 알림 설정</h3>
<p>파이프라인 실패 시 Slack 또는 이메일로 알림을 받으려면 SNS 토픽을 연동합니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># SNS 토픽 생성 및 CodePipeline에 연결</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">SNS_TOPIC_ARN</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#ff7b72">$(</span>aws sns create-topic --name codepipeline-alerts <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --query <span style="color:#a5d6ff">&#39;TopicArn&#39;</span> --output text<span style="color:#ff7b72">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>aws codepipeline update-pipeline <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --pipeline-name my-pipeline <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --pipeline-configuration <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#f85149">&#39;</span><span style="color:#ff7b72;font-weight:bold">{</span>
</span></span><span style="display:flex;"><span>      
</span></span></code></pre></div>]]></content:encoded>
    </item>
  </channel>
</rss>
