<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Good coder</title>
    <link>https://bob-data.tistory.com/</link>
    <description>어차피 할거 즐기자</description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 10:21:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>coder_</managingEditor>
    <image>
      <title>Good coder</title>
      <url>https://tistory1.daumcdn.net/tistory/4347205/attach/c94cecf9cf8b44d999eee3a11ab1922d</url>
      <link>https://bob-data.tistory.com</link>
    </image>
    <item>
      <title>[APM-AWS-3] PHP를 통한 웹서버 구축 (로그인/홈페이지 접속) - 코드포함</title>
      <link>https://bob-data.tistory.com/56</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;1192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bteNE5/dJMb9MplWi6/nKqaOaM4C3JK7cNdTzhxwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bteNE5/dJMb9MplWi6/nKqaOaM4C3JK7cNdTzhxwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bteNE5/dJMb9MplWi6/nKqaOaM4C3JK7cNdTzhxwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbteNE5%2FdJMb9MplWi6%2FnKqaOaM4C3JK7cNdTzhxwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1862&quot; height=&quot;1192&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;1192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM 구축이 완료되었으므로 html, php 를 활용하여 페이지를 만들어보려고 합니다. MySQL 에는 users, users_pw 테이블을 생성하여 id 와 pw 를 따로 분리하여 저장하도록 구성했습니다. pw 는 password_hash 를 사용하여 만들었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 구조&lt;/h2&gt;
&lt;pre id=&quot;code_1760943913491&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/var/www/html/
├── assets/                  # 정적 리소스
│   ├── img/                 # 이미지 파일
│   └── style.css            # 전역 스타일시트
│
├── inc/                     # 공통 include 파일 (모듈)
│   ├── auth.php             # 로그인 세션 체크 및 접근 제어
│   ├── config.php           # DB 환경 설정 (비공개)
│   ├── db.php               # PDO 연결용 DB 핸들러
│   ├── header.php           # 공통 헤더 (HTML head, nav 등)
│   └── footer.php           # 공통 푸터
│
└── public/                  # 실제 접근 가능한 웹 루트
    ├── index.php            # 메인 페이지 (로그인 후 접근)
    ├── login.php            # 로그인 폼 및 처리
    └── logout.php           # 로그아웃 처리&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소스코드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;config.php (db 정보)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;db 정보를 저장할 변수 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760944698849&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
define('DB_NAME', 'apmdb');
define('DB_USER', 'apmdb');
define('DB_PASS', 'password');
define('DB_DSN', 'mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=' . DB_NAME . ';charset=utf8mb4');&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;db.php (db 연결)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;config.php 에서 db 정보 불러옴&lt;/li&gt;
&lt;li&gt;new PDO 클래스를 생성하여 MtSQL 서버와 연결&lt;/li&gt;
&lt;li&gt;Options
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ERRMODE_EXCEPTION : SQL 에러가 나면 예외(throw) 발생 -&amp;gt; 에러 디버깅&lt;/li&gt;
&lt;li&gt;FETCH_ASSOC : fetch() 시 결과를 연관 배열로 반환 -&amp;gt; $row['userid'] 형태&lt;/li&gt;
&lt;li&gt;EMULATE_PREPARES =&amp;gt; false : MySQL 의 실제 prepared statement 사용 -&amp;gt; SQL 인젝션 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760944738205&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
require_once __DIR__ . '/config.php';

try {
    $pdo = new PDO(DB_DSN, DB_USER, DB_PASS, [
        PDO::ATTR_ERRMODE =&amp;gt; PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE =&amp;gt; PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES =&amp;gt; false,
    ]);
} catch (PDOException $e) {
    error_log('DB Connection failed: ' . $e-&amp;gt;getMessage());
    http_response_code(500);
    exit('Internal Server Error');
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;login.php (로그인 로직 및 페이지 html)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직 : post 요청으로 id/pw 전송 -&amp;gt; users 테이블에 계정 존재 여부 확인 -&amp;gt; db에 저장된 pw 와 입력한 pw 를 password_verify 를 사용하여 비교 -&amp;gt; 맞으면 session 시작 및 저장 -&amp;gt; 틀리면 error 에 저장 후 로그인 페이지에서 에러문구 표기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;db 와 연결&lt;/li&gt;
&lt;li&gt;session_start() 로 세션 관리&lt;/li&gt;
&lt;li&gt;$_SESSION 에 userid 저장&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760944066310&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/db.php';

if($_SERVER['REQUEST_METHOD'] === 'POST') {
        $id = $_POST['id'] ?? '';
        $pw = $_POST['pw'] ?? '';
        $stmt = $pdo-&amp;gt;prepare('SELECT * FROM users WHERE userid = ? LIMIT 1');
        $stmt-&amp;gt;execute([$id]);
        $user = $stmt-&amp;gt;fetch();

	if ($user) {
		$stmt = $pdo-&amp;gt;prepare('SELECT password_hash FROM users_pw WHERE userid = ? LIMIT 1');
        	$stmt-&amp;gt;execute([$id]);
		$pwRow = $stmt-&amp;gt;fetch();

		if ($pwRow &amp;amp;&amp;amp; password_verify($pw, $pwRow['password_hash'])) {
			$_SESSION['user'] = $id;
			header('Location: /');
			exit;
		} else {
			$error = '아이디 또는 비밀번호가 잘못되었습니다';
		}
	} else {
                $error = '아이디 또는 비밀번호가 잘못되었습니다';
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Index.php (로그인 후 접속하는 홈페이지)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;auth.php 에서 생성한 require_login() 함수를 통해 세션 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760944301942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
require_once __DIR__ . '/../inc/auth.php';
require_login();  // 로그인 안 된 상태면 login.php로 리다이렉트
include __DIR__ . '/../inc/header.php';
?&amp;gt;
&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;&amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;&amp;lt;title&amp;gt;메인 페이지&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;환영합니다, &amp;lt;?=htmlspecialchars($_SESSION['user'])?&amp;gt;님!&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;auth.php (세션 체크)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;session_status() 를 통해 상태 체크
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #e5edff; color: #001d35; text-align: start;&quot;&gt;PHP_SESSION_DISABLED&lt;/span&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt; &lt;/span&gt;: 세션이 비활성화된 경우&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #e5edff; color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #e5edff; color: #001d35; text-align: start;&quot;&gt;PHP_SESSION_NONE&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt; : 세션이 존재하지만 비활성 상태인 경우. 즉 세션이 있지만 session_start() 가 호출되지 않은 상태&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #e5edff; color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #e5edff; color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #e5edff; color: #001d35; text-align: start;&quot;&gt;PHP_SESSION_ACTIVE&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt; : 세션이 활성 상태인 경우&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt;&lt;span style=&quot;color: #001d35; text-align: start;&quot;&gt;$_SESSION['user'] 가 비어 있으면 login.php 페이지로 이동&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760944380734&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

function require_login() {
    if (empty($_SESSION['user'])) {
        header('Location: login.php');
        exit;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>모의해킹</category>
      <category>Home</category>
      <category>login</category>
      <category>mysql</category>
      <category>password_hash</category>
      <category>pdo</category>
      <category>php</category>
      <category>session</category>
      <category>모의해킹</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/56</guid>
      <comments>https://bob-data.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 20 Oct 2025 16:30:46 +0900</pubDate>
    </item>
    <item>
      <title>[APM-AWS-2] MySQL 설치 / Lightsail (Ubuntu)</title>
      <link>https://bob-data.tistory.com/55</link>
      <description>&lt;figure id=&quot;og_1760932364771&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[APM-1] AWS Lightsail 에 Ubuntu 24.04 / Apache2/ PHP 설치&quot; data-og-description=&quot;모의해킹을 위한 WEB, WAS 서버를 구축합니다. 우선 서버 Instance 생성, Apahce, PHP 설치 를 진행한다.서버 환경AWS LightsailLightsail 은 저렴한 서비스는 3개월 무료로 사용할 수 있다. APM 서버 자체가 가볍&quot; data-og-host=&quot;bob-data.tistory.com&quot; data-og-source-url=&quot;https://bob-data.tistory.com/54&quot; data-og-url=&quot;https://bob-data.tistory.com/54&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/txH8G/hyZK93Bo64/ntfSA7cmvuEmJXYKAjIMhk/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/cmLms5/hyZLwMwglL/cxQyrGZyu95YK4Rf3K43q1/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/darrEq/hyZK9oWE7m/S8WgyMyKTDyJvZMnxBFukK/img.png?width=2004&amp;amp;height=1026&amp;amp;face=0_0_2004_1026&quot;&gt;&lt;a href=&quot;https://bob-data.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bob-data.tistory.com/54&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/txH8G/hyZK93Bo64/ntfSA7cmvuEmJXYKAjIMhk/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/cmLms5/hyZLwMwglL/cxQyrGZyu95YK4Rf3K43q1/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/darrEq/hyZK9oWE7m/S8WgyMyKTDyJvZMnxBFukK/img.png?width=2004&amp;amp;height=1026&amp;amp;face=0_0_2004_1026');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[APM-1] AWS Lightsail 에 Ubuntu 24.04 / Apache2/ PHP 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;모의해킹을 위한 WEB, WAS 서버를 구축합니다. 우선 서버 Instance 생성, Apahce, PHP 설치 를 진행한다.서버 환경AWS LightsailLightsail 은 저렴한 서비스는 3개월 무료로 사용할 수 있다. APM 서버 자체가 가볍&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bob-data.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 APM 구조중 Apache2, PHP 서버를 설치했습니다. 이번에는 MySQL을 설치 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lightsail 에서 가장 저렴한 (Ram - 512MB, vCpu - 1) 을 사용하였는데, MySQL 설치중 메모리가 부족하여 원활한 설치가 진행되지 않는 문제가 발생했습니다. 그래서 한 단계 업그레이드 진행했습니다. 제 기준 (Ram - 1GB, vCpu - 2) 를 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 정리하자면&lt;b&gt; Ubuntu 24.04&lt;/b&gt; 에&lt;b&gt; MySQL 8.0&lt;/b&gt; 을 설치합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MySQL 설치&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 설치 후 로컬에서도 접속하기 위해 포트 및 방화벽을 특정 ip 한정 오픈했습니다. 작업을 진행하며 고려한 사항은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CRUD 용 mysql db 계정 생성&lt;/li&gt;
&lt;li&gt;특정 ip port, firewall 오픈&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760941402878&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 패키지 목록 갱신
sudo apt update

# 2. MySQL 서버 설치 noninteractive 는 자동으로 설치를 진행하도록 해준다.
sudo DEBIAN_FRONTEND=noninteractive apt install -y mysql-server

# 3. 설치 후 서비스 상태 확인
sudo systemctl status mysql --no-pager

# 4. 기본 보안 설정 (비밀번호 설정 등)
sudo mysql_secure_installation&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MySQL 접속&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1760942841618&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# root 계정으로 접속, 비밀번호를 설정하지 않았다면 비밀번호 입력하라고 합니다.
# 아무 설정이 없다면 sudo mysql 로 그냥 진입 가능 합니다.
sudo mysql -u root -p

# 생성할 db 전용 CRUD 계정 생성 SQL 명령어
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# WITH mysql_native_password 를 통해 공개된 키 교환을 허락하지 않도록 하는 것이다.
CREATE USER 'appuser'@'localhost' IDENTIFIED WITH mysql_native_password BY '비밀번호입력';
# 만약 외부에서도 사용할 거라면 'appuser'@'localhost' 대신 'appuser'@'%' 로 입력
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost'; 
FLUSH PRIVILEGES;

# 접속 테스트
mysql -u appuser -p
# &amp;rarr; 비밀번호 입력
mysql&amp;gt; SHOW DATABASES;
mysql&amp;gt; USE appdb;
mysql&amp;gt; CREATE TABLE test (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
mysql&amp;gt; INSERT INTO test (name) VALUES ('hello');
mysql&amp;gt; SELECT * FROM test;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MySQL 외부 접속 허용 설정 (서버 사이드)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 로컬에서 db 툴 (dbeaver)를 사용하여 접속을 하려고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1760943033194&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# MySQL 설정 파일 수정
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

# 다음 라인 수정
bind-address = 127.0.0.1 

# 아래와 같이 수정
bind-address = 0.0.0.0

# 저장 후 mysql 재시작
sudo systemctl restart mysql

# 내 ip 방화벽 열기
# UFW: 내 IP만 열어둔 상태 확인
sudo ufw status
# 없으면 추가 (예: 내IP가 1.2.3.4라면)
sudo ufw allow from 1.2.3.4 to any port 3306 proto tcp

# 방화벽 열어주기 특정 port
sudo ufw allow 3306/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 은 정상 설치 되었으며, dbeaver 를 통해 로컬 환경에서도 정상 접속 가능합니다.&lt;/p&gt;</description>
      <category>모의해킹</category>
      <category>apache2</category>
      <category>APM</category>
      <category>AWS</category>
      <category>Lightsail</category>
      <category>Linux</category>
      <category>mysql</category>
      <category>php</category>
      <category>ubuntu 24.04</category>
      <category>모의해킹</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/55</guid>
      <comments>https://bob-data.tistory.com/55#entry55comment</comments>
      <pubDate>Mon, 20 Oct 2025 15:55:47 +0900</pubDate>
    </item>
    <item>
      <title>[APM-AWS-1]  Apache2-PHP 설치 / Lightsail Ubuntu 24.04</title>
      <link>https://bob-data.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모의해킹을 직접 해보기 위해서는 웹서버가 필요합니다. 가장 기초적인 APM (Apache-PHP-MySQL) 구조를 활용합니다. 이 포스트에서는 모의해킹을 위한 WEB, WAS 서버를 구축합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 서버 Instance 생성, Apahce, PHP 설치 를 진행합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서버 환경&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AWS Lightsail&lt;/b&gt;&lt;br /&gt;Lightsail 은 저렴한 서비스는 3개월 무료로 사용할 수 있다. APM 서버 자체가 가볍기 때문에 최저 사양으로 사용하기로 결정했다. 앱인 설치된 것도 할 수 있지만, Apache, PHP, MySQL 모두 직접 설치해보고 설치과정에서 발견할 수 있는 취약점이 있지 않을까 해서 직접 설치하기로 결정했다. 참고로 &lt;b&gt;LAMP&lt;/b&gt; 는 SSL 자동구성, Linux, Apache, PHP, MariaDB 가 설치되어 있고, phpMyAdmin, PHP 기본 모듈이 포함 되어 있다고 한다. (편하게 하려면 이거 설치하는게 좋을듯 합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpqkS6/btsRb6atlNr/DkKumr6eoAdKEhn7okbNrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpqkS6/btsRb6atlNr/DkKumr6eoAdKEhn7okbNrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpqkS6/btsRb6atlNr/DkKumr6eoAdKEhn7okbNrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpqkS6%2FbtsRb6atlNr%2FDkKumr6eoAdKEhn7okbNrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2004&quot; height=&quot;1026&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SSH 키를 통한 접속&lt;/b&gt;&lt;br /&gt;인스턴스를 생성하고 나면 SSH 키를 다운 받을 수 있다. 이 SSH 키를 통해 해당 서버에 접속을 할 수 있으니 잘 관리해야 한다. 키를 다운로드하고 나면 Windows 에서는 &lt;b&gt;Putty&lt;/b&gt; 를 통해, Mac OS 에서는 &lt;b&gt;Terminal&lt;/b&gt; 에서 접속할 수 있다. Mac 의 경우 해당 키 파일에 chmod 600 권한을 줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4xmnW/btsRccV4lVW/X9JRAuYwQi0RkHRKu6mNK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4xmnW/btsRccV4lVW/X9JRAuYwQi0RkHRKu6mNK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4xmnW/btsRccV4lVW/X9JRAuYwQi0RkHRKu6mNK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4xmnW%2FbtsRccV4lVW%2FX9JRAuYwQi0RkHRKu6mNK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1942&quot; height=&quot;376&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;APM 설치&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Apache2 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Apahce 는 웹 서버로 HTTP 통신을 지원해준다. 우선 Apache 설치는 명령어만 따라 치면 돼서 설치가 어렵지는 않다. Apache 설정 값들은 config 에서 직접 수정해줘야 하니, 나중에 SSL 이나 추가로 설정값 바꿔야 하면 직접 바꿔주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760597681501&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 시스템 업데이트
sudo apt update
sudo apt upgrade -y

# Apache2 설치
sudo apt install -y apache2

# Apache 서비스 상태 확인
sudo systemctl enable --now apache2
sudo systemctl status apache2 --no-pager

# 방화벽(ufw)에 http/https 오픈
sudo apt install -y ufw
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'   # 80, 443 허용
sudo ufw enable
sudo ufw status verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://server_ip 접속하면 Apache2 페이지로 접속이 된다. Apache2 는 기본적으로 &lt;b&gt;/var/www/html&lt;/b&gt; 경로를 바라보고 있다. 해당 경로에 있는 index.html 파일을 보여주고 있는 것이다. 추후에 경로 수정이 필요하면 &lt;b&gt;/etc/apache2/sites-available/000-default.conf&lt;/b&gt; 파일에서 수정해주면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Options Indexes FollowSysmLinks&lt;br /&gt;디렉터리 안에 index.html, index.php 와 같은 기본 파일이 없을 때 파일 목록을 자동으로 브라우저에 보여줄지&lt;br /&gt;&lt;span&gt;Options&lt;/span&gt; -Indexes +FollowSymLinks : 파일 목록 막고 링크 허용 / 보안 신경 쓰면 설정&lt;/li&gt;
&lt;li&gt;AllowOverride All&lt;br /&gt;.htaccess 파일 엑세스 허용 .htaccess 는 각 폴더 안에 둘 수 있는 Apache 설정 파일이다. (접근차단, Redirect 등)&lt;/li&gt;
&lt;li&gt;Require all granted&lt;br /&gt;디렉터리에 접근 가능 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;1190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clswrp/dJMb862mfJN/Mm6hNOliSa6a84zEGKxTuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clswrp/dJMb862mfJN/Mm6hNOliSa6a84zEGKxTuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clswrp/dJMb862mfJN/Mm6hNOliSa6a84zEGKxTuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclswrp%2FdJMb862mfJN%2FMm6hNOliSa6a84zEGKxTuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1166&quot; height=&quot;1190&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;1190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PHP 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;php 는 명령어 한줄이면 쉽게 설치 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1760598675625&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# php install
sudo apt install -y php libapache2-mod-php php-mysql php-cli php-curl php-gd php-mbstring php-xml php-zip

# 버전확인
php -v

# libapache2-mod-php 를 설치하면 자동으로 Apache 에 PHP 모듈이 로드 된다.
sudo systemctl restart apache2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 APM 중 AP 까지만 설치했다. 다음에는 우선 MySQL 연결 이전에 로그인 기능을 추가해보자.&amp;nbsp;&lt;/p&gt;</description>
      <category>모의해킹</category>
      <category>apache2</category>
      <category>APM</category>
      <category>Lightsail</category>
      <category>Linux</category>
      <category>normaltic</category>
      <category>php</category>
      <category>ubuntu</category>
      <category>ubuntu 24.04</category>
      <category>리눅스</category>
      <category>모의해킹</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/54</guid>
      <comments>https://bob-data.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 16 Oct 2025 16:16:17 +0900</pubDate>
    </item>
    <item>
      <title>Teams 휴가 시스템 구축 - Microsoft 365 (Automate 자동화, Power Apps 연동)</title>
      <link>https://bob-data.tistory.com/53</link>
      <description>&lt;h2 data-end=&quot;150&quot; data-start=&quot;108&quot; data-ke-size=&quot;size26&quot;&gt;Microsoft 365 기반 휴가 신청 및 승인 자동화 시스템 구축 기록&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Ms_Diagram.drawio (3).png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GIkJC/btsPd4LLrYI/YaDSz8Kkgs71HqEV6m7Uf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIkJC/btsPd4LLrYI/YaDSz8Kkgs71HqEV6m7Uf1/img.png&quot; data-alt=&quot;휴가 시스템 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIkJC/btsPd4LLrYI/YaDSz8Kkgs71HqEV6m7Uf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIkJC%2FbtsPd4LLrYI%2FYaDSz8Kkgs71HqEV6m7Uf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1030&quot; height=&quot;650&quot; data-filename=&quot;Ms_Diagram.drawio (3).png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;휴가 시스템 다이어그램&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;287&quot; data-start=&quot;152&quot; data-ke-size=&quot;size16&quot;&gt;직장 내에서 휴가를 관리하는 일은 생각보다 복잡한 작업인 것 같습니다. 누가 언제 휴가를 갔는지, 대기 중인 요청은 어떤 것인지, 승인자는 어떻게 확인하고 처리할 수 있는지 등 여러 절차가 수작업으로 진행되면 누락이나 중복, 혼선이 발생하기 쉽습니다.&lt;/p&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;289&quot; data-ke-size=&quot;size16&quot;&gt;우리 회사는 이전까지 &lt;b&gt;노션&lt;/b&gt;을 활용해 팀원들의 연차 및 휴가 일정을 공유해왔습니다. 모두가 자유롭게 수정 가능한 구조였기 때문에, 다른 사람의 휴가 일정을 확인하는 데에는 편리했지만 &lt;b&gt;승인 절차가 없고&lt;/b&gt;, &lt;b&gt;데이터 변경의 위험성&lt;/b&gt;이 있다는 점에서 개선이 필요했습니다.&lt;/p&gt;
&lt;p data-end=&quot;525&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하고자, &lt;b&gt;Microsoft 365의 다양한 앱을 연동하여 휴가 신청과 승인 절차를 자동화하는 시스템&lt;/b&gt;을 구축하게 되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;525&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;시작하기에 앞서 Microsoft 365 &lt;b&gt;Business 계정 이상&lt;/b&gt; 라이선스가 있어야 이용하실 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;530&quot; data-start=&quot;527&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;542&quot; data-start=&quot;532&quot; data-ke-size=&quot;size23&quot;&gt;시스템 개요&lt;/h3&gt;
&lt;p data-end=&quot;666&quot; data-start=&quot;544&quot; data-ke-size=&quot;size16&quot;&gt;본 시스템은 &lt;b&gt;Microsoft Forms, Power Automate, Excel Online, SharePoint List, Power Apps, Teams&lt;/b&gt; 등 M365 생태계 안의 툴들을 연결해 구현했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;852&quot; data-start=&quot;668&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;695&quot; data-start=&quot;668&quot;&gt;휴가 신청은 &lt;b&gt;Forms&lt;/b&gt;에서 이루어지고,&lt;/li&gt;
&lt;li data-end=&quot;739&quot; data-start=&quot;696&quot;&gt;신청 데이터는 &lt;b&gt;Excel Online&lt;/b&gt;과 &lt;b&gt;List&lt;/b&gt;에 저장되며,&lt;/li&gt;
&lt;li data-end=&quot;780&quot; data-start=&quot;740&quot;&gt;&lt;b&gt;Power Automate&lt;/b&gt;가 전체 흐름을 자동으로 제어합니다.&lt;/li&gt;
&lt;li data-end=&quot;818&quot; data-start=&quot;781&quot;&gt;사용자 인터페이스는 &lt;b&gt;Power Apps&lt;/b&gt;를 통해 제공하며,&lt;/li&gt;
&lt;li data-end=&quot;852&quot; data-start=&quot;819&quot;&gt;모든 접근과 알림은 &lt;b&gt;Teams&lt;/b&gt;를 통해 연결됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;857&quot; data-start=&quot;854&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;874&quot; data-start=&quot;859&quot; data-ke-size=&quot;size23&quot;&gt;사용한 도구 및 역할&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1239&quot; data-start=&quot;876&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1239&quot; data-start=&quot;920&quot;&gt;
&lt;tr data-end=&quot;956&quot; data-start=&quot;920&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;939&quot; data-start=&quot;920&quot;&gt;&lt;b&gt;Forms&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;956&quot; data-start=&quot;939&quot; data-col-size=&quot;sm&quot;&gt;사용자의 휴가 신청 입력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1000&quot; data-start=&quot;957&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;976&quot; data-start=&quot;957&quot;&gt;&lt;b&gt;Excel Online&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;1000&quot; data-start=&quot;976&quot; data-col-size=&quot;sm&quot;&gt;신청 내역 및 휴가 현황 데이터 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1053&quot; data-start=&quot;1001&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1020&quot; data-start=&quot;1001&quot;&gt;&lt;b&gt;MS List&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;1053&quot; data-start=&quot;1020&quot; data-col-size=&quot;sm&quot;&gt;Power Apps와 연동되는 구조화된 데이터 저장소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1112&quot; data-start=&quot;1054&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1073&quot; data-start=&quot;1054&quot;&gt;&lt;b&gt;Power Apps&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;1112&quot; data-start=&quot;1073&quot; data-col-size=&quot;sm&quot;&gt;사용자에게 휴가 현황 및 신청 내역 UI 제공, 휴가 취소 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1176&quot; data-start=&quot;1113&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1134&quot; data-start=&quot;1113&quot;&gt;&lt;b&gt;Power Automate&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;1176&quot; data-start=&quot;1134&quot; data-col-size=&quot;sm&quot;&gt;신청&amp;ndash;승인 전 과정 자동화, 승인 여부에 따른 분기 처리, 알림 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1239&quot; data-start=&quot;1177&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1196&quot; data-start=&quot;1177&quot;&gt;&lt;b&gt;Teams&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;1239&quot; data-start=&quot;1196&quot; data-col-size=&quot;sm&quot;&gt;신청 탭, 알림, 승인 요청 등 사용자 접근성과 커뮤니케이션 채널 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;1244&quot; data-start=&quot;1241&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1257&quot; data-start=&quot;1246&quot; data-ke-size=&quot;size23&quot;&gt;프로세스 요약&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1789&quot; data-start=&quot;1259&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1362&quot; data-start=&quot;1259&quot;&gt;&lt;b&gt;Power Apps&lt;/b&gt;&lt;br /&gt;사용자는 Power Apps를 통해 &lt;b&gt;본인의 휴가 현황 및 신청 내역을 확인&lt;/b&gt;하고, &lt;b&gt;승인 대기 중인 휴가 신청을 취소&lt;/b&gt;할 수 있습니다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1489&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lPoFY/btsPdICcGfc/LVOvqtBgeZ2p81VlOcQGu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lPoFY/btsPdICcGfc/LVOvqtBgeZ2p81VlOcQGu0/img.png&quot; data-alt=&quot;Power Apps UI 휴가 현황 체크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lPoFY/btsPdICcGfc/LVOvqtBgeZ2p81VlOcQGu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlPoFY%2FbtsPdICcGfc%2FLVOvqtBgeZ2p81VlOcQGu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;408&quot; data-origin-width=&quot;1489&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Power Apps UI 휴가 현황 체크&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1430&quot; data-start=&quot;1364&quot;&gt;&lt;b&gt;Forms (휴가 신청)&lt;/b&gt;&lt;br /&gt;사용자는 Teams 탭에서 &lt;b&gt;Forms로 휴가 신청서를 작성&lt;/b&gt;합니다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;781&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b51z2O/btsPdNXPX6k/Ubxmc5xz1ib1SkXLpQQjo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b51z2O/btsPdNXPX6k/Ubxmc5xz1ib1SkXLpQQjo0/img.png&quot; data-alt=&quot;휴가 신청 Forms&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b51z2O/btsPdNXPX6k/Ubxmc5xz1ib1SkXLpQQjo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb51z2O%2FbtsPdNXPX6k%2FUbxmc5xz1ib1SkXLpQQjo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;550&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;781&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;휴가 신청 Forms&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1430&quot; data-start=&quot;1364&quot;&gt;&lt;b&gt;&lt;b&gt;Power Automate Flow 작동&lt;/b&gt;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1789&quot; data-start=&quot;1465&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1492&quot; data-start=&quot;1465&quot;&gt;Forms 데이터를 수신하여 자동 플로우 시작&lt;/li&gt;
&lt;li data-end=&quot;1547&quot; data-start=&quot;1496&quot;&gt;신청 내역을 &lt;b&gt;Excel Online&lt;/b&gt; 및 &lt;b&gt;SharePoint List&lt;/b&gt;에 기록&lt;/li&gt;
&lt;li data-end=&quot;1576&quot; data-start=&quot;1551&quot;&gt;신청 상태에 따라 승인 요청 프로세스 시작&lt;/li&gt;
&lt;li data-end=&quot;1621&quot; data-start=&quot;1580&quot;&gt;승인 절차 중 &lt;b&gt;사용자가 신청을 취소할 경우 예외 처리 로직 적용&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1656&quot; data-start=&quot;1625&quot;&gt;승인자에게 &lt;b&gt;Teams 알림&lt;/b&gt; 및 승인 요청 전송&lt;/li&gt;
&lt;li data-end=&quot;1789&quot; data-start=&quot;1660&quot;&gt;승인 여부에 따라 데이터 업데이트 및 사용자 알림
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1789&quot; data-start=&quot;1695&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1755&quot; data-start=&quot;1695&quot;&gt;&lt;b&gt;승인&lt;/b&gt;: Excel 및 List 업데이트 + Teams 알림 + Outlook 메일 + 캘린더 등록&lt;/li&gt;
&lt;li data-end=&quot;1789&quot; data-start=&quot;1761&quot;&gt;&lt;b&gt;거절&lt;/b&gt;: 동일하게 Teams 및 메일 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1789&quot; data-start=&quot;1761&quot;&gt;&lt;b&gt;휴가 데이터 관리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1789&quot; data-start=&quot;1761&quot;&gt;Excel Online 으로 데이터 적재,&lt;/li&gt;
&lt;li data-end=&quot;1789&quot; data-start=&quot;1761&quot;&gt;Excel Online &amp;lt;-&amp;gt; SharePoint List 데이터 주기적으로 동기화 (Automate 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-end=&quot;1794&quot; data-start=&quot;1791&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1805&quot; data-start=&quot;1796&quot; data-ke-size=&quot;size23&quot;&gt;도입 후기&lt;/h3&gt;
&lt;p data-end=&quot;1980&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size16&quot;&gt;Microsoft의 통합 생태계는 기대 이상이었습니다. 단순히 Excel이나 SharePoint List에 데이터를 저장하면, 그 데이터를 Power Apps, Automate, Teams 등 다양한 앱과 연결해 &lt;b&gt;UI 생성, 승인 처리, 알림 전송 등 거의 모든 작업을 코드 없이 구현&lt;/b&gt;할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;2108&quot; data-start=&quot;1982&quot; data-ke-size=&quot;size16&quot;&gt;특히 사용자의 입장에서도 접근성이 좋고, 별다른 교육 없이도 &lt;b&gt;직관적인 인터페이스&lt;/b&gt;를 통해 쉽게 적응할 수 있었던 점이 인상적이었습니다. &lt;b&gt;한 번 구축해두면 운영이 매우 효율적이고 안정적&lt;/b&gt;이라는 것도 큰 장점이었습니다.&lt;/p&gt;
&lt;hr data-end=&quot;2113&quot; data-start=&quot;2110&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2131&quot; data-start=&quot;2115&quot; data-ke-size=&quot;size23&quot;&gt;추가로 생각해볼 개선점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2291&quot; data-start=&quot;2133&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2201&quot; data-start=&quot;2133&quot;&gt;캘린더(Outlook/Google) 자동 등록은 현재 단순 구현 상태인데, 사용자 개별 동기화를 고려한 고도화가 필요함&lt;/li&gt;
&lt;li data-end=&quot;2248&quot; data-start=&quot;2202&quot;&gt;Power Apps에서 휴가 신청도 함께 받는 구조로 통합하면 UI 일원화 가능&lt;/li&gt;
&lt;li data-end=&quot;2291&quot; data-start=&quot;2249&quot;&gt;연차 통계, 사용률 분석 기능 추가 시, Power BI 연동 고려 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/89kUK/btsPdz6w0rY/UPP4qnc2A5WKsdpp4blG71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/89kUK/btsPdz6w0rY/UPP4qnc2A5WKsdpp4blG71/img.png&quot; data-alt=&quot;휴가 시스템 로직&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/89kUK/btsPdz6w0rY/UPP4qnc2A5WKsdpp4blG71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F89kUK%2FbtsPdz6w0rY%2FUPP4qnc2A5WKsdpp4blG71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;309&quot; height=&quot;828&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;휴가 시스템 로직&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Cloud</category>
      <category>Automate</category>
      <category>excel online</category>
      <category>microsoft 365</category>
      <category>MS 365</category>
      <category>MS Forms</category>
      <category>ms 휴가</category>
      <category>Power Apps</category>
      <category>sharepoint</category>
      <category>teams 휴가</category>
      <category>휴가 시스템</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/53</guid>
      <comments>https://bob-data.tistory.com/53#entry53comment</comments>
      <pubDate>Thu, 10 Jul 2025 21:25:20 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD] GCP 서버 CI/CD 파이프라인 구축 (Github Actions)</title>
      <link>https://bob-data.tistory.com/52</link>
      <description>&lt;p data-end=&quot;253&quot; data-start=&quot;143&quot; data-ke-size=&quot;size16&quot;&gt;운영 중인 백엔드 시스템에 CI/CD를 도입하면서, 코드 배포의 시간을 50% 이상 단축할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;258&quot; data-start=&quot;255&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;276&quot; data-start=&quot;260&quot; data-ke-size=&quot;size26&quot;&gt;1. 프로젝트 환경 구성&lt;/h2&gt;
&lt;p data-end=&quot;307&quot; data-start=&quot;278&quot; data-ke-size=&quot;size16&quot;&gt;현재 시스템은 다음과 같은 구조로 운영되고 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;607&quot; data-start=&quot;309&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;422&quot; data-start=&quot;309&quot;&gt;&lt;b&gt;로컬 개발 환경 &amp;rarr; Bastion Host &amp;rarr; Portal Server&lt;/b&gt;&lt;br /&gt;개발된 코드는 GitHub를 통해 관리되며, Bastion Host를 거쳐 Portal Server에 배포됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;422&quot; data-start=&quot;309&quot;&gt;기존 구조: Local (.war 파일 mvn build) -&amp;gt; .war 파일 Bastion Host 전송 -&amp;gt; .war 파일 Portal Server 전송 -&amp;gt; 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;607&quot; data-start=&quot;424&quot;&gt;&lt;b&gt;보안 강화 구조 (GCP Private IP + IAP)&lt;/b&gt;&lt;br /&gt;Portal 서버는 GCP 내부 네트워크에 위치하고 있으며, &lt;b&gt;외부에서 직접 접근할 수 없고&lt;/b&gt;, IAP(Identity-Aware Proxy)를 통해서만 접속이 가능합니다. 모든 배포 작업은 IAP 터널링을 통해 Bastion Host를 경유합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;612&quot; data-start=&quot;609&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;646&quot; data-start=&quot;614&quot; data-ke-size=&quot;size26&quot;&gt;2. 의존성 이슈 대응 (googlebigquery)&lt;/h2&gt;
&lt;p data-end=&quot;844&quot; data-start=&quot;648&quot; data-ke-size=&quot;size16&quot;&gt;배포 과정에서 googlebigquery-jdbc 관련 외부 의존성이 Maven 중앙 저장소에서 정상적으로 다운로드되지 않는 문제가 있었습니다.&lt;br /&gt;이 문제는 해당 라이브러리 .jar 파일을 프로젝트 레포에 직접 포함시키는 방식으로 해결했습니다. 완전한 자동화에는 다소 한계가 있었지만, CI 파이프라인 내 의존성 충돌은 방지할 수 있었습니다.&lt;/p&gt;
&lt;hr data-end=&quot;849&quot; data-start=&quot;846&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;885&quot; data-start=&quot;851&quot; data-ke-size=&quot;size26&quot;&gt;3. CI 파이프라인 구성 (GitHub Actions)&lt;/h2&gt;
&lt;h3 data-end=&quot;897&quot; data-start=&quot;887&quot; data-ke-size=&quot;size23&quot;&gt;트리거 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1009&quot; data-start=&quot;899&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1009&quot; data-start=&quot;899&quot;&gt;test, production 브랜치로 &lt;b&gt;Pull Request가 생성될 때&lt;/b&gt; 자동으로 동작합니다.&lt;br /&gt;브랜치별로 설정된 프로파일(dev, prd)에 따라 빌드 환경이 구분됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1023&quot; data-start=&quot;1011&quot; data-ke-size=&quot;size23&quot;&gt;주요 작업 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1390&quot; data-start=&quot;1025&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1092&quot; data-start=&quot;1025&quot;&gt;&lt;b&gt;코드 체크아웃 및 Java 환경 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1092&quot; data-start=&quot;1056&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1092&quot; data-start=&quot;1056&quot;&gt;GitHub Actions에서 Temurin JDK 11 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1164&quot; data-start=&quot;1093&quot;&gt;&lt;b&gt;Maven 빌드 실행&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1164&quot; data-start=&quot;1115&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1164&quot; data-start=&quot;1115&quot;&gt;mvn clean install -Pdev 혹은 -Pprd로 WAR 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1258&quot; data-start=&quot;1165&quot;&gt;&lt;b&gt;Google Cloud 인증 및 SSH 연결&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1258&quot; data-start=&quot;1200&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1218&quot; data-start=&quot;1200&quot;&gt;서비스 계정 키를 활용한 인증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1218&quot; data-start=&quot;1200&quot;&gt;Github 레포지토리 Settings &amp;gt; Secrets and variables &amp;gt; Actions &amp;gt; New repository secret 생성 -&amp;gt; yml 파일에서 사용&lt;/li&gt;
&lt;li data-end=&quot;1218&quot; data-start=&quot;1200&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1258&quot; data-start=&quot;1222&quot;&gt;IAP 터널을 통해 Bastion Host에 WAR 파일 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1390&quot; data-start=&quot;1259&quot;&gt;&lt;b&gt;Bastion Host에서 배포 스크립트 실행&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1390&quot; data-start=&quot;1295&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1343&quot; data-start=&quot;1295&quot;&gt;사전에 작성된 deploy_dev.sh 또는 deploy_prod.sh 실행&lt;/li&gt;
&lt;li data-end=&quot;1390&quot; data-start=&quot;1347&quot;&gt;해당 스크립트는 내부 Portal 서버로 WAR 파일을 복사하는 역할 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1751015648213&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Deploy WAR to Bastion (production)

on:
  pull_request:
    branches: [production]

jobs:
  deploy:
    name: Build and Deploy to Production
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Java
      uses: actions/setup-java@v4
      with:
        distribution: 'temurin'
        java-version: '11'

    - name: Build WAR using Maven
      run: mvn -B clean install -Pprd

    - name: Set up Google Cloud SDK
      uses: google-github-actions/auth@v2
      with:
        project_id: service-account
        credentials_json: ${{ secrets.key }}
        export_default_credentials: true

    - name: Activate service account for gcloud CLI
      run: |
        gcloud auth activate-service-account --key-file=&quot;${GOOGLE_APPLICATION_CREDENTIALS}&quot;
        gcloud config set account service-account@service-account.iam.gserviceaccount.com

    - name: Transfer WAR to Bastion Host via IAP
      run: |
        gcloud compute scp target/file.war bastion-host:/home/service-account/ \
          --zone=asia-northeast3-a \
          --tunnel-through-iap \
          --project=service-account

    - name: Run deployment script on Bastion
      run: |
        gcloud compute ssh bastion-host \
          --zone=asia-northeast3-a \
          --tunnel-through-iap \
          --project=dmdashboard-prod \
          --command=&quot;bash /home/service-account/deploy_prod.sh&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfaKsP/btsOUUDC30P/tbqGyo8GcmcRfWwukFdlI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfaKsP/btsOUUDC30P/tbqGyo8GcmcRfWwukFdlI0/img.png&quot; data-alt=&quot;Github Repository secrets&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfaKsP/btsOUUDC30P/tbqGyo8GcmcRfWwukFdlI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfaKsP%2FbtsOUUDC30P%2FtbqGyo8GcmcRfWwukFdlI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;298&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Repository secrets&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-end=&quot;1395&quot; data-start=&quot;1392&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1417&quot; data-start=&quot;1397&quot; data-ke-size=&quot;size26&quot;&gt;4. CD (배포 자동화) 전략&lt;/h2&gt;
&lt;p data-end=&quot;1545&quot; data-start=&quot;1419&quot; data-ke-size=&quot;size16&quot;&gt;보안상의 이유로, &lt;b&gt;최종 WAS 배포는 자동화하지 않고 운영자가 수동으로 수행&lt;/b&gt;하도록 했습니다.&lt;br /&gt;WAR 파일만 전달된 상태에서, 직접 Tomcat을 재시작하거나 웹 애플리케이션을 교체하는 식으로 운영 환경에 반영합니다.&lt;/p&gt;
&lt;p data-end=&quot;1568&quot; data-start=&quot;1547&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 설계한 이유는 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1631&quot; data-start=&quot;1570&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1587&quot; data-start=&quot;1570&quot;&gt;운영 서버 접근 권한 최소화&lt;/li&gt;
&lt;li data-end=&quot;1606&quot; data-start=&quot;1588&quot;&gt;실시간 장애 대응 여지를 확보&lt;/li&gt;
&lt;li data-end=&quot;1631&quot; data-start=&quot;1607&quot;&gt;배포 승인 및 리스크 확인 프로세스 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2104&quot; data-start=&quot;1936&quot; data-ke-size=&quot;size16&quot;&gt;이번 CI/CD 구축을 통해 배포 시간과 실수를 줄이고, 운영 안정성을 높일 수 있게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chQI0o/btsOW9Gah9a/l3pe9ZWkRSuQLGH6aSqbuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chQI0o/btsOW9Gah9a/l3pe9ZWkRSuQLGH6aSqbuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chQI0o/btsOW9Gah9a/l3pe9ZWkRSuQLGH6aSqbuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchQI0o%2FbtsOW9Gah9a%2Fl3pe9ZWkRSuQLGH6aSqbuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1293&quot; height=&quot;91&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Git</category>
      <category>Bastion host</category>
      <category>ci/cd</category>
      <category>GCP</category>
      <category>github</category>
      <category>github actions</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/52</guid>
      <comments>https://bob-data.tistory.com/52#entry52comment</comments>
      <pubDate>Fri, 27 Jun 2025 18:20:37 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] Bastion Host VM 서버 구축 (Proxy, OS Login)</title>
      <link>https://bob-data.tistory.com/51</link>
      <description>&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;GCP 환경에서 직접 VM에 외부에서 접근하는 것은 보안상 위험이 있어, 안전한 방법을 찾게 되었습니다. 특히, Cloud IAP(Identity-Aware Proxy)를 활용해 외부 IP 오픈 없이도 SSH 접속을 가능하게 하려는 요구가 있었고, 이를 위해 Bastion Host를 설정하게 되었습니다. Bastion Host는 관리용 VM으로 최소한의 보안 구성을 갖추어 내부 자원에 대한 안전한 접근 통로 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sWZTP/btsNCfaetz5/k8UBnwkPkrezX6E15Vwhq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sWZTP/btsNCfaetz5/k8UBnwkPkrezX6E15Vwhq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sWZTP/btsNCfaetz5/k8UBnwkPkrezX6E15Vwhq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsWZTP%2FbtsNCfaetz5%2Fk8UBnwkPkrezX6E15Vwhq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;436&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. Bastion Host VM 설정&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;VM 생성 방법&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Compute Engine &amp;rarr; VM 인스턴스 &amp;rarr; &amp;lsquo;인스턴스 만들기&amp;rsquo; 선택&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 설정 값&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;이름: &lt;/span&gt;&lt;span&gt;bastion-host-dev&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리전/존: 서비스 환경과 동일한 리전 선택 (예: &lt;/span&gt;&lt;span&gt;asia-northeast3-a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;머신 타입: &lt;/span&gt;&lt;span&gt;e2-medium&lt;/span&gt;&lt;span&gt; (1vCPU, 4GB RAM)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;부팅 디스크: &lt;/span&gt;&lt;span&gt;Debian GNU/Linux 11&lt;/span&gt;&lt;span&gt; (기본값)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;네트워크: 전용 VPC 또는 default VPC 사용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;방화벽:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;HTTP, HTTPS 체크 해제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;IAP를 통한 SSH 터널링 허용 (35.235.240.0/20 대역의 22번 포트 Ingress 허용)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;추가 설정&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Metadata&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;enable-oslogin=TRUE&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;보안&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Project-wide SSH keys 비활성화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;OS Login 2단계 인증(2FA) 활성화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. Bastion Host Service 계정 생성&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;서비스 계정 생성&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;IAM &amp;amp; Admin &amp;rarr; 서비스 계정 &amp;rarr; '서비스 계정 만들기'&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이름: &lt;/span&gt;&lt;span&gt;bastion-host-sa&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;역할:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Compute Instance Admin (v1)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Service Account User&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;VM에 서비스 계정 연결&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Bastion Host VM 수정 &amp;rarr; 서비스 계정 &amp;rarr; &lt;/span&gt;&lt;span&gt;bastion-host-sa&lt;/span&gt;&lt;span&gt; 선택&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. 로컬에서 Bastion Host로 접속&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;접속 명령어&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;gcloud compute ssh bastion-host-dev \
  --project=[PROJECT_ID] \
  --zone=asia-northeast3-a \
  --tunnel-through-iap&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;--tunnel-through-iap&lt;/span&gt;&lt;span&gt; 옵션으로 Cloud IAP를 통한 보안 터널링&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;접속 전 &lt;/span&gt;&lt;span&gt;gcloud auth login&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;gcloud config set project [PROJECT_ID]&lt;/span&gt;&lt;span&gt; 필수&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. Bastion Host에서 Dev VM 접속&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Bastion Host 내부에서 Dev VM으로 SSH 접속&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;ssh [DEV_VM_USER]@[DEV_VM_INTERNAL_IP]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;[DEV_VM_INTERNAL_IP]는 같은 VPC 내 프라이빗 IP 주소 사용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Bastion Host와 Dev VM은 서로 통신 가능해야 함 (VPC/Subnet 설정 확인)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5. Bastion Host를 Proxy로 Cloud SQL MySQL 접속&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Cloud SQL Auth Proxy 설치&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;curl -o cloud-sql-proxy https://dl.google.com/cloudsql/cloud-sql-proxy.linux.amd64
chmod +x cloud-sql-proxy
sudo mv cloud-sql-proxy /usr/local/bin/&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Proxy 실행&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;cloud-sql-proxy [INSTANCE_CONNECTION_NAME] \
  --private-ip \
  --port=3306&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;[INSTANCE_CONNECTION_NAME]&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;project:region:instance&lt;/span&gt;&lt;span&gt; 형식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;--private-ip&lt;/span&gt;&lt;span&gt; 옵션 필수 (Private IP로 연결)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;로컬에서 Bastion Host를 통한 DB 접속&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mysql -h 127.0.0.1 -P 3306 -u [DB_USER] -p&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;localhost로 접속하면 Bastion Host를 통해 Cloud SQL과 연결됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이렇게 설정하면 외부 노출 없이 Bastion Host를 통한 안전한 접속 경로를 구축할 수 있습니다. 추가로, Bastion Host에는 최소한의 권한만 부여하고, 정기적인 접속 로그 점검이 권장됩니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Cloud</category>
      <category>Bastion host</category>
      <category>GCP</category>
      <category>Google Cloud Platform</category>
      <category>os login</category>
      <category>proxy</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/51</guid>
      <comments>https://bob-data.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 27 Apr 2025 13:08:47 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] Google Cloud Armor 적용 (feat. Custom Error page - Cloud Storage)</title>
      <link>https://bob-data.tistory.com/50</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6OM3/btsMSvmj2gJ/Rte7rKKOMtP3laKZTX2a8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6OM3/btsMSvmj2gJ/Rte7rKKOMtP3laKZTX2a8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6OM3/btsMSvmj2gJ/Rte7rKKOMtP3laKZTX2a8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6OM3%2FbtsMSvmj2gJ%2FRte7rKKOMtP3laKZTX2a8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;663&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 운영하다 보면 내부 시스템을 외부에 노출시켜야 하는 상황이 생긴다. 특히 클라이언트사 담당자들이 접근하는 운영/통계용 대시보드는 접근 권한을 세밀하게 제어해야 할 필요가 있는데, GCP 환경에서 &lt;b&gt;Google Cloud Armor&lt;/b&gt;를 활용한 IP 기반 접근 제어를 구현하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 Cloud Armor를 활용하여 특정 IP만 접근 가능하도록 구성하고, 허용되지 않은 IP는 &lt;b&gt;커스텀 에러 페이지로 유도하는 구성 방법&lt;/b&gt;을 상세히 정리한 실전 가이드이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Cloud Armor를 도입했는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud Armor를 도입한 이유는 명확하다. &lt;b&gt;운영 중인 VM이 퍼블릭하게 노출되어 있고, 이 서비스에 접근 가능한 IP를 고객사 내부망으로 제한하고자 했기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP의 VM 방화벽 기능으로도 IP를 제어할 수 있지만, 다음과 같은 이유로 Cloud Armor를 선택했다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VM 수준이 아닌 &lt;b&gt;LB(Load Balancer) 수준에서 제어&lt;/b&gt; 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스텀 에러 페이지 리디렉션&lt;/b&gt; 구성 가능&lt;/li&gt;
&lt;li&gt;향후 &lt;b&gt;WAF, DDoS 대응까지 고려한 확장성&lt;/b&gt; 보유&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Cloud Armor 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적:&lt;/b&gt; 특정 IP만 허용하고, 허용되지 않은 IP의 요청을 차단하여 403 에러를 반환하도록 설정&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계별 설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GCP 콘솔 &amp;rarr; 네트워크 보안 &amp;rarr; Cloud Armor 이동&lt;/li&gt;
&lt;li&gt;&quot;보안 정책(Security Policy) 생성&quot; 클릭&lt;/li&gt;
&lt;li&gt;정책 이름 설정: 예) dev-access-policy&lt;/li&gt;
&lt;li&gt;정책 규칙 추가:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;허용할 IP 추가 &amp;rarr; ALLOW 설정&lt;/li&gt;
&lt;li&gt;차단할 모든 IP 추가 &amp;rarr; DENY (403) 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;정책 저장 후 Load Balancer의 백엔드 서비스에 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: dev-web-loadbalancer-backend&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; 지정된 IP 이외의 접근은 모두 403 Forbidden 반환&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Load Balancer 선택 &amp;amp; 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적:&lt;/b&gt; Cloud Armor가 적용된 Load Balancer에서 차단된 요청이 발생하면, 특정 에러 페이지로 리디렉션&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계별 설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GCP 콘솔 &amp;rarr; 네트워크 서비스 &amp;rarr; Load Balancing 이동&lt;/li&gt;
&lt;li&gt;기존 Load Balancer 선택 (예: dev-loadbalancer)&lt;/li&gt;
&lt;li&gt;백엔드 서비스(Backend Service) 확인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dev-web-loadbalancer-backend가 Cloud Armor와 연결되어 있는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라우팅 규칙 수정:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: dev.example.com 도메인 확인&lt;/li&gt;
&lt;li&gt;Cloud Armor 정책 적용 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; 허용된 IP는 정상 접속, 차단된 IP는 403 Forbidden 발생&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Cloud Storage에 403 에러 페이지 추가 후 공개 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적:&lt;/b&gt; 차단된 사용자가 403 Forbidden 페이지를 보도록 설정&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계별 설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GCP 콘솔 &amp;rarr; Cloud Storage 이동&lt;/li&gt;
&lt;li&gt;새 버킷 생성 (예: dev-error-page-bucket)&lt;/li&gt;
&lt;li&gt;403.html 파일 업로드 (커스텀 메시지 작성)&lt;/li&gt;
&lt;li&gt;파일 공개 설정:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;allUsers에게 READER 권한 부여&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; Public URL 을 통해&amp;nbsp;접근 가능&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Load Balancer에 백엔드 버킷 추가 후 dev 환경에 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적:&lt;/b&gt; Cloud Armor에서 403 응답이 발생하면, Cloud Storage의 403 페이지로 리디렉트&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 백엔드 버킷 추가&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GCP 콘솔 &amp;rarr; Load Balancing 이동&lt;/li&gt;
&lt;li&gt;&quot;백엔드 구성&quot; 탭 클릭&lt;/li&gt;
&lt;li&gt;&quot;새 백엔드 버킷 생성&quot;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름: dev-error-page-bucket-backend&lt;/li&gt;
&lt;li&gt;Cloud Storage 버킷 선택: dev-error-page-bucket&lt;/li&gt;
&lt;li&gt;Cloud CDN 여부 설정 (선택)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;저장 및 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; Load Balancer가 정적 페이지를 서빙할 수 있음&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) dev 환경에 403 경로 추가&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Load Balancer &amp;rarr; 라우팅 규칙 편집&lt;/li&gt;
&lt;li&gt;호스트 및 경로 규칙 추가:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트: error-dev.example.com&lt;/li&gt;
&lt;li&gt;경로: /403.html&lt;/li&gt;
&lt;li&gt;백엔드: dev-error-page-bucket-backend&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;저장 및 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 정책에 403 커스텀 오류 페이지 적용&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;defaultCustomErrorResponsePolicy:
  errorResponseRules:
    - matchResponseCodes:
      - 403
      path: '/403.html'
  errorService: https://www.googleapis.com/compute/v1/projects/my-gcp-project/global/backendBuckets/dev-error-page-bucket-backend
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; Cloud Armor가 차단한 요청은 /403.html 페이지로 자동 리디렉트됨&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;VM 방화벽과의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 단순히 VM 방화벽으로도 IP를 제한할 수 있지 않을까 생각했지만, 운영 VM에 직접 방화벽을 적용하면 배포 시 마다 규칙을 변경해야 하고, 커스텀 에러 페이지를 구성할 수 없다는 점이 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud Armor는 LB 앞단에서 트래픽을 차단하기 때문에 서비스 자체에 부담을 주지 않으며, UX를 고려한 안내 페이지 제공까지 가능하다. 여기에 DDoS 대응과 WAF 설정까지 고려할 수 있는 구조라, 확장성과 보안성을 모두 만족시킬 수 있는 선택이었다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1493&quot; data-start=&quot;1162&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1493&quot; data-start=&quot;1257&quot;&gt;
&lt;tr data-end=&quot;1323&quot; data-start=&quot;1257&quot;&gt;
&lt;td&gt;&lt;b&gt;DDoS 방어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ Google Edge Network에서 차단&lt;/td&gt;
&lt;td&gt;❌ VM까지 트래픽이 도달 후 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1380&quot; data-start=&quot;1324&quot;&gt;
&lt;td&gt;&lt;b&gt;IP Spoofing 대응&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 네트워크 계층에서 차단&lt;/td&gt;
&lt;td&gt;❌ VM 내부에서만 적용됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1438&quot; data-start=&quot;1381&quot;&gt;
&lt;td&gt;&lt;b&gt;SQL Injection / XSS 방어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ WAF 기능 제공&lt;/td&gt;
&lt;td&gt;❌ 수동 설정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1493&quot; data-start=&quot;1439&quot;&gt;
&lt;td&gt;&lt;b&gt;L7 공격 방어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 웹 애플리케이션 공격 차단 가능&lt;/td&gt;
&lt;td&gt;❌ L4 레벨에서만 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 구성은 단순한 접근 제한을 넘어서, 사용자에게 명확한 안내 메시지를 제공하고 내부망 IP만 접근할 수 있도록 구성하는 실전형 아키텍처이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud Armor를 통해 IP 기반 접근 제어를 구현하고, 커스텀 에러 페이지로 사용자 경험까지 챙기는 설정이 필요한 상황이라면, 이 글이 충분히 도움이 될 것이다.&lt;/p&gt;</description>
      <category>Cloud</category>
      <category>cloud armor</category>
      <category>GCP</category>
      <category>Google</category>
      <category>google cloud armor</category>
      <category>클라우드 아머</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/50</guid>
      <comments>https://bob-data.tistory.com/50#entry50comment</comments>
      <pubDate>Sun, 23 Mar 2025 12:54:23 +0900</pubDate>
    </item>
    <item>
      <title>[Apache upgrade] 2.4.55 -&amp;gt; 2.4.60 version 업그레이드 (CentOS 7.9.2009)</title>
      <link>https://bob-data.tistory.com/49</link>
      <description>&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;Apache 2.4.55 버전이 보안 검사에서 이슈가 많이 발생해서 2.4.60 버전으로 업그레이드 하기로 결정했다. 서버는 CentOS 7.9.2009 다. 신경 써야 될 부분도 있었는데, 기존 Apache 는 유지 시키고 새 경로에 Apache 를 설치 했다. (문제 생길시 바로 되돌리기 위해)&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;아래는 꼭 신경 써야 하는 내용들이다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;1 1 []&quot;&gt;&amp;nbsp;apr, apr-util 설치&lt;/li&gt;
&lt;li data-pm-slice=&quot;1 1 []&quot;&gt;로드 밸런싱을 사용한다면 mod_jk 도 설치 해야한다. (이 경우는 yum update 를 하는게 아니라면 workers.properties 파일을 생성하고 거기에 로드밸런싱 설정 값을 넣어 줘야 한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;적용하는데 어렵지는 않지만 서비스 중인 서버라면 서비스가 멈추지 않도록 계획을 잘 짜야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. Apache 버전 확인&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;현재 설치된 Apache 버전 확인:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;httpd -v
httpd -V&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;출력 예시:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Server version: Apache/2.4.55 (Unix)
Server built:   Jan 17 2023 16:57:1&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. Apache 다운로드 및 설치 준비&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;1. Apache 소스 다운로드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Apache 최신 버전을 &lt;/span&gt;&lt;a href=&quot;https://downloads.apache.org/httpd/&quot;&gt;&lt;span&gt;Apache 공식 다운로드 페이지&lt;/span&gt;&lt;/a&gt;&lt;span&gt;에서 다운로드.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;wget https://downloads.apache.org/httpd/httpd-2.4.60.tar.gz&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&lt;span&gt;&lt;b&gt;서버로 파일 전송&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;다운로드한 파일을 서버로 전송 (SCP 또는 SFTP 사용 가능):&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;scp httpd-2.4.60.tar.gz user@&amp;lt;서버IP&amp;gt;:/usr/local/src&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;&lt;span&gt;&lt;b&gt;압축 해제 및 디렉토리 이동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;cd /usr/local/src
tar -xvf httpd-2.4.60.tar.gz
cd httpd-2.4.60&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;4. 필수 패키지 설치&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;yum groupinstall -y &quot;Development Tools&quot;
yum install -y gcc gcc-c++ make pcre pcre-devel expat expat-devel&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;3. Apache APR 및 APR-Util 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;1. 다운로드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;wget https://downloads.apache.org/apr/apr-1.7.5.tar.gz
wget https://downloads.apache.org/apr/apr-util-1.6.3.tar.gz&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&lt;span&gt;&lt;b&gt;압축 해제 및 이동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;tar -xvf apr-1.7.5.tar.gz
tar -xvf apr-util-1.6.3.tar.gz
mv apr-1.7.5 srclib/apr
mv apr-util-1.6.3 srclib/apr-util&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;&lt;span&gt;&lt;b&gt;구조 확인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;ls srclib&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;출력 예시:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;apr
apr-util&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4. Apache 컴파일 및 설치&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;./configure&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./configure --prefix=/usr/local/apache2 --enable-so --enable-ssl --with-mpm=event --with-included-apr&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;컴파일 및 설치&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;make
make install&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;설치 확인&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/usr/local/apache2/bin/httpd -v&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;5. 포트 변경 후 테스트&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;1. 포트 변경&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;/usr/local/apache2/conf/httpd.conf&lt;/span&gt;&lt;span&gt; 파일에서 &lt;/span&gt;&lt;span&gt;Listen&lt;/span&gt;&lt;span&gt; 값을 수정:&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Listen 8081&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&lt;span&gt;&lt;b&gt;Apache 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/usr/local/apache2/bin/apachectl start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;&lt;span&gt;&lt;b&gt;테스트&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;curl -I http://127.0.0.1:8081&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;출력 예시:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;6. 기존 Apache에서 새 Apache로 전환&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;1. 기존 Apache 중지&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;systemctl stop httpd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&amp;nbsp;&lt;span&gt;포트 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;/usr/local/apache2/conf/httpd.conf&lt;/span&gt;&lt;span&gt;에서 &lt;/span&gt;&lt;span&gt;Listen&lt;/span&gt;&lt;span&gt; 값을 &lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;span&gt;으로 변경:&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Listen 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;&lt;span&gt;&lt;b&gt;새 Apache 재시작&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/usr/local/apache2/bin/apachectl restart&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;&lt;span&gt;&lt;b&gt;테스트&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;curl -I http://127.0.0.1&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;7. 필요 없는 파일 정리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;rm -rf /usr/local/src/httpd-2.4.60
rm -rf /usr/local/src/httpd-2.4.60.tar.gz&lt;/code&gt;&lt;/pre&gt;</description>
      <category>컴퓨터 과학 [CS]</category>
      <category>apache</category>
      <category>apache 2.4.55</category>
      <category>apache 2.4.60</category>
      <category>centos 7.9.2009</category>
      <category>아파치</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/49</guid>
      <comments>https://bob-data.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 26 Dec 2024 21:33:12 +0900</pubDate>
    </item>
    <item>
      <title>[GIT] Git Flow - 가장 쉬운 깃 브랜치 전략 (생애 첫 기술 발표 영상)</title>
      <link>https://bob-data.tistory.com/48</link>
      <description>&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=1EFkftPJSR8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/gDbTh/hyYqNu6YDz/boKuUu5Mn9MYk3imRCBDTK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/z8zAn/hyYudllfln/yRnL7tuhfQkSy9K46pq5d0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[코드팩토리 IGNITE 2차] Git을 활용한 코드 협업, 근데 이제 충돌을 곁들인.. (강승준)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/1EFkftPJSR8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Git Flow란 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git Flow&lt;/b&gt;는 소프트웨어 개발 프로세스에서 &lt;b&gt;효율적인 브랜치 관리와 협업&lt;/b&gt;을 돕는 브랜치 전략입니다. 개발, 테스트, 배포 등의 단계에서 코드의 안정성을 확보하고, 여러 개발자 간 협업을 체계적으로 할 수 있게 해줍니다. &lt;b&gt;Git Flow&lt;/b&gt;는 &lt;b&gt;feature&lt;/b&gt;, &lt;b&gt;develop&lt;/b&gt;, &lt;b&gt;release&lt;/b&gt;, &lt;b&gt;hotfix&lt;/b&gt;, &lt;b&gt;main&lt;/b&gt;(또는 &lt;code&gt;prod&lt;/code&gt;)와 같은 브랜치 구조를 사용하여 명확한 워크플로우를 제공합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Git Flow의 주요 브랜치 규칙&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow는 다섯 가지 주요 브랜치로 작업을 나누며, 각 브랜치는 특정 목적과 규칙을 가지고 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) &lt;code&gt;main&lt;/code&gt; 브랜치 (&lt;code&gt;prod&lt;/code&gt;)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적:&lt;/b&gt; 운영 서버에 배포되는 &lt;b&gt;안정적인 코드&lt;/b&gt;를 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;규칙:&lt;/b&gt; &lt;code&gt;main&lt;/code&gt;에는 항상 릴리스된 코드만 포함되어야 하며, 직접 작업이 아닌 다른 브랜치(&lt;code&gt;release&lt;/code&gt;, &lt;code&gt;hotfix&lt;/code&gt;)에서 병합됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) &lt;code&gt;develop&lt;/code&gt; 브랜치&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적:&lt;/b&gt; 배포 준비를 위한 &lt;b&gt;다음 릴리스&lt;/b&gt; 버전을 개발합니다. 새로운 기능이 모두 &lt;code&gt;develop&lt;/code&gt;에서 통합되고 테스트됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;규칙:&lt;/b&gt; &lt;code&gt;develop&lt;/code&gt;에는 &lt;b&gt;반드시 &lt;code&gt;prod&lt;/code&gt;에 배포할 예정인 코드&lt;/b&gt;만 병합해야 합니다. 아직 검증되지 않은 기능이나 임시 코드가 들어가면 안 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) &lt;code&gt;feature&lt;/code&gt; 브랜치&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적:&lt;/b&gt; 개별 기능 또는 버그 수정을 개발합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;규칙:&lt;/b&gt; 항상 &lt;b&gt;&lt;code&gt;develop&lt;/code&gt; 브랜치&lt;/b&gt;에서 파생되어 개발하고, 완료되면 다시 &lt;code&gt;develop&lt;/code&gt;에 병합합니다. 브랜치 이름은 &lt;code&gt;feature/&lt;/code&gt;로 시작하며, 기능을 설명하는 이름이나 이슈 번호를 붙여 일관성을 유지합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;code&gt;feature/DS-212&lt;/code&gt;, &lt;code&gt;feature/user-authentication&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4) &lt;code&gt;release&lt;/code&gt; 브랜치&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적:&lt;/b&gt; 배포 준비가 완료된 코드를 마지막으로 검증하고, 버그 수정 및 안정화 작업을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;규칙:&lt;/b&gt; &lt;code&gt;develop&lt;/code&gt; 브랜치에서 생성하며, 모든 검증 작업이 끝나면 &lt;code&gt;main&lt;/code&gt;과 &lt;code&gt;develop&lt;/code&gt;에 병합합니다. 브랜치 이름은 &lt;code&gt;release/&lt;/code&gt;로 시작하고 버전 번호를 포함합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;code&gt;release/v1.0.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5) &lt;code&gt;hotfix&lt;/code&gt; 브랜치&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적:&lt;/b&gt; 운영 중인 &lt;code&gt;main&lt;/code&gt;에서 긴급한 버그를 수정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;규칙:&lt;/b&gt; &lt;b&gt;&lt;code&gt;main&lt;/code&gt;&lt;/b&gt; 브랜치에서 파생되어 작업하고, 수정이 완료되면 &lt;code&gt;main&lt;/code&gt;과 &lt;code&gt;develop&lt;/code&gt;에 모두 병합합니다. 브랜치 이름은 &lt;code&gt;hotfix/&lt;/code&gt;로 시작합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;code&gt;hotfix/loginError&lt;/code&gt;, &lt;code&gt;hotfix/payment-issue&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 왜 Git Flow를 지켜야 하는가?&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;명확한 워크플로우:&lt;/b&gt; 각 브랜치의 역할과 작업 흐름이 명확하게 구분되므로, 협업 시 발생할 수 있는 혼란을 방지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 안정성 확보:&lt;/b&gt; &lt;code&gt;develop&lt;/code&gt;과 &lt;code&gt;release&lt;/code&gt;를 통한 검증 프로세스를 거치므로, 배포되는 코드의 안정성을 보장할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;독립적인 기능 개발:&lt;/b&gt; &lt;code&gt;feature&lt;/code&gt; 브랜치를 통해 독립적으로 기능을 개발하고 테스트할 수 있습니다. 다른 기능에 영향을 주지 않고 필요한 기능만 선택적으로 배포할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;긴급 대응:&lt;/b&gt; &lt;code&gt;hotfix&lt;/code&gt; 브랜치를 통해 운영 환경에서 발생한 긴급한 문제를 신속하게 해결하고, 배포할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Git Flow 관리 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow를 제대로 관리하기 위해서는 몇 가지 원칙과 규칙을 따라야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 브랜치의 명확한 규칙 설정&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브랜치 이름에 접두사를 붙여 역할을 명확히 구분합니다 (&lt;code&gt;feature/&lt;/code&gt;, &lt;code&gt;hotfix/&lt;/code&gt;, &lt;code&gt;release/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;일관성 있는 브랜치 네이밍 규칙은 작업의 목적과 상태를 쉽게 파악할 수 있도록 돕습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 병합 전략&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;feature&lt;/code&gt; &amp;rarr; &lt;code&gt;develop&lt;/code&gt; 병합:&lt;/b&gt; 기능 개발이 완료되면 &lt;code&gt;develop&lt;/code&gt; 브랜치에 병합하여 테스트합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;develop&lt;/code&gt; &amp;rarr; &lt;code&gt;release&lt;/code&gt; 병합:&lt;/b&gt; 배포 준비가 완료되면 &lt;code&gt;release&lt;/code&gt; 브랜치를 만들어 최종 검증을 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;release&lt;/code&gt; &amp;rarr; &lt;code&gt;main&lt;/code&gt; 병합:&lt;/b&gt; &lt;code&gt;release&lt;/code&gt; 브랜치의 최종 검증이 완료되면 &lt;code&gt;main&lt;/code&gt;에 병합하고, 배포합니다. 동시에 &lt;code&gt;release&lt;/code&gt; 브랜치를 &lt;code&gt;develop&lt;/code&gt;에도 병합하여 최신 상태를 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;hotfix&lt;/code&gt; &amp;rarr; &lt;code&gt;main&lt;/code&gt; 및 &lt;code&gt;develop&lt;/code&gt; 병합:&lt;/b&gt; 운영 환경의 긴급한 버그를 수정할 때는 &lt;code&gt;hotfix&lt;/code&gt; 브랜치를 만들어 작업하고, 수정이 완료되면 &lt;code&gt;main&lt;/code&gt;과 &lt;code&gt;develop&lt;/code&gt;에 병합하여 두 브랜치의 싱크를 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) 커밋 관리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커밋 메시지를 명확하게 작성하여 변경 사항을 추적할 수 있도록 합니다. 예를 들어, 이슈 번호와 함께 &quot;Fix login error (issue #123)&quot;와 같이 작성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기능 단위로 커밋:&lt;/b&gt; 각 커밋은 가능한 작은 기능 단위로 작성하여, 필요 시 선택적으로 병합하거나 되돌릴 수 있도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4) CI/CD와 연계&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Continuous Integration/Continuous Deployment (CI/CD)&lt;/b&gt; 도구를 활용하여 &lt;code&gt;develop&lt;/code&gt;, &lt;code&gt;release&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt; 브랜치에 병합될 때 자동으로 테스트를 실행하고, 코드 품질을 검증합니다. 이를 통해 버그를 조기에 발견하고 배포 안정성을 확보할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Git Flow의 한계와 보완점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;복잡성:&lt;/b&gt; 브랜치가 많아질 수 있으므로, 브랜치 관리가 복잡해질 수 있습니다. 이를 보완하려면 팀원 간의 명확한 규칙과 커뮤니케이션이 중요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동기화:&lt;/b&gt; &lt;code&gt;develop&lt;/code&gt;과 &lt;code&gt;main&lt;/code&gt; 브랜치의 싱크를 주기적으로 맞춰야 하며, 특히 긴급 수정(&lt;code&gt;hotfix&lt;/code&gt;) 후에는 이 작업을 꼭 수행해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git Flow&lt;/b&gt;는 소프트웨어 개발의 모든 단계에서 발생하는 다양한 요구 사항을 충족하기 위한 체계적인 브랜치 전략입니다. 이 방식의 핵심은 &lt;b&gt;각 브랜치의 역할을 명확히 규정하고, 이를 엄격히 지키는 것&lt;/b&gt;입니다. 이렇게 함으로써 협업 과정에서 혼란을 줄이고, 안정적인 코드를 배포할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow를 통해 브랜치 관리가 체계적으로 이루어지면, 팀 전체의 개발 효율성이 높아지고, 배포 시 발생할 수 있는 위험을 최소화할 수 있습니다. &lt;b&gt;규칙을 지키는 것은 단순히 브랜치 관리의 편의를 넘어, 소프트웨어 품질을 높이는 가장 효과적인 방법&lt;/b&gt;입니다.&lt;/p&gt;</description>
      <category>Git</category>
      <category>branch</category>
      <category>git</category>
      <category>Git flow</category>
      <category>ignite</category>
      <category>발표</category>
      <category>코드팩토리</category>
      <category>코드협업</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/48</guid>
      <comments>https://bob-data.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 3 Oct 2024 13:22:25 +0900</pubDate>
    </item>
    <item>
      <title>[javascript] var, let, const 비교 및 사용법</title>
      <link>https://bob-data.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현대적인 JavaScript 개발에서는 &lt;b&gt;const&lt;/b&gt;를 기본으로 사용하고, 값이 변경되어야 하는 경우에만 &lt;b&gt;let&lt;/b&gt;을 사용한다. var는 스코프와 호이스팅 문제로 인해 사용을 지양하는 것이 일반적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;const의 사용 권장&lt;/b&gt;: 불변성을 유지하여 코드의 안정성과 예측 가능성을 높인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;let의 보조적 사용&lt;/b&gt;: 변경이 필요한 변수에 한해 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. var&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스코프(scope)&lt;/b&gt;: 함수 스코프를 가진다. 즉, &lt;b&gt;함수 내에서 선언된 변수는 함수 전체&lt;/b&gt;에서 접근 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호이스팅(hoisting)&lt;/b&gt;: 변수 선언이 스코프의 최상단으로 끌어올려짐. 그러나 초기화는 선언 위치에서 이루어지므로, 선언 이전에 변수를 참조하면 undefined를 반환.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재선언 가능&lt;/b&gt;: 동일한 스코프 내에서 같은 이름의 변수를 재선언할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;적절한 사용 상황&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;레거시 코드 유지보수&lt;/b&gt;: 기존에 작성된 ES5 이전의 코드나 레거시 시스템을 다룰 때 var를 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;함수 스코프 활용&lt;/b&gt;: 함수 내부 전체에서 변수가 필요할 때 var를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1726635469765&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex1
function example() {
  console.log(a); // undefined
  var a = 1;
  console.log(a); // 1
}

// ex2
function example() {
  if (true) {
    var message = 'Hello';
  }
  console.log(message); // 'Hello'
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. let&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스코프(scope)&lt;/b&gt;: 블록 스코프를 가진다. {}로 감싸진 블록 내에서만 유효.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호이스팅(hoisting)&lt;/b&gt;: 선언은 호이스팅되지만, 초기화는 호이스팅되지 않는다. 따라서 선언 이전에 변수를 참조하면 ReferenceError가 발생.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재선언 불가&lt;/b&gt;: 동일한 스코프 내에서 같은 이름의 변수를 재선언할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;적절한 사용 상황&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;변경 가능한 변수&lt;/b&gt;: 값이 변경될 수 있는 변수를 선언할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;블록 스코프 필요 시&lt;/b&gt;: 조건문, 반복문 등의 블록 내에서 변수가 필요할 때 let을 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1726635541066&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex1
{
  console.log(b); // ReferenceError
  let b = 2;
  console.log(b); // 2
}

// ex2
for (let i = 0; i &amp;lt; 5; i++) {
  // i는 이 블록 내에서만 유효.
}
console.log(i); // ReferenceError: i is not defined&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. const&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스코프(scope)&lt;/b&gt;: let과 동일하게 블록 스코프를 가진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상수 선언&lt;/b&gt;: 선언과 동시에 초기화해야 하며, 이후 값 변경이 불가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호이스팅(hoisting)&lt;/b&gt;: let과 동일하게 동작.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재선언 불가&lt;/b&gt;: 동일한 스코프 내에서 재선언 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;적절한 사용 상황&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상수 선언&lt;/b&gt;: 변경되지 않는 값을 선언할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체와 배열의 참조 고정&lt;/b&gt;: 객체나 배열을 선언하고 참조를 변경하지 않을 때 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1726635604088&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex1
{
  const c = 3;
  c = 4; // TypeError: Assignment to constant variable.
}

// ex2
const PI = 3.14;
PI = 3.1415; // TypeError: Assignment to constant variable.

const user = { name: 'Alice' };
user.name = 'Bob'; // 가능 (프로퍼티 변경)
user = {}; // TypeError: Assignment to constant variable.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 전역 변수와 지역 변수의 활용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;전역 변수&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;var로 선언된 전역 변수&lt;/b&gt;: 함수 밖에서 var로 선언하면 전역 객체의 프로퍼티로 추가된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;let과 const로 선언된 전역 변수&lt;/b&gt;: 스크립트의 최상위 레벨에서 선언되며, 전역 스코프를 가진다. 그러나 전역 객체의 프로퍼티는 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1726636164885&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var globalVar = 'var';
let globalLet = 'let';
const globalConst = 'const';

console.log(window.globalVar); // 'var' (브라우저 환경)
console.log(window.globalLet); // undefined
console.log(window.globalConst); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;지역 변수&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;함수 스코프 지역 변수&lt;/b&gt;: 함수 내에서 선언된 var, let, const는 함수 내부에서만 접근 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;블록 스코프 지역 변수&lt;/b&gt;: let과 const로 선언된 변수는 해당 블록 내에서만 유효.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1726636187664&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function scopeTest() {
  var a = 1;
  let b = 2;
  const c = 3;

  if (true) {
    var a = 4; // 같은 함수 스코프 내에서 재선언 및 값 변경
    let b = 5; // 새로운 블록 스코프
    const c = 6; // 새로운 블록 스코프
    console.log(a, b, c); // 4, 5, 6
  }

  console.log(a, b, c); // 4, 2, 3
}

scopeTest();&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Javascript</category>
      <category>const</category>
      <category>Javascript</category>
      <category>let</category>
      <category>var</category>
      <category>변수</category>
      <author>coder_</author>
      <guid isPermaLink="true">https://bob-data.tistory.com/47</guid>
      <comments>https://bob-data.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 18 Sep 2024 14:11:23 +0900</pubDate>
    </item>
  </channel>
</rss>