<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Free</title>
    <link>https://seomj74.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 01:49:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>seomj</managingEditor>
    <image>
      <title>Free</title>
      <url>https://tistory1.daumcdn.net/tistory/3869291/attach/5cc4b6f695e14880818c54d1b9c86af8</url>
      <link>https://seomj74.tistory.com</link>
    </image>
    <item>
      <title>[Monitoring]LPG(Loki + Prometheus + Grafana에서 Loki) (1)</title>
      <link>https://seomj74.tistory.com/396</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 모니터링 구현까지 진행했다. 3년 전 모니터링 환경을 구축해 봤던 경험이 있지만 거의 처음이라고 봐도 될 정도였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 글 하나에 전부 적으려고 했으나... 양이 많아질 거 같아 3개에 거쳐 포스팅하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 프로젝트 구조에 대해 가볍게 설명한 뒤, LPG 중 Loki에 대해 적어보고자 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 프로젝트 인프라 구조는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1847&quot; data-origin-height=&quot;1217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca8Ibk/dJMcahKFevD/xkKXBtIgPXp35dRatP4ef0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca8Ibk/dJMcahKFevD/xkKXBtIgPXp35dRatP4ef0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca8Ibk/dJMcahKFevD/xkKXBtIgPXp35dRatP4ef0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca8Ibk%2FdJMcahKFevD%2FxkKXBtIgPXp35dRatP4ef0%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;750&quot; height=&quot;494&quot; data-origin-width=&quot;1847&quot; data-origin-height=&quot;1217&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;AWS EC2(t2.xlarge): Main Server&lt;/li&gt;
&lt;li&gt;AWS EC2(t2.micro): Mock Server&lt;/li&gt;
&lt;li&gt;Mac mini: Monitoring Server (+ GitLab Runner)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitLab Runner를 Monitoring Server에 둔 이유:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Build 과정에서 CPU와 메모리를 사용하기 때문에 Main Server는 적합하지 않다고 판단했다. 또한 성능면에서도 Mac Mini가 유리하다. 만약 GitLab Runner에서 문제가 생기더라도 Main Server와 Mock Server에는 영향이 없기 때문에 서비스는 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술 선정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker-compose&lt;/li&gt;
&lt;li&gt;GitLab CI/CD&lt;/li&gt;
&lt;li&gt;LPG
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promtail&lt;/li&gt;
&lt;li&gt;cAdvisor, Node Exporter&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 굳이 Kubernetes를 도입하진 않았다. 단일 노드에서 단일 모놀리식 아키텍처의 서버를 띄우는데 의미가 없을 거 같다는결론을 내렸다. 그래서 Kubernetes 대신 Docker Compose를 선택하여 컨테이너를 띄웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitLab CI/CD를 선택한 이유는 리소스 최적화가 가장 컸다. GitLab을 사용해야 했으며, Jenkins를 띄워 리소스를 낭비하는 게 불필요하다고 생각이 들었다. 모놀리식 구조이기 때문에 더욱 더 GitLab CI/CD가 적합하다 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링은 LPG 조합으로 사용했다. Prometheus를 통해 메트릭을 수집했고, Loki를 통해 로그를 수집했다. 에이전트로는 Node Exporter(Server), cAdvisor(Container), Promtail을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로그&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Loki&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki config의 경우, 처음 다뤄봐서 주석을 달아가며 적용했다. 주석을 참고하면 아마 대부분 이해될 것이라 생각한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774921075754&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;auth_enabled: false

server:
  http_listen_port: 3100 # Grafana와 Promtail 사용


### 공통 설정: 반복되는 경로 설정을 한 번에 관리하는 곳 
common:
  path_prefix: /loki # 모든 데이터가 저장될 기본 루트 폴더 
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1 # 1개만 저장
  ring:
    kvstore:
      store: inmemory

### 데이터 구조 설정: 데이터를 어떤 방식으로 저장하고 인덱싱할지
schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb # 로그 데이터의 인덱싱 방식
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

### 제한 및 보관 정책: 권한과 데이터 수명 결정
limits_config:
  allow_structured_metadata: true
  retention_period: ${LOKI_RETENTION}

### 집행자: retention_period가 지나면 실제로 디스크에서 파일을 삭제하는 역할
compactor:
  working_directory: /loki/compactor
  compaction_interval: 2h        # 삭제 및 압축 확인 주기
  retention_enabled: true         # 보관 주기 정책 활성화
  retention_delete_delay: 2h      # 삭제 대기 시간
  retention_delete_worker_count: 4 # 삭제 작업 처리량
  delete_request_store: filesystem

# ruler는 알림 규칙 사용 시에만 활성화
# ruler:
#   storage:
#     type: local
#     local:
#       directory: /loki/rules
#   rule_path: /loki/rules-temp
#   ring:
#     kvstore:
#       store: inmemory
#   enable_api: true&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Promtail (Agent)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monitor 서버와 Main 서버(Mock 서버)에 에이전트를 배포했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monitor&lt;/p&gt;
&lt;pre id=&quot;code_1774921165656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/promtail/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push
    backoff_config:
      min_period: 500ms
      max_period: 5m
      max_retries: 10

scrape_configs:
  # 시스템 로그
  - job_name: varlogs
    static_configs:
      - targets: [localhost]
        labels:
          job: varlogs
          server_type: monitor
          __path__: /var/log/**/*.log
    pipeline_stages:
      # 불필요한 로그 drop
      - drop:
          expression: '(?i)\b(/healthz?|/ready|/readiness|/live|/liveness|heartbeat|\bpong\b|\bping\b)\b'
      - drop:
          expression: '(?i)\b(level=debug|level=trace|&quot;level&quot;\s*:\s*&quot;(debug|trace)&quot;)\b'
      - drop:
          expression: '(?i)\bsession_monitor_receiver_client\s+connect_failed\b'
      # 민감 정보 마스킹
      - replace:
          expression: '(?i)(authorization:\s*bearer\s+)[^\s&quot;]+'
          replace: '${1}***'
      - replace:
          expression: '(?i)(token=)[^\s&amp;amp;&quot;]+'
          replace: '${1}***'
      - replace:
          expression: '(?i)(&quot;password&quot;\s*:\s*&quot;)[^&quot;]+(&quot;)' 
          replace: '${1}***${2}'
      - replace:
          expression: '(?i)(&quot;secret&quot;\s*:\s*&quot;)[^&quot;]+(&quot;)' 
          replace: '${1}***${2}'

  # docker 컨테이너 로그
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
        filters:
          - name: label
            values: [&quot;promtail.collect=true&quot;]
    relabel_configs:
      - target_label: job
        replacement: docker
      - target_label: server_type
        replacement: monitor
      - source_labels: [__meta_docker_container_log_stream]
        target_label: stream
      - source_labels: [__meta_docker_container_name]
        regex: &quot;/?(.+)&quot;
        target_label: service_name
    
    pipeline_stages:
      - docker: {}

      - drop:
          expression: '(?i)\b(/healthz?|/ready|/readiness|/live|/liveness|heartbeat|\bpong\b|\bping\b)\b'
      - drop:
          expression: '(?i)\b(level=debug|level=trace|&quot;level&quot;\s*:\s*&quot;(debug|trace)&quot;)\b'
      - drop:
          expression: '(?i)\bsession_monitor_receiver_client\s+connect_failed\b'
      - replace:
          expression: '(?i)(authorization:\s*bearer\s+)[^\s&quot;]+'
          replace: '${1}***'
      - replace:
          expression: '(?i)(token=)[^\s&amp;amp;&quot;]+'
          replace: '${1}***'
      - replace:
          expression: '(?i)(&quot;password&quot;\s*:\s*&quot;)[^&quot;]+(&quot;)' 
          replace: '${1}***${2}'
      - replace:
          expression: '(?i)(&quot;secret&quot;\s*:\s*&quot;)[^&quot;]+(&quot;)' 
          replace: '${1}***${2}'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;server: Promtail 자체의 동작 설정&lt;/li&gt;
&lt;li&gt;positions: Promtail이 로그를 어디까지 읽었는지 기록하는 파일 위치&lt;/li&gt;
&lt;li&gt;clients: 수집한 로그를 보낼 Loki 서버의 주소&lt;/li&gt;
&lt;li&gt;scrape configs: 로그 수집 및 가공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;varlogs는 시스템 로그
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 로그에 job: varlogs, server_type: monitor라는 라벨을 붙여 Loki에서 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;docker는 컨테이너 로그
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker_sd_configs를 통해 실행 중인 컨테이너를 자동으로 찾음&lt;/li&gt;
&lt;li&gt;promtail.collect=true가 설정된 컨테이너의 로그만 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;pipeline stages
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker: {}: Docker의 JSON 형태 로그에서 실제 메시지 내용(time, stream, log 등)을 파싱하여 구조화 &amp;nbsp;&lt;/li&gt;
&lt;li&gt;drop: 불필요한 로그 삭제&lt;/li&gt;
&lt;li&gt;replace: 민감 정보 마스킹&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main&lt;/p&gt;
&lt;pre id=&quot;code_1774921185482&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/promtail/positions.yaml

clients:
  - url: http://${MONITOR_DOMAIN}:${LOKI_PORT}/loki/api/v1/push
    backoff_config:
      min_period: 500ms
      max_period: 5m
      max_retries: 10

scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
        filters:
          - name: label
            values: [&quot;promtail.collect=true&quot;] 
    
    relabel_configs:
      - target_label: job
        replacement: docker
      - target_label: server_type
        replacement: main
      - source_labels: [__meta_docker_container_log_stream]
        target_label: stream
      - source_labels: [__meta_docker_container_name]
        regex: &quot;/?(.+)&quot;
        target_label: service_name
      - source_labels: [__meta_docker_container_id]
        regex: &quot;(.+)&quot;
        target_label: __path__
        replacement: /var/lib/docker/containers/$1/$1-json.log
    
    pipeline_stages:
      # Docker JSON 로그 파싱 &amp;rarr; 실제 로그 메시지 추출
      - docker: {}
      # 불필요한 로그 drop
      - drop:
          expression: '(?i)\b(/healthz?|/ready|/readiness|/live|/liveness|heartbeat|\bpong\b|\bping\b)\b'
      - drop:
          expression: '(?i)\b(level=debug|level=trace|&quot;level&quot;\s*:\s*&quot;(debug|trace)&quot;)\b'
      # 민감 정보 마스킹
      - replace:
          expression: '(?i)(authorization:\s*bearer\s+)[^\s&quot;]+'
          replace: '${1}***'
      - replace:
          expression: '(?i)(token=)[^\s&amp;amp;&quot;]+'
          replace: '${1}***'
      - replace:
          expression: '(?i)(&quot;password&quot;\s*:\s*&quot;)[^&quot;]+(&quot;)' 
          replace: '${1}***${2}'
      - replace:
          expression: '(?i)(&quot;secret&quot;\s*:\s*&quot;)[^&quot;]+(&quot;)' 
          replace: '${1}***${2}'&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;monitor와 다른 점만 언급하도록 하겠다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;relabel_configs에서 로그 파일의 실제 경로를 직접 지정&lt;/li&gt;
&lt;li&gt;Docker 컨테이너의 고유 ID($1)를 추출하여, Docker가 로그를 저장하는 표준 경로인 /var/lib/docker/containers/[ID]/[ID]-json.log를 직접 가리키도록 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker compose&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose 설정에서도 유의해야 할 점들이 있어 함께 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;labels 태그를 추가해서 promtail이 로그를 수집해야 하는 컨테이너를 지정해줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775029085449&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  loki:
    image: grafana/loki:3.6.7
    container_name: loki
    labels:
      - &quot;promtail.collect=true&quot;
...&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volumes 설정의 경우, monitor와 main에 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monitor 서버는 시스템 로그 중심, Main 서버는 Docker 컨테이너 로그 경로를 직접 제어하는 방식이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;monitor&lt;/p&gt;
&lt;pre id=&quot;code_1775206412140&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  promtail:
    image: grafana/promtail:3.6.7
    container_name: monitor-promtail
    restart: always
    depends_on:
      - loki
    volumes:
      - /var/log:/var/log:ro
      - ./promtail-config.yaml:/etc/promtail/config.yml
      - ./promtail-data:/tmp/promtail
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command: -config.file=/etc/promtail/config.yml
    networks:
      - monitor-net&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/var/log: 시스템 로그(/var/log/auth.log, syslog 등)를 수집하기 위해 연결&lt;/li&gt;
&lt;li&gt;./promtail-config.yaml: Promtail 설정 파일 전달&lt;/li&gt;
&lt;li&gt;./promtail-data: 로그를 어디까지 읽었는지 기록(positions.yaml)하는 저장소&lt;/li&gt;
&lt;li&gt;&lt;span&gt;/var/run/docker.sock: Docker 엔진과 통신하여 컨테이너 목록 및 메타데이터를 가져옴&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main&lt;/p&gt;
&lt;pre id=&quot;code_1775206421606&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  promtail:
    image: grafana/promtail:3.6.7
    container_name: main-promtail
    restart: always
    environment:
      MONITOR_DOMAIN: ${MONITOR_DOMAIN:-}
      LOKI_PORT: ${LOKI_PORT:-3100}
    command:
      - -config.file=/etc/promtail/config.yml
      - -config.expand-env=true
    volumes:
      - ./promtail-config.yaml:/etc/promtail/config.yml:ro
      - ./promtail-data:/tmp/promtail
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/var/lib/docker/containers: config에서 지정한 __path__의 실제 물리적 경로를 연결&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;트러블슈팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Promtail Job&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac mini 로컬 환경에서 Loki + Promtail로 로깅 시스템을 구성했다. 하지만 Grafana에서 확인했을 때 job 레이블에 varlogs만 존재하고 docker가 없었다. 즉, docker 컨테이너 로그가 수집되고 있지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 docker 컨테이너 로그가 수집되지 않는 사실을 몰랐다. 그저 job 레이블 누락 원인을 파악하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static_configs는 job 레이블을 명시하지만 docker_sd_configs는 job_name 값을 job 레이블로 자동 변환해주지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775655840235&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;relabel_configs:
  - target_label: job
    replacement: docker&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;하지만 여전히 동작하지 않았다. promtail 로그를 확인해보니 관련 로그가 없었기에 docker job 자체가 동작하지 않는 것을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 Mac 환경에서는 /var/lib/docker/containers가 호스트 Mac 파일 시스템에 존재하지 않는다는 것이었다.&amp;nbsp;Docker Desktop은 내부 Linux VM 위에서 동작해 컨테이너 로그 파일이 Mac 파일시스템에 존재하지 않았고, __path__로 지정한 경로가 실제로 존재하지 않았던 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 불필요한 볼륨 마운트를 제거했다.&lt;/p&gt;
&lt;pre id=&quot;code_1775656039036&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;volumes:
  - /var/lib/docker/containers:/var/lib/docker/containers:ro&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;그리고 __path__를 제거했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775656107392&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;relabel_configs:
  - target_label: job
    replacement: docker
  - target_label: server_type
    replacement: monitor
  - source_labels: [__meta_docker_container_log_stream]
    target_label: stream
  - source_labels: [__meta_docker_container_name]
    regex: &quot;/?(.+)&quot;
    target_label: service_name
  # __path__ 설정 제거&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;이후 Grafana에서 job=docker 레이블이 정상적으로 표시되고 Docker 컨테이너 로그가 Loki로 수집되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/396</guid>
      <comments>https://seomj74.tistory.com/396#entry396comment</comments>
      <pubDate>Wed, 8 Apr 2026 23:00:11 +0900</pubDate>
    </item>
    <item>
      <title>[Cloud]토스페이먼츠 하이브리드 클라우드 전환</title>
      <link>https://seomj74.tistory.com/395</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;원본&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.tech/article/payments-legacy-9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://toss.tech/article/payments-legacy-9&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771978967779&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;레거시 인프라 작살내고 하이브리드 클라우드 만든 썰&quot; data-og-description=&quot;오픈소스 기반 OpenStack 프라이빗 클라우드를 직접 구축해, 퍼블릭과 Active-Active 하이브리드 클라우드로 운영하며 자동화&amp;middot;모니터링&amp;middot;고가용성을 확보한 이야기를 들려드려요.&quot; data-og-host=&quot;toss.tech&quot; data-og-source-url=&quot;https://toss.tech/article/payments-legacy-9&quot; data-og-url=&quot;https://toss.tech/article/payments-legacy-9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bPkexp/dJMb87f3sVH/nyw8H3L6SycDqgR8AoX6R1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=1185_702_1692_840,https://scrap.kakaocdn.net/dn/Ryw2X/dJMb8PGtc5f/6yGO15eRT6924jftITngBK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=1185_702_1692_840,https://scrap.kakaocdn.net/dn/b8Rnc2/dJMb9lk4b4d/KS34KihA7xnncqolJLCqw0/img.png?width=2112&amp;amp;height=1228&amp;amp;face=0_0_2112_1228&quot;&gt;&lt;a href=&quot;https://toss.tech/article/payments-legacy-9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://toss.tech/article/payments-legacy-9&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bPkexp/dJMb87f3sVH/nyw8H3L6SycDqgR8AoX6R1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=1185_702_1692_840,https://scrap.kakaocdn.net/dn/Ryw2X/dJMb8PGtc5f/6yGO15eRT6924jftITngBK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=1185_702_1692_840,https://scrap.kakaocdn.net/dn/b8Rnc2/dJMb9lk4b4d/KS34KihA7xnncqolJLCqw0/img.png?width=2112&amp;amp;height=1228&amp;amp;face=0_0_2112_1228');&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;레거시 인프라 작살내고 하이브리드 클라우드 만든 썰&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오픈소스 기반 OpenStack 프라이빗 클라우드를 직접 구축해, 퍼블릭과 Active-Active 하이브리드 클라우드로 운영하며 자동화&amp;middot;모니터링&amp;middot;고가용성을 확보한 이야기를 들려드려요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;toss.tech&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/x2jep67D698?si=498UNuKTj7aLpn6J&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/x2jep67D698?si=498UNuKTj7aLpn6J&lt;/a&gt;&lt;/p&gt;
&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=x2jep67D698&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eoXyfH/dJMb9hCYlrp/eOm9Zh78lSKhM9Rk1KQtj0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=782_464_1136_552,https://scrap.kakaocdn.net/dn/hKyge/dJMb9eTMHi3/cuozXk9CpPc26EKkbFZXrk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=782_464_1136_552,https://scrap.kakaocdn.net/dn/oSdp7/dJMb88F1Vvz/nuM6uwkJHVOQOHkk8wZo40/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=782_464_1136_552&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;TMC25 | Engineering - 레거시 인프라 작살내고 하이브라우드 클라우드 만든 썰&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/x2jep67D698&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;내용 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Active-Active로 운영하는 하이브리드 클라우드를 만드는 것이 목표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관건은 프라이빗 클라우드를 어떻게 구성하느냐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;애자일한 인프라&lt;/li&gt;
&lt;li&gt;유연성&lt;/li&gt;
&lt;li&gt;오픈소스 기반&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; OpenStack&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcIyUr/dJMcaiPSwg9/7XMSKI0gKkHzSwmTjvrQSK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcIyUr/dJMcaiPSwg9/7XMSKI0gKkHzSwmTjvrQSK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcIyUr/dJMcaiPSwg9/7XMSKI0gKkHzSwmTjvrQSK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcIyUr%2FdJMcaiPSwg9%2F7XMSKI0gKkHzSwmTjvrQSK%2Fimg.jpg&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;734&quot; height=&quot;413&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;But, 시스템 엔지니어는 2명이며 OpenStack 경험이 없었다. OpenStack의 큰 단점은 운영 난이도가 높다는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3가지 고민&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 2명으로 프로덕션 레벨 운영이 가능한가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. K8S를 어떻게 구성할 것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 네트워크 민첩성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2명으로 프로덕션 레벨 운영이 가능한가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 직접 경험을 해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경험 축적
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3가지 버전의 OpenStack을 각각 수십 번씩 설치해보고 장애 시나리오를 재현&lt;/li&gt;
&lt;li&gt;이를 통해 OpenStack 아키텍처에 대한 이해도를 높일 수 있도록 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;튜닝&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커뮤니티 버전을 그대로 사용해서는 니즈를 만족시킬 수 없었다.&lt;/li&gt;
&lt;li&gt;소스를 수정해서 필요한 로그들을 남길 수 있도록 했다.&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;모든 자원의 라이프 사이클 관리를 Ansible과 Terraform 코드를 이용해 자동화&lt;/li&gt;
&lt;li&gt;표준 골든 이미지를 통해 신규 인스턴스 생성까지 10초 이내에 가능하도록 구성&lt;/li&gt;
&lt;li&gt;CMDB에 수집하여 봇을 통해 언제든 쉽게 조회할 수 있게 구현&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;장애 상황에 대한 대응&lt;/li&gt;
&lt;li&gt;필요하다고 판단한 모든 메트릭들을 수집(Zabbix, Prometheus, Mimir 등)하고, Grafana를 통해 시각화, 알람을 빠르게 확인&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;어떤 식으로 빠르게 조치할 수 있을 것인가&lt;/li&gt;
&lt;li&gt;완전히 독립된 OpenStack 클러스터 2개를 구성하여 Active-Active로 운영하다가 장애가 있을 경우 트래픽 인입을 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서로 간의 의존성을 배제한 클러스터 2개를 만드는 것을 목적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;K8S를 어떻게 구성할 것인가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 관리 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 관리가 가능하면서도 쉽고 빠르게 운영이 가능한 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; Cluster API&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;K8S 클러스터 자체를 K8S의 리소스로 관리하는 툴킷&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K8S 클러스터를 선언적으로 관리 가능&lt;/li&gt;
&lt;li&gt;롤링 업데이트 등 클러스터의 라이프 사이클 관리를 마치 애플리케이션을 관리하는 것처럼 쉽고 빠르게 처리&lt;/li&gt;
&lt;li&gt;CAPO, OCCM 같은 컴포넌트 등과 연동하여 K8S를 사용하는 데 필요한 OpenStack의 인프라 자원들(VM노드, 로드밸런스 등)이 자동으로 생성되고 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;네트워크 민첩성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 레벨의 민첩성과 유연성 확보 고민&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; NetDevOps&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Configuration 모듈화: 신규 서비스가 오픈될 경우 새로운 모듈을 추가하고, 특정 서비스가 종료될 경우 해당 모듈만 제거하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python 코드로 템플릿화,&amp;nbsp;각 장비에 맞는 파라미터를 적용해 배포하는 형태로 관리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 독립된 Pod(Pod: 서버, 네트워크, 스토리지 등이 모인 인프라 관점에서 하나의 완결된 서비스 단위)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod1, Pod2: 프로덕션 서비스용&lt;/li&gt;
&lt;li&gt;Pod0: 관리 목적 클러스터(Cluster API, 모니터링 시스템, 보안 툴, 쿼럼 노드 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Active-Active 서비스 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenStack 인프라 구성을 AWS와 최대한 유사하게 만들고, Active-Active로 운영하는 서비스 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라우드 리소스 매핑: ALB는 F5사의 BigIP, NLB는 OpenStack의 Octavia, EC2는 OpenStack의 Nova, ECR은 Harbor, EKS는 Kubernetes Cluster API&lt;/li&gt;
&lt;li&gt;배포 시스템 확장: 빌드된 컨테이너 이미지를 AWS ECR과 OpenStack Harbor 양쪽으로 업로드하고, AWS EKS 클러스터 2개와 OpenStack Kubernetes Service(OKS) 클러스터 2개에 동시 배포
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Istio와 Argo Rollout 기반으로 트래픽 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라우드 부하분산: AWS Route53을 통해 AWS IP와 OpenStack IP 동시 노출(DNS 기반 부하 분산 방식)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객사 방화벽에 OpenStack IP를 미리 등록해야 하는 문제 &amp;rarr; Global Accelerator 구성을 변경해 트래픽 부하 분산에 우선 적용&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로그 수집 및 분석: 로그 저장 및 분석을 위한 Elasticsearch 클러스터를 AWS와 OpenStack에 각각 구성하고, Elasticsearch의 Cross Cluster 기능을 이용해 전용선 구간의 트래픽 병목을 해결&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenStack에서 AWS 리소스 접근&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Roles Anywhere&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS SDK의 임시 토큰 획득 방법을 이용해서 구현&lt;/li&gt;
&lt;li&gt;AWS SDK 제한으로 Nova와 OKS 컨테이너에서 서로 다른 방법으로 구현&lt;/li&gt;
&lt;li&gt;Nova
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS SDK가 Crendential 프로세서를 통해 Roles Anywhere Gateway 서비스에 권한 요청&lt;/li&gt;
&lt;li&gt;Roles Anywhere Gateway는 OpenStack Metadata를 조회해 유효한 요청인지와 할당된 AWS IAM Role을 확인&lt;/li&gt;
&lt;li&gt;AWS Roles Anywhere로부터 임시 토큰을 받아 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OKS
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod의 애플리케이션 컨테이너가 Tosspayments Sidecar 컨테이너를 통해 Roles Anywhere Gateway 서비스에 권한 요청&lt;/li&gt;
&lt;li&gt;Roles Anywhere Gateway는 Kubernetes Service Account 정보를 조회해 요청의 유효성과 할당된 AWS IAM Role 확인&lt;/li&gt;
&lt;li&gt;AWS Roles Anywhere로부터 임시 토큰을 받아 전달&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;AWS Roles Anywhere는 퍼블릭 서비스&lt;/li&gt;
&lt;li&gt;Session Policy를 이용해 발급된 임시 키가 허용된 네트워크에서만 사용 가능하도록 설정&lt;/li&gt;
&lt;/ul&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;배운 점 및 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스를 튜닝해보는 경험을 해봐야 겠다. 오픈소스를 제대로 이해하고, 원하는 대로 소스를 수정해서 튜닝해보며 역량을 키워보자는 생각을 했다. 더 나아가 오픈소스에 기여할 수 있다면 더 좋지 않을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zabbix, Mimir, Nova, Octavia 등 정말 다양한 오픈소스들이 존재한다는 것을 알게 되었다. 이것도 잘 알고 검색해서 찾는 능력이 있어야 필요한 걸 잘 찾아 쓸 수 있겠다. 많은 오픈소스들을 접하고 사용해보는 것도 중요한 거 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 Kubernetes에 CAPO, OCCM과 같은 다양한 컴포넌트들이 존재하는 걸 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 걸 설계하고, 구축하고, 테스트해보고.. 얼마나 걸렸을까. 대단하다고 느껴졌다. 나도 할 수 있을까. 언젠가 나도 능수능란하게 설계하고 도전해보는 인재가 되어 있기를.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 되게 잘해주신다고 느꼈다. 영상을 보고 글을 읽으며 막히는 구간이 없었다. 그렇다고 모든 걸 다 이해했다고는 못하겠다. 왜냐면 해보라고 하면 못 할 거 같으니.. 그래도 읽으면서 흐름을 놓치지 않았다. 이렇게 쉽게 설명해주는 것도 중요한 것 같다.&lt;/p&gt;</description>
      <category>Study/Cloud</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/395</guid>
      <comments>https://seomj74.tistory.com/395#entry395comment</comments>
      <pubDate>Wed, 25 Feb 2026 21:00:25 +0900</pubDate>
    </item>
    <item>
      <title>[Cloud]TossBank DevOps팀이 일하는 방식</title>
      <link>https://seomj74.tistory.com/394</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;같이 공부했던 사람들과 이야기하며 토스 CNI를 검색해보게 되었다. 그 과정에서 토스 컨퍼런스와 기술 블로그를 봤는데 그 중 가볍게 본 컨퍼런스 영상에 대해 인사이트를 얻었다. 때마침 스터디 방향성도 조정되어 이를 기록하고 생각을 나눠보고자 한다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;영상&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/_pz3CxNdXEU?si=7Tp9a_XbovFFYPyj&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://youtu.be/_pz3CxNdXEU?si=7Tp9a_XbovFFYPyj&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;TMC25 | Engineering - 작지만 강한 DevOps Team 운영하기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;#토스뱅크 #DevOps #TMC25토스뱅크는 500개 이상의 마이크로서비스로 구성된 서비스를 1,200만 사용자에게 제공합니다. 꽤나 큰 규모의 컨테이너 환경을 DevOps 엔지니어 3명이 운영해 왔어요. 적은 인&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/watch?v=_pz3CxNdXEU&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/dg0l1C/dJMb8VNqTJw/AAAAAAAAAAAAAAAAAAAAAN5IKCjsIVpWHyvPD_fzbzwPbV2eh39a5VQu6vtUOhnZ/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1772290799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=S0zO5Y9Ce8Q6oN%2FHi2%2BLNUQ%2Fzjc%3D&quot; data-og-url=&quot;https://www.youtube.com/watch?v=_pz3CxNdXEU&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=_pz3CxNdXEU&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/watch?v=_pz3CxNdXEU&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/dg0l1C/dJMb8VNqTJw/AAAAAAAAAAAAAAAAAAAAAN5IKCjsIVpWHyvPD_fzbzwPbV2eh39a5VQu6vtUOhnZ/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1772290799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=S0zO5Y9Ce8Q6oN%2FHi2%2BLNUQ%2Fzjc%3D');&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;TMC25 | Engineering - 작지만 강한 DevOps Team 운영하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#토스뱅크 #DevOps #TMC25토스뱅크는 500개 이상의 마이크로서비스로 구성된 서비스를 1,200만 사용자에게 제공합니다. 꽤나 큰 규모의 컨테이너 환경을 DevOps 엔지니어 3명이 운영해 왔어요. 적은 인&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;내용 정리&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;뭐 하는 팀인가?&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Platform Division: 서비스 운영을 위한 플랫폼과 인프라를 책임지는 조직&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Server Platform Department:&amp;nbsp;Server Plaform 팀과 DevOps 팀&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;DevOps 팀이 하는 일&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes 클러스터&lt;/li&gt;
&lt;li&gt;여러 내부 시스템들을 운영 및 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;DevOps 팀은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지원 조직 (무언가를 달성하는 것이 아니라 어떤 변화에도 준비되어 있는 상태를 유지하는 것이 목표)&lt;/li&gt;
&lt;li&gt;시스템 구조를 바꾸거나 새로운 기술을 도입할 때, 일상적인 업무들(빌드, 배포, 모니터링)의 생산성을 높이고 전체 시스템의 복잡성을 낮추는 것을 중요하게 생각한다.&lt;/li&gt;
&lt;li&gt;새로운 오류에 대응하거나 트래픽이 늘어나는 것과 같은 예외 상황에도 안정적으로 동작하고, 문제 원인을 분석하기 쉬운 시스템을 만들기 위해 노력한다.&lt;/li&gt;
&lt;li&gt;그 과정에서 보안이나 컨플라이언스를 준수한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;작으면 좋은가?&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;N사(4백만명, 9명)와 T사(9억명, 60명) 등의 사례를 보았을 때, 서비스 규모와 개발자 수가 비례할 필요는 없는 거 같다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 인원 수보다는 일하는 방식이 중요하다!&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbM82U/dJMcafMhC0Z/W1psIJh8V3I6pTbyZoJXw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbM82U/dJMcafMhC0Z/W1psIJh8V3I6pTbyZoJXw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbM82U/dJMcafMhC0Z/W1psIJh8V3I6pTbyZoJXw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbM82U%2FdJMcafMhC0Z%2FW1psIJh8V3I6pTbyZoJXw0%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;599&quot; height=&quot;305&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;무엇을 할 것인가?&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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&gt;계획은 문제가 예측 가능할 수록 효과적이다.&lt;/li&gt;
&lt;li&gt;DevOps 업무는 예측하기 어려운 경우가 대부분이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라이브 모니터링 시스템에 문제가 생겼다? &amp;rarr; 오늘 바로 해결해야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;미리 세워둔 계획이 많으면 상황에 맞춰서 대응하기가 어려워진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 우선순위를 유연하게 판단하고 빠르게 실행하는 데 집중한다. (&lt;/b&gt;Like Greedy 알고리즘)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;어떻게 할 것인가?&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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&gt;개인 중심 R&amp;amp;R?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인원이 적다보니 개인이 차지하는 비중이 커지게 된다.&lt;/li&gt;
&lt;li&gt;한 명이 자리를 비우게 되면 비는 부분이 크다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;팀 중심 R&amp;amp;R!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀원 모두가 서로 업무를 대체할 수 있도록 공유하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;장애 대응 당번은 나누지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠르게 해결하는 게 제일 중요하다.&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;장애보다는 속도가 덜 중요하다.&lt;/li&gt;
&lt;li&gt;요일별 운영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&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;자리나 전화로 이야기를 나눌 수도 있지만, 이것도 메신저로 공유한다.&lt;/li&gt;
&lt;li&gt;모든 사람들이 확인 가능하며, 시간이 지나도 검색이 가능하다.&lt;/li&gt;
&lt;li&gt;개인 메시지보다는 공유되는 채널에 남겨 달라고 부탁을 하기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;정말로 사람이 부족한가?&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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&gt;일을 떠올리는 속도 &amp;gt; 일을 처리하는 속도&lt;/li&gt;
&lt;li&gt;할 일이 정해져 있고, 그걸 나눠서 할 사람을 찾는다 &amp;rarr;&lt;span&gt; 사람이 필요할 수 있다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사람이 정해져 있고, 각자 할 일을 판단한다 &amp;rarr; 그만큼 필요한 일만 하게 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;우리의 현실은 후자에 가깝다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사람이 부족하다고 생각이 든다면, 일하는 방식에 대해서도 생각해봐야 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일이 많다면 중요한 일에 집중해야 한다. 이에 대한 기준이 명확해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 우리는 ROI (Return on investment) 비용 대비 효율을 중요하게 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하면 좋은 일들 중에 해야 하는 일은 적다고 생각한다. 효용이 비용보다 커야 한다. 이 크기는 수치화가 안되기 때문에 팀원들과 논의를 하거나 기댓값과 비슷하게 생각하는 것이 도움이 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcq6GX/dJMcafrWUyB/wWtKMPPaG2JXip8k07hotk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcq6GX/dJMcafrWUyB/wWtKMPPaG2JXip8k07hotk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcq6GX/dJMcafrWUyB/wWtKMPPaG2JXip8k07hotk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcq6GX%2FdJMcafrWUyB%2FwWtKMPPaG2JXip8k07hotk%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;600&quot; height=&quot;307&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;641&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;얼마나 기대할 수 있는지, 우리에게 그것이 필요한지&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;여러 계층에서 진행되어야 한다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; 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;b&gt;하면 좋은 일이라 판단&lt;/b&gt;&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;1227&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zuMo6/dJMcajnxtPK/Fyls32cqkD38a98LcIg9E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zuMo6/dJMcajnxtPK/Fyls32cqkD38a98LcIg9E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zuMo6/dJMcajnxtPK/Fyls32cqkD38a98LcIg9E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzuMo6%2FdJMcajnxtPK%2FFyls32cqkD38a98LcIg9E1%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;600&quot; height=&quot;290&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;593&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산 부하가 얼마나 되는지, 얼마나 개선할 수 있을지&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;공통 설정을 바꿔서 서비스들을 배포 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; 크진 않다&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 노드에 몰려서 서비스에 영향이 있었다면 &lt;b&gt;해야 하는 일이라 판단&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지 말아야 하는 일 != 하면 안 좋은 일&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sEFqm/dJMcagRWQTQ/SvymI5eJES5GD2jyrehdgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sEFqm/dJMcagRWQTQ/SvymI5eJES5GD2jyrehdgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sEFqm/dJMcagRWQTQ/SvymI5eJES5GD2jyrehdgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsEFqm%2FdJMcagRWQTQ%2FSvymI5eJES5GD2jyrehdgK%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;598&quot; height=&quot;224&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;잘못된 내용은 아니다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 이런 기술을 써보고 싶다고 건의. 하지만 ROI가 별로인 상황 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ROI가 별로니까 하지 말아야 하나? 고객이 개발자라면 고객 중심으로 해야 하니 도입해야 하나? &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;애매하다&lt;/span&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;우리 고객은 토스뱅크 고객이라 정의해보자.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;도입을 했을 때, 결과적으로 토스 뱅크 고객의 효용으로 이어질까?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;동료의 기술 선호도는 중요하지 않다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;지원 조직의 역할은 동료를 섬기는 게 아니라 유능하게 만드는 것&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민해 봐야 할 것:&amp;nbsp;미리 해야 하는 일은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐하는 팀인가? 가상화 계층에서 코드보다 인프라와 가까운 일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작으면 좋은가? 계획보다는 대응에 집중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 할 것인가? 미팅보다 메신저로 소통&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 할 것인가? 우선 순위는 ROI를 중심으로 판단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말로 사람이 부족한가? 우리의 고객은 동료 개발자가 아닌 토스뱅크 고객&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 인프라를 담당하며 직접 느끼고 배운 점을 관통하는 내용이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계획적으로 처리할 수 없다는 것. 인프라 및 DevOps의 경우 정말 다양한 일들이 많다. 물론 나의 경우 대규모가 아니기에 충분히 고려하면 예방할 수 있는 부분이지만, 실무에서는 정말 많은 예외 상황들이 존재할 것이다. 이를 예방하고 대응하기 위해서는 평소 지식이 깊고 풍부해야 한다 생각한다. CS 지식이 정말 중요한 분야 중 하나가 아닐까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 새롭게 느낀 내용은 사람 수보다 일하는 방식이 중요하는 것이다. 혼자서 생각한 적은 있다. 사람을 교육시키고 가르치는 것보다 혼자서 일처리하는 게 속도가 더 빠르지 않을지, 그렇다면 신입은 왜 필요한지. 부정할 수 없다고 생각했다. 내가 아무리 취업 준비생이라 부정하고 싶어도 이성적으로 생각했을 때 사람을 늘리는 것보다 시스템을 바꾸는 게 더 나을지도 모르겠다고 생각했었다. 아직도 이에 대한 의문이 해결되진 않았다. 다음에 기회가 되면 찾아봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메신저 논의 좋다고 생각했다. 글로 정리한 내용을 서로 공유하는 것이 다시 찾아보기에도 좋고, 내 생각을 정리해서 전달하기에도 좋다는 생각을 했다. 또한 모든 사람이 내용을 공유할 수 있다는 것도 장점이다. 이러한 메신저 논의 문화가 자리만 잘 잡는다면 훨씬 더 효율적일 수 있다고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로, 내 역량을 많이 키워서 혼자서도 일을 처리할 수 있는 사람이 되어야 한다는 생각이 많이 들었다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/Cloud</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/394</guid>
      <comments>https://seomj74.tistory.com/394#entry394comment</comments>
      <pubDate>Mon, 9 Feb 2026 20:00:02 +0900</pubDate>
    </item>
    <item>
      <title>[Docker]로컬 개발 환경 세팅(vscode, IntelliJ 그리고 devcontainer.json) (3)</title>
      <link>https://seomj74.tistory.com/392</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 충돌을 이전에 해결하지 못했다.현재까지 진행한 내용은 내가 생각했던 개발 환경 세팅과는 다른 방향이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해 고민하며 지금까지의 접근법을 정리하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지금까지의 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&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;/li&gt;
&lt;li&gt;&lt;b&gt;컨테이너 내부에서 개발 진행 후 빌드 및 디버깅&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1차로 구현한 환경 (&lt;a href=&quot;https://seomj74.tistory.com/390#toc9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seomj74.tistory.com/390)&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너를 단순히 실행 도구(Runtime)로만 사용&lt;/li&gt;
&lt;li&gt;docker-compose up 한 번에 모든 서비스가 다 동작&lt;/li&gt;
&lt;li&gt;Front
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 빌드에서 Node.js 실행 환경만 갖춘 상태&lt;/li&gt;
&lt;li&gt;/app/node_modules 익명 볼륨을 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제 간의 라이브러리 충돌 방지(호스트와 컨테이너)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨테이너가 생성될 때마다 npm install을 체크 (docker-compose command)&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너가 생성될 때마다 package.json을 읽어 라이브러리를 설치&lt;/li&gt;
&lt;li&gt;기동 속도 저하&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Back
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 빌드에서 ubuntu 위에 Java만 설치하여 순수 JDK 환경 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ubuntu 이미지 사용 (ubuntu:22.04)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바를 실행하기에는 불필요한 패키지가 많아 무거움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;호스트 캐시( ~/.gradle:/root/.gradle )를 공유
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 구동이 빠름&lt;/li&gt;
&lt;li&gt;파일 잠금(Lock) 충돌이나 권한 문제 &amp;nbsp;발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;./gradlew 파일을 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/app 폴더에 있는 코드를 컴파일하고 빌드하여 Spring Boot 앱 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;문제&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDK는 컨테이너에, 캐시와 코드는 호스트에 있는 것을 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 환경 통일과 거리가 멈&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2차로 구현한 환경 (&lt;a href=&quot;https://seomj74.tistory.com/391&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seomj74.tistory.com/391&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker-compose up을 진행하지만 실행은 선택적&lt;/li&gt;
&lt;li&gt;WSL 2 기반 환경 통일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WSL 2 내부에 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;IDE의 핵심 엔진 설치&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;IntelliJ와 WebStorm에서 WSL 2 엔진과 통신하며 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Front
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 빌드 시 npm install 진행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스 코드가 변해도 package.json이 수정되지 않으면 속도가 빠름&lt;/li&gt;
&lt;li&gt;수정 시 컨테이너 내 npm install 혹은 이미지 재빌드 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기존에 생성되던 익명 볼륨을 기명 볼륨으로 바꾸어 재사용성을 높임&lt;/li&gt;
&lt;li&gt;호스트와 컨테이너 분리&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트의 node_modules가 컨테이너를 덮어 쓰지 않음으로써 컨테이너 내 빌드 안정성 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Back
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 빌드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경량화 이미지 사용 (eclipse-temurin:21-jdk-alpine)&lt;/li&gt;
&lt;li&gt;gradlew, build.gradle 등 설정 파일만 먼저 복사하여 빌드 도구 캐싱
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;build.gradle이 변경되면 ./gradlew build 혹은 이미지 재빌드 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Gradle 실행 엔진 다운로드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;profiles를 통해 선택적 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB, Redis 등 필요한 도구만 띄워두고 로컬 개발 진행 후 컨테이너를 띄워 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 관리 효율적&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빌드 결과물과 캐시 격리를 통해 컴파일 충돌 방지&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;package.json이나 build.gradle이 변경될 때마다 이미지를 다시 빌드하거나 컨테이너 내부에서 수동 설치해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차와 2차의 경우 내가 구현하고자 하는 방향과 많이 다르다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인의 경우, 컨테이너가 개발 환경 역할만 하길 바랬기에 컨테이너를 띄우고 내부에 접속해서 빌드, 디버깅이 가능한 환경을 바랬다. Java, OS, 의존성 버전 등을 맞춰야 추후 &quot;내 컴퓨터에선 되는데?&quot;를 방지할 수 있을 것이라 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이는 앱 자체를 실행하는 환경에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트의 경우에는 HMR(Hot Module Replacement)로 인해 빌드없이 소스 코드 수정만으로 웹 사이트에서 즉시 확인이 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드의 경우에는 수정 시 마다 빌드가 필요했다. 컨테이너가 실행 중인 상황이며 이미 포트를 점유하고 있기에 개발자가 내부에서 수동 빌드 및 실행을 하려고 하면 포트 충돌이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 다시 고민해보기 시작했다. 알아보던 와중 Dev Container에서 devcontainer.json이라는 게 존재한다는 것을 발견했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;devcontainer.json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의된 도구와 런타임 스택에 대한 개발 컨테이너를 구성하는 데 필요한 모든 메타데이터와 설정 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 항목들이 궁금하면 아래 링크를 참고하길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://containers.dev/implementors/json_reference/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://containers.dev/implementors/json_reference/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770013177988&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dev Container metadata reference&quot; data-og-description=&quot;The devcontainer.json file contains any needed metadata and settings required to configure a development container for a given well-defined tool and runtime stack. It can be used by tools and services that support the dev container spec to create a develop&quot; data-og-host=&quot;containers.dev&quot; data-og-source-url=&quot;https://containers.dev/implementors/json_reference/&quot; data-og-url=&quot;https://containers.dev/implementors/json_reference/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://containers.dev/implementors/json_reference/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://containers.dev/implementors/json_reference/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Dev Container metadata reference&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The devcontainer.json file contains any needed metadata and settings required to configure a development container for a given well-defined tool and runtime stack. It can be used by tools and services that support the dev container spec to create a develop&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;containers.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile은 OS와 런타임을 어떻게 구성할지를 정의하는 파일로, 설계도와 같은 역할&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose는 서비스 간의 네트워크와 데이터 저장소를 연결하는 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;devcontainer.json은 IDE가 개발자를 위해 수행할 동작을 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3차로 구현할 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행하지 않고 컨테이너만 유지하여 개발 환경만 제공&amp;nbsp;&lt;/li&gt;
&lt;li&gt;빌드를 도커에서 진행하지 않고 개발자가 필요할 때 IDE에서 직접 진행&lt;/li&gt;
&lt;li&gt;Front
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 빌드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;npm install을 진행하여 frontend_node_modules 볼륨에 저장&lt;/li&gt;
&lt;li&gt;bash와 git 등 개발에 필요한 도구들을 미리 설치&lt;/li&gt;
&lt;li&gt;대기 상태 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;devcontainer.json을 통해 VS Code나 WebStorm/IntelliJ같은 IDE가 컨테이너 내부의 서비스에 직접 접&lt;/li&gt;
&lt;li&gt;패키지를 추가해야 하면 IDE 터미널에서 npm install 실행&lt;/li&gt;
&lt;li&gt;npm run dev를 통해 개발자아 원할 때 앱 실행&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Back
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 빌드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;eclipse-temurin:21-jdk를 사용하여 git, curl, procps 등을 설치&lt;/li&gt;
&lt;li&gt;ENV GRADLE_USER_HOME=/home/gradle_cache와 기명 볼륨(gradle_cache)을 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;postCreateCommand를 통해 컨테이너가 생성되자마자 실행 권한 부여와 Gradle 엔진 확인&lt;/li&gt;
&lt;li&gt;IntelliJ가 컨테이너 내부로 접속하여 소스 코드를 인덱싱하고 직접 명령&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front devcontainer.json&lt;/p&gt;
&lt;pre id=&quot;code_1769934217576&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Frontend Dev Environment&quot;,
  &quot;dockerComposeFile&quot;: &quot;../../docker-compose.yml&quot;,
  &quot;service&quot;: &quot;frontend&quot;,
  &quot;workspaceFolder&quot;: &quot;/app&quot;
  &quot;forwardPorts&quot;: [5173]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Back devcontainer.json&lt;/p&gt;
&lt;pre id=&quot;code_1769934211630&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Backend Dev Environment&quot;,
  &quot;dockerComposeFile&quot;: &quot;../../docker-compose.yml&quot;,
  &quot;service&quot;: &quot;backend&quot;,
  &quot;workspaceFolder&quot;: &quot;/app&quot;,

  &quot;context&quot;: &quot;../../&quot;,

  &quot;customizations&quot;: {
    &quot;jetbrains&quot;: {
      &quot;backend&quot;: &quot;IntelliJ&quot;
    }
  },

  &quot;postCreateCommand&quot;: &quot;chmod +x gradlew &amp;amp;&amp;amp; ./gradlew --version&quot;,

  &quot;forwardPorts&quot;: [8080, 5005, 3306],
  &quot;remoteUser&quot;: &quot;root&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yaml&lt;/p&gt;
&lt;pre id=&quot;code_1769934205854&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  backend:
    build:
      context: ../../
      dockerfile: ./Infra/local_dev/backend.Dockerfile
    volumes:
      - ../../Backend:/app
      - /app/build
      - /app/.gradle
      - gradle_cache:/home/gradle_cache
    ports:
      - &quot;8080:8080&quot;
      - &quot;5005:5005&quot;
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/testdb
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=1234
      - SPRING_DATA_REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
  
  frontend:
    build:
      context: ../../
      dockerfile: ./Infra/local_dev/frontend.Dockerfile
    volumes:
      - ../../Frontend:/app
      - frontend_node_modules:/app/node_modules
    ports:
      - &quot;5173:5173&quot;

  mysql:
    image: mysql:8.0
    ports:
      - &quot;3306:3306&quot;
    environment:
      - MYSQL_ROOT_PASSWORD=1234
      - MYSQL_DATABASE=testdb
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7.2-alpine
    volumes:
      - redis_data:/data
    ports:
      - &quot;6379:6379&quot;

volumes:
  mysql_data:
  frontend_node_modules:
  redis_data:
  gradle_cache:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front Dockerfile&lt;/p&gt;
&lt;pre id=&quot;code_1769934199119&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:20-alpine
WORKDIR /app

RUN apk add --no-cache bash git

COPY Frontend/package*.json ./
RUN npm install

COPY Frontend/ .

CMD [&quot;tail&quot;, &quot;-f&quot;, &quot;/dev/null&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Back Dockerfile&lt;/p&gt;
&lt;pre id=&quot;code_1769934193285&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM eclipse-temurin:21-jdk

WORKDIR /app

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    git \
    bash \
    curl \
    procps \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

ENV GRADLE_USER_HOME=/home/gradle_cache

COPY Backend/gradlew .
COPY Backend/gradle gradle
COPY Backend/build.gradle Backend/settings.gradle ./

RUN chmod +x gradlew
RUN ./gradlew --version --no-daemon

COPY Backend/ .

CMD [&quot;tail&quot;, &quot;-f&quot;, &quot;/dev/null&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;VS Code&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VS Code에서 .devcontainer 디렉터리가 있는 위치를 오픈한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 .devcontainer가 오픈한 폴더 바로 아래에 있지 않으면 못 찾으니 유의하도록!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dev container 플러그인을 설치해야 한다. 이는 설치되어 있다는 가정하에 설명한다. 설치하지 않았다면 플러그인에서 검색해서 그냥 설치해주면 끝이다. (간단)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 오른쪽 하단에 Reopen in Container라고 뜬다. 이걸 클릭하자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UFsWM/dJMcai3cuY5/NvcxxbKRpJ7oHQIovvND6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UFsWM/dJMcai3cuY5/NvcxxbKRpJ7oHQIovvND6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UFsWM/dJMcai3cuY5/NvcxxbKRpJ7oHQIovvND6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUFsWM%2FdJMcai3cuY5%2FNvcxxbKRpJ7oHQIovvND6K%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;651&quot; height=&quot;386&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 Ctrl + Shift + P로 접근해서 Rebuild and Reopen in Container를 선택하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjn4xx/dJMcac9KSXF/g9gKpnkSh85UAyPGIHWQ0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjn4xx/dJMcac9KSXF/g9gKpnkSh85UAyPGIHWQ0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjn4xx/dJMcac9KSXF/g9gKpnkSh85UAyPGIHWQ0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjn4xx%2FdJMcac9KSXF%2Fg9gKpnkSh85UAyPGIHWQ0k%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;644&quot; height=&quot;119&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래와 같이 devcontainer.json을 기준으로 어떤 것을 오픈할 것인지 묻는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nlaNj/dJMcabQAjEt/F39QPJSYCrRbvspgCMkhe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nlaNj/dJMcabQAjEt/F39QPJSYCrRbvspgCMkhe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nlaNj/dJMcabQAjEt/F39QPJSYCrRbvspgCMkhe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnlaNj%2FdJMcabQAjEt%2FF39QPJSYCrRbvspgCMkhe1%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;650&quot; height=&quot;99&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Front&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front 컨테이너로 접속해서 실행하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1770011899049&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run dev -- --host 0.0.0.0&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;2048&quot; data-origin-height=&quot;1154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0bAzc/dJMcacIKsPq/wQhbAmUJCe3nBZgZxveaNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0bAzc/dJMcacIKsPq/wQhbAmUJCe3nBZgZxveaNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0bAzc/dJMcacIKsPq/wQhbAmUJCe3nBZgZxveaNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0bAzc%2FdJMcacIKsPq%2FwQhbAmUJCe3nBZgZxveaNk%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;702&quot; height=&quot;396&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Back&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 플러그인을 설치하고 Run하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UUNQs/dJMcadAOs5W/0Y9nOsjueoO0dskbAyKjL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UUNQs/dJMcadAOs5W/0Y9nOsjueoO0dskbAyKjL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UUNQs/dJMcadAOs5W/0Y9nOsjueoO0dskbAyKjL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUUNQs%2FdJMcadAOs5W%2F0Y9nOsjueoO0dskbAyKjL1%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;699&quot; height=&quot;413&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1211&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbMjbN/dJMcafSZ2vp/KwgGXx44KgJ4sYm917Mlk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbMjbN/dJMcafSZ2vp/KwgGXx44KgJ4sYm917Mlk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbMjbN/dJMcafSZ2vp/KwgGXx44KgJ4sYm917Mlk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbMjbN%2FdJMcafSZ2vp%2FKwgGXx44KgJ4sYm917Mlk0%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;700&quot; height=&quot;180&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WebStorm &amp;amp;&amp;nbsp;IntelliJ&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 WebStorm과 IntelliJ는 계속 시도했으나 접속이 안된다. Ultimate 버전에서만 제공하는 기능으로 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1770012836245&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dev Container overview | IntelliJ&amp;nbsp;IDEA&quot; data-og-description=&quot; &quot; data-og-host=&quot;www.jetbrains.com&quot; data-og-source-url=&quot;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&quot; data-og-url=&quot;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qVNbS/dJMb8XR0EmO/8acN7aW2ydCwfYGqVeC8c0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jetbrains.com/help/idea/connect-to-devcontainer.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qVNbS/dJMb8XR0EmO/8acN7aW2ydCwfYGqVeC8c0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;Dev Container overview | IntelliJ&amp;nbsp;IDEA&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jetbrains.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;devcontainer.json에서 우클릭 후 Dev Containers에서 실행하면 된다. 아마 Ultimate 버전을 사용 중이라면 무리없이 접근이 되지 않을까 싶다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;1746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nSikk/dJMcagYEBDS/A1BDdpSeVi2o70mCL8Badk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nSikk/dJMcagYEBDS/A1BDdpSeVi2o70mCL8Badk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nSikk/dJMcagYEBDS/A1BDdpSeVi2o70mCL8Badk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnSikk%2FdJMcagYEBDS%2FA1BDdpSeVi2o70mCL8Badk%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;700&quot; height=&quot;660&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;1746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://benggri.tistory.com/137&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://benggri.tistory.com/137&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study/Docker</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/392</guid>
      <comments>https://seomj74.tistory.com/392#entry392comment</comments>
      <pubDate>Wed, 4 Feb 2026 20:00:09 +0900</pubDate>
    </item>
    <item>
      <title>[Docker]로컬 개발 환경 세팅(vscode, IntelliJ) (2)</title>
      <link>https://seomj74.tistory.com/391</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 로컬 개발 환경 세팅을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 미숙했던 점이 많았고 이로 인해 문제가 발생했다. 이에 대한 내용을 다뤄보도록 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅 참고&lt;/p&gt;
&lt;figure id=&quot;og_1769134328797&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;[Docker] 로컬 개발 환경 세팅(vscode, IntelliJ) (1) - 실패&quot; data-og-description=&quot;서론SSAFY 2학기를 맞이해서 프로젝트를 진행하고 있다.본인은 인프라를 담당했고, 우선적으로 팀원들의 개발 환경 통일을 위한 세팅을 제공하기 위해 진행한 내용에 대해 정리한다. 필독해당 글&quot; data-og-host=&quot;seomj74.tistory.com&quot; data-og-source-url=&quot;https://seomj74.tistory.com/390&quot; data-og-url=&quot;https://seomj74.tistory.com/390&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bUpcAL/dJMb83ScZud/WnZVYKDAIYSjOYKJxCPYp0/img.png?width=800&amp;amp;height=367&amp;amp;face=0_0_800_367,https://scrap.kakaocdn.net/dn/RSfpQ/dJMb8VNpuvx/GIfG0KVWueQHCXAJdYXKW0/img.png?width=800&amp;amp;height=367&amp;amp;face=0_0_800_367,https://scrap.kakaocdn.net/dn/5tKxQ/dJMb9kl602g/nLDoKnqVSe23b2t9zUpWNk/img.png?width=2879&amp;amp;height=1699&amp;amp;face=0_0_2879_1699&quot;&gt;&lt;a href=&quot;https://seomj74.tistory.com/390&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seomj74.tistory.com/390&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bUpcAL/dJMb83ScZud/WnZVYKDAIYSjOYKJxCPYp0/img.png?width=800&amp;amp;height=367&amp;amp;face=0_0_800_367,https://scrap.kakaocdn.net/dn/RSfpQ/dJMb8VNpuvx/GIfG0KVWueQHCXAJdYXKW0/img.png?width=800&amp;amp;height=367&amp;amp;face=0_0_800_367,https://scrap.kakaocdn.net/dn/5tKxQ/dJMb9kl602g/nLDoKnqVSe23b2t9zUpWNk/img.png?width=2879&amp;amp;height=1699&amp;amp;face=0_0_2879_1699');&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;[Docker] 로컬 개발 환경 세팅(vscode, IntelliJ) (1) - 실패&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서론SSAFY 2학기를 맞이해서 프로젝트를 진행하고 있다.본인은 인프라를 담당했고, 우선적으로 팀원들의 개발 환경 통일을 위한 세팅을 제공하기 위해 진행한 내용에 대해 정리한다. 필독해당 글&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seomj74.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Front 컨테이너 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트 담당 팀원들은 WebStorm을 통해 진행을 하고자 했다. WebStorm에서 Front 컨테이너에 접속하고자 했으나 프로젝트 열기 버튼이 활성화되지 않았다. vscode 등에서 코드를 수정하면 바로 반영이 되기 때문에 큰 문제는 없었으나, 내가 예상한 구성이 아니라는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Back 빌드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;intellij에서 컨테이너를 열어 빌드를 시도했으나 아래와 같은 오류가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 현재 포트 8080이 이미 사용 중이라서 Spring Boot가 뜨지 못한다는 내용이었다.&lt;/p&gt;
&lt;pre id=&quot;code_1769143244726&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2026-01-22T06:25:15.962Z ERROR 6036 --- [roundy] [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:
Web server failed to start. Port 8080 was already in use.

Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

종료 코드 1(으)로 완료된 프로세스&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;이미 컨테이너 형태로 백엔드가 돌아가고 있는 상황이며, intelliJ 내부에서 또 Run을 해 애플리케이션을 띄우려고 한 것이다. 이대로 실행을 하려면 코드를 수정하고 컨테이너가 자동으로 재시작 되는 걸 기다리거나 재시작해야 한다. 이러한 점을 미처 생각지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제들을 겪으면서 구성에 대해 다시 생각하고 정리하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 그저 docker를 사용해서 하나의 가상환경을 제공하고자 했다.하지만 포트 충돌, 개발 루프 지연 등의 문제가 발생했다. 포트의 경우, 포트를 분리하면 해결이 가능할 수 있다고 생각했지만 복잡하다고 판단했다. 모든 포트를 관리해야 하기 때문이다. 그렇다고 이를 유지하기엔 코드를 고칠 때마다 백엔드에서는 컨테이너를 재시작해야 하는데 이는 매우 불편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker는 기본적으로 완성된 상태(Immutable)를 실행하는 데 최적화되어 있다. 반면 개발 환경은 계속 변하는 상태(Mutable)이다. 그렇기 때문에 이를 다시 설계하고 생각해 볼 필요가 있다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재설계한 전반적인 구조는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1903&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b59d9d/dJMcahQJY9B/RMRVOtH8cP3cKclkadnZD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b59d9d/dJMcahQJY9B/RMRVOtH8cP3cKclkadnZD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b59d9d/dJMcahQJY9B/RMRVOtH8cP3cKclkadnZD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb59d9d%2FdJMcahQJY9B%2FRMRVOtH8cP3cKclkadnZD1%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;699&quot; height=&quot;369&quot; data-origin-width=&quot;1903&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;GitLab에서 clone을 WSL 내부에 받는다.&lt;/li&gt;
&lt;li&gt;프론트와 백에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;webstorm과 intellij를 사용한다고 하여&lt;span&gt; 여기서 WSL에 접속한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Frontend와 Backend 폴더에서 개발을 진행한다.&lt;/li&gt;
&lt;li&gt;이를 실행하게 되면 Docker 컨테이너 형식으로 Front와 Back이 실행된다.&lt;/li&gt;
&lt;li&gt;이를 통해서 localhost:5173, localhost:8080 등 브라우저에서 확인이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서는 이해하기 쉽게 webstorm과 intellij를 직접 연결해 사용하는 것으로 표현했지만, 실제로는 JetBrains의 Gateway 방식을 사용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JetBrains Gateway&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/remote-development/gateway/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jetbrains.com/remote-development/gateway/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769141103657&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JetBrains Gateway - Remote Development for JetBrains IDEs&quot; data-og-description=&quot;Get started with remote development workflows from your favorite IDE in no time.&quot; data-og-host=&quot;www.jetbrains.com&quot; data-og-source-url=&quot;https://www.jetbrains.com/remote-development/gateway/&quot; data-og-url=&quot;https://www.jetbrains.com/remote-development/gateway/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bUxoNc/dJMb85WNqlw/9mhKm8TqWM7EtP9JSUjKr0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/remote-development/gateway/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jetbrains.com/remote-development/gateway/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bUxoNc/dJMb85WNqlw/9mhKm8TqWM7EtP9JSUjKr0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;JetBrains Gateway - Remote Development for JetBrains IDEs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Get started with remote development workflows from your favorite IDE in no time.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jetbrains.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 IDE의 머리(UI)는 Windows에 있고 IDE의 몸통은 WSL2 안에 설치되어 실행된다. 이는 설치 과정을 보면 더 잘 이해할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Backend 실행:&lt;/b&gt; WSL2 Ubuntu 내부에 IDE의 핵심 엔진 설치&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;Thin Client 연결:&lt;/b&gt; Windows에서는 가벼운 'JetBrains Client'만 실행되어 WSL2 안의 엔진과 통신&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;성능:&lt;/b&gt; 모든 파일 연산과 컴파일이 리눅스(WSL2) 내부에서 일어나므로 속도가 빠름&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세팅 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WSL2, Docker, IntelliJ or WebStorm이 설치되어 있다는 가정 하에 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker 컨테이너 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 팀원들이 WSL 내부에서 git clone 후 docker-compose up -d --build 과정을 통해 생성하게 된다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;docker-compose.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1769141994406&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  backend:
    profiles: [&quot;full-test&quot;]
    build:
      context: ../../
      dockerfile: ./Infra/local_dev/backend.Dockerfile
    volumes:
      # 1. 소스 코드는 공유 (수정 시 컨테이너에 즉시 반영)
      - ../../Backend:/app
      # 2. 빌드 결과물 및 캐시 격리 (충돌 방지 핵심 - 익명 볼륨)
      - /app/build
      - /app/.gradle
    ports:
      - &quot;8080:8080&quot;
      - &quot;5005:5005&quot;
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/${MYSQL_DATABASE}
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - SPRING_DATA_REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
  
  frontend:
    build:
      context: ../../
      dockerfile: ./Infra/local_dev/frontend.Dockerfile
    volumes:
      - ../../Frontend:/app
      - frontend_node_modules:/app/node_modules
    ports:
      - &quot;5173:5173&quot;

  mysql:
    image: mysql:8.0
    ports:
      - &quot;3306:3306&quot;
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7.2-alpine
    volumes:
      - redis_data:/data
    ports:
      - &quot;6379:6379&quot;

volumes:
  mysql_data:
  frontend_node_modules:
  redis_data:&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스&amp;nbsp;코드는&amp;nbsp;Bind&amp;nbsp;Mount로&amp;nbsp;연결하고,&amp;nbsp;무거운&amp;nbsp;의존성(node_modules,&amp;nbsp;build,&amp;nbsp;.gradle)은&amp;nbsp;Volume으로&amp;nbsp;분리&lt;/li&gt;
&lt;li&gt;/app/build, /app/.gradle 경로를 호스트로부터 격리한다. 호스트와 컨테이너의 빌드 환경 차이로 인한 충돌을 방지한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bind Mount가 호스트 파일을 컨테이너 /app에 덮어 씌우면서 호스트의 build 폴더로 컨테이너 내부로 들어오게 된다.&lt;/li&gt;
&lt;li&gt;/app/build로 선언된 익명 볼륨이 그 위에 덮어 씌우듯이 경로를 가로챈다.&lt;/li&gt;
&lt;li&gt;이로 인해 컨테이너의 /app/build는 호스트 폴더와 연결되지 않는다. 호스트의 빌드 결과물은 컨테이너에 영향을 주지 않고, 컨테이너의 빌드 결과물도 마찬가지이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Front&lt;/h4&gt;
&lt;pre id=&quot;code_1769142021288&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:20-alpine
WORKDIR /app
COPY Frontend/package*.json ./
RUN npm install
COPY . .
# Vite의 경우 외부 접속 허용을 위해 --host 옵션 필요
CMD [&quot;npm&quot;, &quot;run&quot;, &quot;dev&quot;, &quot;--&quot;, &quot;--host&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Back&lt;/h4&gt;
&lt;pre id=&quot;code_1769142033340&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
# 빌드 도구 캐싱을 위해 설정 파일 먼저 복사
COPY Backend/gradlew .
COPY Backend/gradle gradle
COPY Backend/build.gradle Backend/settings.gradle ./

RUN chmod +x gradlew
RUN ./gradlew --version  # Gradle 미리 내려받기

COPY . .
# 개발 모드로 실행 (Spring Boot 3.x)
CMD [&quot;./gradlew&quot;, &quot;bootRun&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;--no-daemon
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker 컨테이너는 메인 프로세스(PID 1)가 살아있는 동안 유지된다. 해당 옵션을 사용하면 Gradle이 종료될 때까지 프로세스가 foregroud에 머물러 있으므로 docker가 작업 중이라고 인식한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;IntelliJ 혹은 WebStorm에서 WSL 2 접속&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Remote Develpment를 통해 원격 환경과 연결할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EYXP1/dJMcaiviVWJ/1aonCaVPZq0TmYgQDMvkik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EYXP1/dJMcaiviVWJ/1aonCaVPZq0TmYgQDMvkik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EYXP1/dJMcaiviVWJ/1aonCaVPZq0TmYgQDMvkik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEYXP1%2FdJMcaiviVWJ%2F1aonCaVPZq0TmYgQDMvkik%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;499&quot; height=&quot;411&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wsl 연결&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v5iNl/dJMcacu70vv/to0iiQCfkK2T4amE3IDhqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v5iNl/dJMcacu70vv/to0iiQCfkK2T4amE3IDhqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v5iNl/dJMcacu70vv/to0iiQCfkK2T4amE3IDhqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv5iNl%2FdJMcacu70vv%2Fto0iiQCfkK2T4amE3IDhqK%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;501&quot; height=&quot;424&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ubuntu 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 환경에 맞춰 리스트업이 되니 그 중 선택하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;1363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpnbpB/dJMcabpsxE2/s1wRmK3UWQzbiBHbhMzEuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpnbpB/dJMcabpsxE2/s1wRmK3UWQzbiBHbhMzEuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpnbpB/dJMcabpsxE2/s1wRmK3UWQzbiBHbhMzEuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpnbpB%2FdJMcabpsxE2%2Fs1wRmK3UWQzbiBHbhMzEuk%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;500&quot; height=&quot;428&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;1363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webstrom 버전과 frontend 연동하기 / intellij 버전과 backend 연동하기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1569&quot; data-origin-height=&quot;1363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JOnJ2/dJMcaaD40s6/9NHDZ0cesgH38ofK2M2WG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JOnJ2/dJMcaaD40s6/9NHDZ0cesgH38ofK2M2WG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JOnJ2/dJMcaaD40s6/9NHDZ0cesgH38ofK2M2WG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJOnJ2%2FdJMcaaD40s6%2F9NHDZ0cesgH38ofK2M2WG1%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;501&quot; height=&quot;435&quot; data-origin-width=&quot;1569&quot; data-origin-height=&quot;1363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;1268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pW1yK/dJMcafyCBAD/8fPwtyIv133mo9mAKmlQCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pW1yK/dJMcafyCBAD/8fPwtyIv133mo9mAKmlQCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pW1yK/dJMcafyCBAD/8fPwtyIv133mo9mAKmlQCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpW1yK%2FdJMcafyCBAD%2F8fPwtyIv133mo9mAKmlQCk%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;500&quot; height=&quot;408&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;1268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 WSL 2 내부에 설치를 진행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료되면 각 폴더로 접속 가능해진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;1273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl1OBe/dJMcahDbAQX/yNpP7qc3aH4Siv02RDJW9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl1OBe/dJMcahDbAQX/yNpP7qc3aH4Siv02RDJW9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl1OBe/dJMcahDbAQX/yNpP7qc3aH4Siv02RDJW9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl1OBe%2FdJMcahDbAQX%2FyNpP7qc3aH4Siv02RDJW9K%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;501&quot; height=&quot;409&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;1273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front의 경우에는 코드를 수정하면 바로 반영이 되기 때문에 컨테이너가 동작 중인 상태에서 개발을 진행하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Back의 경우에는 IntelliJ에서 개발하듯이 진행하면 된다. 하지만 컨테이너가 동작 중일 때 애플리케이션 실행이 불가능하다. 이는 포트 충돌로 이어진다. 즉, 포트 충돌 문제는 해결하지 못했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은, mysql과 redis만 띄우고 로컬(WSL 2)에서 개발을 진행하고 단위 테스트 진행 시에 Backend 컨테이너를 띄워서 테스트하는 방식으로 설계를 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이는 WSL 2 환경을 통일한 것이며, 백에서는 mysql과 redis만 제공한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트는 컨테이너가 실행 중이어야 하며 그 상황에서 개발을 해도 바로 반영이 되어 문제가 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 백은 빌드하는 과정이 필요하기 때문에 컨테이너와 충돌이 발생하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에 더 이상 시간을 쏟을 수 없다고 판단하여 이번 프로젝트는 해당 환경 구성으로 진행할 생각이다. WSL 2 환경은 모두 동일하며, JDK 버전을 21로 맞추고 mysql과 redis는 컨테이너로 접속해서 진행하니... 그래도 추후 배포 때 괜찮지 않을까 예상 중이다... (제발)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원했던 환경은 컨테이너 위에 개발에 필요한 도구를 설치하고, 로컬이 아닌 컨테이너에 접속해서 내부에서 개발을 진행하는 것이었다. 개발을 내부에서 진행하고 빌드, 디버깅 모두 내부에서 진행하는 방식을 원했다. 그리고 이렇게 해야 개발 환경을 통일하는 것이 의미있는 작업이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에 대해서 2차 프로젝트 혹은 이후에 다시 고민해보고 가능한지 알아보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 빠르게 CI/CD를 구축하고 배포 환경을 마련하는 것이 급선무이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내 상태&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라를 담당으로 혼자 프로젝트에 참여한 것이 처음이라 혼자 머리가 깨져라 고민하고 진행하고 있다... 매우 힘들지만 오기도 생기고 재밌다. 한편으로는 나로 인해 팀원들에게 피해가 갈까봐 책임감이 크고 압박감을 느낀다. 물론 팀원들은 나를 다독여주고 괜찮다고 응원해준다. 우리팀 너무 따숩고 재밌고 최고다. 2차 프로젝트도 같이 하고 싶을 만큼.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 CI/CD 관련해서 특강이 있었고 실습코치님이 &quot;팀에서 인프라를 담당하고 있는 팀원이 있다면 꼭 안아주세요&quot;라고 하셨다. 팀원들이 나를 안아줬는데 그게 생각보다 엄청 힘이 됐다. 흑흑.&lt;/p&gt;</description>
      <category>Study/Docker</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/391</guid>
      <comments>https://seomj74.tistory.com/391#entry391comment</comments>
      <pubDate>Sat, 24 Jan 2026 20:00:32 +0900</pubDate>
    </item>
    <item>
      <title>[Docker]로컬 개발 환경 세팅(vscode, IntelliJ) (1) - 실패</title>
      <link>https://seomj74.tistory.com/390</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSAFY 2학기를 맞이해서 프로젝트를 진행하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 인프라를 담당했고, 우선적으로 팀원들의 개발 환경 통일을 위한 세팅을 제공하기 위해 진행한 내용에 대해 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;필독&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글대로 진행을 했으나 문제가 생겼고, 이를 다룬 내용은 다음 포스팅을 참고해주길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seomj74.tistory.com/391&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seomj74.tistory.com/391&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769427993929&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;[Docker] 로컬 개발 환경 세팅(vscode, IntelliJ) (2)&quot; data-og-description=&quot;서론이전 포스팅에서 로컬 개발 환경 세팅을 진행했다.하지만 미숙했던 점이 많았고 이로 인해 문제가 발생했다. 이에 대한 내용을 다뤄보도록 할 것이다. 이전 포스팅 참고 [Docker] 로컬 개발 환&quot; data-og-host=&quot;seomj74.tistory.com&quot; data-og-source-url=&quot;https://seomj74.tistory.com/391&quot; data-og-url=&quot;https://seomj74.tistory.com/391&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dHkFRt/dJMb9dHiu2j/I4akNhJz3ukkcY1Kc8uP7k/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/bYMERk/dJMb9lk1DVY/VpOpdhAdetAZXVGWavds9k/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/bv1jIw/dJMb9lk1DVZ/lCv1JLkWqP68pnaXhmB2p1/img.png?width=1903&amp;amp;height=1004&amp;amp;face=0_0_1903_1004&quot;&gt;&lt;a href=&quot;https://seomj74.tistory.com/391&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seomj74.tistory.com/391&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dHkFRt/dJMb9dHiu2j/I4akNhJz3ukkcY1Kc8uP7k/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/bYMERk/dJMb9lk1DVY/VpOpdhAdetAZXVGWavds9k/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/bv1jIw/dJMb9lk1DVZ/lCv1JLkWqP68pnaXhmB2p1/img.png?width=1903&amp;amp;height=1004&amp;amp;face=0_0_1903_1004');&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;[Docker] 로컬 개발 환경 세팅(vscode, IntelliJ) (2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서론이전 포스팅에서 로컬 개발 환경 세팅을 진행했다.하지만 미숙했던 점이 많았고 이로 인해 문제가 발생했다. 이에 대한 내용을 다뤄보도록 할 것이다. 이전 포스팅 참고 [Docker] 로컬 개발 환&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seomj74.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로컬 환경 세팅을 위한 Docker&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술 스펙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FE&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50.4651%; height: 21px;&quot;&gt;이름&lt;/td&gt;
&lt;td style=&quot;width: 49.4186%; height: 21px;&quot;&gt;버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50.4651%; height: 17px;&quot;&gt;Node.js&lt;/td&gt;
&lt;td style=&quot;width: 49.4186%; height: 17px;&quot;&gt;20.x (LTS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50.4651%; height: 17px;&quot;&gt;Vite&lt;/td&gt;
&lt;td style=&quot;width: 49.4186%; height: 17px;&quot;&gt;7.2.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50.4651%; height: 17px;&quot;&gt;React&lt;/td&gt;
&lt;td style=&quot;width: 49.4186%; height: 17px;&quot;&gt;19.2.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50.4651%; height: 17px;&quot;&gt;TypeScript&lt;/td&gt;
&lt;td style=&quot;width: 49.4186%; height: 17px;&quot;&gt;5.9.x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BE&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50.5813%;&quot;&gt;이름&lt;/td&gt;
&lt;td style=&quot;width: 49.3024%;&quot;&gt;버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50.5813%;&quot;&gt;Java&lt;/td&gt;
&lt;td style=&quot;width: 49.3024%;&quot;&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50.5813%;&quot;&gt;Spring&lt;/td&gt;
&lt;td style=&quot;width: 49.3024%;&quot;&gt;3.5.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50.5813%;&quot;&gt;MySQL&lt;/td&gt;
&lt;td style=&quot;width: 49.3024%;&quot;&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50.5813%;&quot;&gt;Redis&lt;/td&gt;
&lt;td style=&quot;width: 49.3024%;&quot;&gt;7.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker를 사용해서 팀원들의 개발 환경을 동일시 만들 것이다. 내가 생각했을 때 중요하게 고려해야 할 점은 두가지였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프론트엔드와 백엔드가 모든 팀원들의 환경에서 코드만 있으면 동일하게 수행되어야 한다.&lt;/li&gt;
&lt;li&gt;도커 컨테이너 내에서 개발을 진행해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker를 사용하는 것은 환경 불일치를 막기 위함이다. 여러 기술 스택을 사용하게 될텐데 이때 버전이 다르거나 연결 설정이 달라져 개발 환경에 따라 차이가 생길 수 있다고 생각했다. 이를 위해 기술 스택 및 버전, 환경을 통일해서 세팅하는 방법도 있지만, Docker를 활용해서 제공하면 더 확실하다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음으로 진행해보는 과정이라 실제로 개발하거나 적용했을 때 또 다른 문제가 발생할지도 모른다. (만약 발생하게 된다면 업데이트하겠다. 해결과정 또한 함께.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Dockerfile, docker-compose.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1768879444318&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  backend:
    build:
      context: ../../
      dockerfile: ./Infra/local_dev/backend.Dockerfile
    volumes:
      - ../../backend:/app
      - ~/.gradle:/root/.gradle
    ports:
      - &quot;8080:8080&quot;
      - &quot;5005:5005&quot;
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/${MYSQL_DATABASE}
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - SPRING_DATA_REDIS_HOST=redis
    working_dir: /app                 # 컨테이너의 작업 디렉토리 지정
    tty: true                         # 컨테이너 종료 방지
    stdin_open: true
    command: ./gradlew bootRun
    depends_on:
      - mysql
      - redis
  frontend:
    build:
      context: ../../
      dockerfile: ./Infra/local_dev/frontend.Dockerfile
    volumes:
      - ../../frontend:/app
      - /app/node_modules
    ports:
      - &quot;5173:5173&quot;
    environment:
      - VITE_API_URL=http://localhost:8080
    tty: true
    stdin_open: true
    command: &amp;gt;
      sh -c &quot;npm install &amp;amp;&amp;amp; npm run dev -- --host 0.0.0.0&quot;

  mysql:
    image: mysql:8.0
    ports:
      - &quot;3306:3306&quot;
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7.2-alpine
    ports:
      - &quot;6379:6379&quot;

volumes:
  mysql_data:&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;backend&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 data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;ports&lt;/b&gt;: 8080은 웹 서버 포트이며, 5005는 일반적으로 원격 디버깅(Remote Debugging)을 위해 열어둔 포트&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,4,0&quot;&gt;tty &amp;amp; stdin_open&lt;/b&gt;: 터미널 입력을 유지하여 Spring 컨테이너가 실행 직후 종료되지 않도록 설정&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,5,0&quot;&gt;depends_on&lt;/b&gt;: mysql과 redis가 먼저 실행된 후에 백엔드가 실행되도록 순서를 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Front&lt;/h4&gt;
&lt;pre id=&quot;code_1768879560918&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:20-slim

WORKDIR /app

EXPOSE 5173

CMD [&quot;npm&quot;, &quot;run&quot;, &quot;dev&quot;, &quot;--&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Back&lt;/h4&gt;
&lt;pre id=&quot;code_1768879571440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM ubuntu:22.04

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    curl git unzip wget \
    openjdk-21-jdk \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
ENV PATH=$PATH:$JAVA_HOME/bin

WORKDIR /app&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;885&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lUFqK/dJMcahJXWWW/VHPMKPfLsryJ3KOZYp9K21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lUFqK/dJMcahJXWWW/VHPMKPfLsryJ3KOZYp9K21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lUFqK/dJMcahJXWWW/VHPMKPfLsryJ3KOZYp9K21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlUFqK%2FdJMcahJXWWW%2FVHPMKPfLsryJ3KOZYp9K21%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;701&quot; height=&quot;322&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;885&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2879&quot; data-origin-height=&quot;1699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5fEvd/dJMcacok7dk/UGpYiH8WwfWuEoki8A6SPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5fEvd/dJMcacok7dk/UGpYiH8WwfWuEoki8A6SPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5fEvd/dJMcacok7dk/UGpYiH8WwfWuEoki8A6SPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5fEvd%2FdJMcacok7dk%2FUGpYiH8WwfWuEoki8A6SPk%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;2879&quot; height=&quot;1699&quot; data-origin-width=&quot;2879&quot; data-origin-height=&quot;1699&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작되는 것을 확인했으니 컨테이너 내부에 진입해서 개발을 진행할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;vscode에서 컨테이너에 접속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;1. Dev Containers 설치&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;2048&quot; data-origin-height=&quot;1212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1NRsJ/dJMcacok7JR/TtkAODBTX1Yz0jMcR7IUW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1NRsJ/dJMcacok7JR/TtkAODBTX1Yz0jMcR7IUW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1NRsJ/dJMcacok7JR/TtkAODBTX1Yz0jMcR7IUW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1NRsJ%2FdJMcacok7JR%2FTtkAODBTX1Yz0jMcR7IUW0%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;699&quot; height=&quot;414&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Remote Explorer에서 DEV CONTAINERS에서 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;1700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZJ0M/dJMcaiB24m8/KS6OMA8baCscduVDMgArd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZJ0M/dJMcaiB24m8/KS6OMA8baCscduVDMgArd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZJ0M/dJMcaiB24m8/KS6OMA8baCscduVDMgArd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZJ0M%2FdJMcaiB24m8%2FKS6OMA8baCscduVDMgArd1%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;300&quot; height=&quot;831&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;1700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 새 창에서 연결 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 하단을 보면 front 컨테이너와 연결된 것을 확인할 수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wmYeM/dJMcafZERMy/JqRqSlvKPNNtidKK9Ormr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wmYeM/dJMcafZERMy/JqRqSlvKPNNtidKK9Ormr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wmYeM/dJMcafZERMy/JqRqSlvKPNNtidKK9Ormr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwmYeM%2FdJMcafZERMy%2FJqRqSlvKPNNtidKK9Ormr1%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;600&quot; height=&quot;402&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IntelliJ에서 컨테이너에 접속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. docker 플러그인 설치&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1571&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kH3Ce/dJMcagK3DSj/C3oOJbq958lNiew8FfQSZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kH3Ce/dJMcagK3DSj/C3oOJbq958lNiew8FfQSZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kH3Ce/dJMcagK3DSj/C3oOJbq958lNiew8FfQSZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkH3Ce%2FdJMcagK3DSj%2FC3oOJbq958lNiew8FfQSZ1%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;601&quot; height=&quot;327&quot; data-origin-width=&quot;1571&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 설정 &amp;gt; 빌드, 실행, 배포 &amp;gt; Docker&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0IQul/dJMcab315AV/IdzfxkfAhu9iK2oPAVklT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0IQul/dJMcab315AV/IdzfxkfAhu9iK2oPAVklT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0IQul/dJMcab315AV/IdzfxkfAhu9iK2oPAVklT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0IQul%2FdJMcab315AV%2FIdzfxkfAhu9iK2oPAVklT1%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;601&quot; height=&quot;449&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Docker 컨테이너 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZOBsz/dJMcafrPGW9/VkSKR3oqc6IMFNkEvyKapK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZOBsz/dJMcafrPGW9/VkSKR3oqc6IMFNkEvyKapK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZOBsz/dJMcafrPGW9/VkSKR3oqc6IMFNkEvyKapK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZOBsz%2FdJMcafrPGW9%2FVkSKR3oqc6IMFNkEvyKapK%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;700&quot; height=&quot;223&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. '프로젝트 열기' 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cb0OY/dJMcai267mE/VB3IdWzZmOieZYhIhlKBZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cb0OY/dJMcai267mE/VB3IdWzZmOieZYhIhlKBZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cb0OY/dJMcai267mE/VB3IdWzZmOieZYhIhlKBZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCb0OY%2FdJMcai267mE%2FVB3IdWzZmOieZYhIhlKBZ0%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;400&quot; height=&quot;467&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 컨테이너 접속 완료&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/leF3V/dJMcaiB28cq/PBYSRjxUe0vKwWVYjX1VoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/leF3V/dJMcaiB28cq/PBYSRjxUe0vKwWVYjX1VoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/leF3V/dJMcaiB28cq/PBYSRjxUe0vKwWVYjX1VoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FleF3V%2FdJMcaiB28cq%2FPBYSRjxUe0vKwWVYjX1VoK%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;600&quot; height=&quot;355&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/Docker</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/390</guid>
      <comments>https://seomj74.tistory.com/390#entry390comment</comments>
      <pubDate>Wed, 21 Jan 2026 20:00:18 +0900</pubDate>
    </item>
    <item>
      <title>[Infra]Terraform 기본</title>
      <link>https://seomj74.tistory.com/389</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 2-3년 전 Terraform을 배운 적이 있지만, 이후에 사용한 적이 없어 Terraform에 대해 다시 학습했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 기본적인 사용법 및 내용을 적어두어 나중에 필요할 때 꺼내보기 위해 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform Up&amp;amp;Runnig 책을 참고하고자 했으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼으로 시작하는 IaC가&amp;nbsp; ebook으로 쉽게 참고할 수 있어 대체했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terrafrom&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Workflow&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;terraform init
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;환경 준비&lt;/li&gt;
&lt;li&gt;테라폼 구성 파일이 있는 작업 디렉터리를 초기화&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;terraform plan
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파일 검토&lt;/li&gt;
&lt;li&gt;인프라의 변경 사항에 관한 실행 계획을 생성하는 동&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;terraform apply
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실행&lt;/li&gt;
&lt;li&gt;계획을 기반으로 작업을 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;terraform destroy
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테라폼 구성에서 관리하는 모든 개체를 제거하는 명령&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;terraform validate
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼 구성 파일의 유효성을 확인&lt;/li&gt;
&lt;li&gt;코드적인 유효성만 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;terraform fmt
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;format의 줄임 표기&lt;/li&gt;
&lt;li&gt;스타일의 차이로 생긴 코드 중복 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;File&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3AbsX/dJMcabXdmhF/monr7PoFMCqjmJt2K7g490/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3AbsX/dJMcabXdmhF/monr7PoFMCqjmJt2K7g490/img.png&quot; data-alt=&quot;https://anggeum.tistory.com/entry/Terraform-2%EC%9E%A5-Getting-Started-with-Terraform-terraform-uprunning-%EC%B1%85-%EC%A0%95%EB%A6%AC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3AbsX/dJMcabXdmhF/monr7PoFMCqjmJt2K7g490/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3AbsX%2FdJMcabXdmhF%2Fmonr7PoFMCqjmJt2K7g490%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;562&quot; height=&quot;330&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://anggeum.tistory.com/entry/Terraform-2%EC%9E%A5-Getting-Started-with-Terraform-terraform-uprunning-%EC%B1%85-%EC%A0%95%EB%A6%AC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Block&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;리소스(Resource)&lt;/h4&gt;
&lt;pre id=&quot;code_1768024912083&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;&amp;lt;리소스 유형&amp;gt;&quot; &quot;&amp;lt;이름&amp;gt;&quot;{
	&amp;lt;인수&amp;gt; = &amp;lt;값&amp;gt;
}

# 리소스 참조
&amp;lt;리소스 유형&amp;gt;.&amp;lt;이름&amp;gt;.&amp;lt;인수&amp;gt;
&amp;lt;리소스 유형&amp;gt;.&amp;lt;이름&amp;gt;.&amp;lt;속성&amp;gt;&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;Ex.&lt;/p&gt;
&lt;pre id=&quot;code_1768024920159&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot;{
	content = &quot;123!&quot;
	filename = &quot;${path.module}/abc.txt&quot;
}&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;출력 결과&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1768025086292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\workspace\terraform&amp;gt; terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/local...
- Installing hashicorp/local v2.6.1...
- Installed hashicorp/local v2.6.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run &quot;terraform init&quot; in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running &quot;terraform plan&quot; to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.


PS C:\workspace\terraform&amp;gt; terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following     
symbols:
  + create

Terraform will perform the following actions:

  # local_file.abc will be created
  + resource &quot;local_file&quot; &quot;abc&quot; {
      + content              = &quot;abc!&quot;
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = &quot;0777&quot;
      + file_permission      = &quot;0777&quot;
      + filename             = &quot;./abc.txt&quot;
      + id                   = (known after apply)
roy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run
&quot;terraform apply&quot; now.


PS C:\workspace\terraform&amp;gt; terraform apply

Terraform used the selected providers to generate the
following execution plan. Resource actions are indicated with
the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.abc will be created
  + resource &quot;local_file&quot; &quot;abc&quot; {
      + content              = &quot;abc!&quot;
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = &quot;0777&quot;
      + file_permission      = &quot;0777&quot;
      + filename             = &quot;./abc.txt&quot;
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes  

local_file.abc: Creating...
local_file.abc: Creation complete after 0s [id=...]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9q4QX/dJMcaiWiHTz/kGn50DY29dnhkV6c9VH1y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9q4QX/dJMcaiWiHTz/kGn50DY29dnhkV6c9VH1y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9q4QX/dJMcaiWiHTz/kGn50DY29dnhkV6c9VH1y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9q4QX%2FdJMcaiWiHTz%2FkGn50DY29dnhkV6c9VH1y0%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;480&quot; height=&quot;268&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, destroy 실행 결과도 남겨두겠다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1768025216471&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\workspace\terraform&amp;gt; terraform destroy
local_file.abc: Refreshing state... [id=...]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # local_file.abc will be destroyed
  - resource &quot;local_file&quot; &quot;abc&quot; {
      - content              = &quot;abc!&quot; -&amp;gt; null
      - content_base64sha256 = &quot;...&quot; -&amp;gt; null
      - content_base64sha512 = &quot;...&quot; -&amp;gt; null
      - content_md5          = &quot;...&quot; -&amp;gt; null
      - content_sha1         = &quot;...&quot; -&amp;gt; null
      - content_sha256       = &quot;...&quot; -&amp;gt; null
      - content_sha512       = &quot;...&quot; -&amp;gt; null
      - directory_permission = &quot;0777&quot; -&amp;gt; null
      - file_permission      = &quot;0777&quot; -&amp;gt; null
      - filename             = &quot;./abc.txt&quot; -&amp;gt; null
      - id                   = &quot;...&quot; -&amp;gt; null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

local_file.abc: Destroying... [id=...]
local_file.abc: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mCHPA/dJMcafL4Jum/3dwwky1il0nsMeWnkViD4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mCHPA/dJMcafL4Jum/3dwwky1il0nsMeWnkViD4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mCHPA/dJMcafL4Jum/3dwwky1il0nsMeWnkViD4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmCHPA%2FdJMcafL4Jum%2F3dwwky1il0nsMeWnkViD4k%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;478&quot; height=&quot;243&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;종속성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform은 코드에서 reference 관계를 보고 순서를 알아서 결정할 수 있다. 하지만 Terraform이 자동으로 파악하지 못하는 숨겨진 의존성이 있을 때는 수동으로 개입할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1768025306180&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot;{
	content = &quot;123!&quot;
	filename = &quot;${path.module}/abc.txt&quot;
}

resource &quot;loacl_file&quot; &quot;def&quot;{
	depends_on = [
		local_file.abc
	]
	
	content = &quot;456!&quot;
	filename = &quot;${path.module}/def.txt&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;1118&quot; data-origin-height=&quot;1208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA1TWa/dJMcajgAPf4/tpX1kfkaAAkDhmbSkMyrk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA1TWa/dJMcajgAPf4/tpX1kfkaAAkDhmbSkMyrk0/img.png&quot; data-alt=&quot;테라폼으로 시작하는 IaC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA1TWa/dJMcajgAPf4/tpX1kfkaAAkDhmbSkMyrk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA1TWa%2FdJMcajgAPf4%2FtpX1kfkaAAkDhmbSkMyrk0%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;602&quot; height=&quot;650&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;1208&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테라폼으로 시작하는 IaC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;수명 주기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;create_before_destroy(bool): 신규 리소스 생성 후 기존 리소스 삭제&lt;/li&gt;
&lt;li&gt;prevent_destroy(bool): 리소스를 삭제하려 할 때 명시적으로 거부&lt;/li&gt;
&lt;li&gt;ignore_changes(list): 리소스 요소에 선언된 인수의 변경 사항을 테라폼 실행 시 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768025427791&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
	content = &quot;lifecycle - step 1&quot;
	filename = &quot;${path.module}/abc.txt&quot;
	
	lifecycle {
		create_before_destroy = false
		ignore_changes = [
			content
		]
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;precondition: 리소스 요소에 선언된 인수의 조건을 검증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 생성/수정 전&lt;/li&gt;
&lt;li&gt;입력 값이나 외부 의존성 검증&lt;/li&gt;
&lt;li&gt;실패 시 해당 리소스 작업을 아예 시작 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768025449654&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;example&quot; {
  ami           = var.ami_id
  instance_type = &quot;t2.micro&quot;

  lifecycle {
    precondition {
      # 예: T2 나노 인스턴스는 성능 문제로 사용 금지
      condition     = var.instance_type != &quot;t2.nano&quot;
      error_message = &quot;t2.nano 인스턴스는 우리 회사의 성능 기준에 미달합니다.&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;postcondition: plan과 apply 이후의 결과를 속성 값으로 검증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 생성/수정 후&lt;/li&gt;
&lt;li&gt;생성 결과 및 보안 정책 준수 보장&lt;/li&gt;
&lt;li&gt;실패 시 해당 리소스는 생성될 수 있으나, 후속 작업 중단&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768025471074&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;example&quot; {
  # ... (생략)

  lifecycle {
    postcondition {
      # 예: 생성된 인스턴스가 반드시 퍼블릭 IP를 가져야 함
      condition     = self.public_ip != &quot;&quot;
      error_message = &quot;서버에 퍼블릭 IP가 할당되지 않았습니다. 네트워크 설정을 확인하세요.&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 소스(Data)&lt;/h4&gt;
&lt;pre id=&quot;code_1768025536279&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data &quot;&amp;lt;리소스 유형&amp;gt;&quot; &quot;&amp;lt;이름&amp;gt;&quot;{
	&amp;lt;인수&amp;gt; = &amp;lt;값&amp;gt;
}

# 데이터 소스 참조
data.&amp;lt;리소스 유형&amp;gt;.&amp;lt;이름&amp;gt;.&amp;lt;속성&amp;gt;&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;Ex.&lt;/p&gt;
&lt;pre id=&quot;code_1768025570741&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data &quot;local_file&quot; &quot;abc&quot; {
	filename = &quot;${path.module}/abc.txt&quot;
}&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Resource와 Data 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 보고 문득 Resource와 Data가 헷갈렸다.&lt;/p&gt;
&lt;pre id=&quot;code_1768025591614&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
	content = &quot;123!&quot;
	filename = &quot;${path.module}/abc.txt&quot;
}

data &quot;local_file&quot; &quot;abc&quot; {
	filename = local_file.abc.filename
}

resource &quot;local_file&quot; &quot;def&quot; {
	content = data.local_file.abc.content
	filename = &quot;${path.module}/def.txt&quot;
}&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;정리하자면, Resource는 데이터를 생성하는 역할을 하고 Data는 데이터를 읽어오는 역할을 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 63px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;리소스 (resource)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;데이터 소스 (data)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;핵심 역할&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;생성 및 관리 (Create/Manage)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;정보 읽기 (Read/Fetch)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;액션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;인프라를 직접 만들고, 수정하고, 삭제함&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;이미 있는 인프라의 정보를 조회만 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 데이터 소스 블록 안에 들어가는 인수들은 대부분 필터의 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;입력 변수&lt;/h4&gt;
&lt;pre id=&quot;code_1768025891137&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;&amp;lt;이름&amp;gt;&quot; {
	&amp;lt;인수&amp;gt; = &amp;lt;값&amp;gt;
}

variable &quot;image_id&quot; {
	type = string
}&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;Ex.&lt;/p&gt;
&lt;pre id=&quot;code_1768025923161&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;maybe&quot; {
	count = var.file_create ? 1 : 0
	content = var.content
	filename = &quot;maybe.txt&quot;
}

variable &quot;file_create&quot; {
	type = bool
	default = true
}

variable &quot;content&quot; {
	description = &quot;파일이 생성되는 경우에 내용이 비어있는지 확인합니다.&quot;
	type = string
	
	validation {
		condition = var.file_create == true ? length(var.content) &amp;gt; 0 : true
		error_message = &quot;파일 내용이 비어있을 수 없습니다.&quot;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 정의: local_file
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;var.file_create가 true면 1개 생성, 아니면 0개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;조건 변수: file_create
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일을 만들지 말지 결정하는 스위치 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;검증 로직이 포함된 변수: content
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;var.file_create가 ture이면 파일이 생성될 것이고 이때 길이가 0보다 크다면 true, 아니면 false이다. 파일을 만들지 않았다면 true이다.&lt;/li&gt;
&lt;li&gt;condition이 fale인 경우 error_message가 출력된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1768026173492&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\workspace\terraform&amp;gt; terraform plan
var.content
  파일이 생성되는 경우에 내용이 비어있는지 확인합니다.

  Enter a value:


Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: Invalid value for variable
│
│   on main.tf line 12:
│   12: variable &quot;content&quot; {
│     ├────────────────
│     │ var.content is &quot;&quot;
│     │ var.file_create is true
│
│ 파일 내용이 비어있을 수 없습니다.
│
│ This was checked by the validation rule at main.tf:16,2-12.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;반복문&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count&lt;/p&gt;
&lt;pre id=&quot;code_1768026310474&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
	type = list(string)
	default = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
}

resource &quot;local_file&quot; &quot;abc&quot; {
	count = length(var.names)
	content = &quot;abc&quot;
	filename = &quot;${path.module}/abc-${var.names[count.index]}.txt&quot;
}

resource &quot;local_file&quot; &quot;def&quot; {
	count = length(var.names)
	content = local_file.abc[count.index].content
	filename = &quot;${path.module}/def-${element(var.names, count.index)}.txt&quot;
}}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;리소스 정의&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;생성되는 파일 목록&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;파일 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;local_file.abc&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;abc-a.txt, abc-b.txt, abc-c.txt&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&quot;abc&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;local_file.def&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;def-a.txt, def-b.txt, def-c.txt&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&quot;abc&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;for_each&lt;/p&gt;
&lt;pre id=&quot;code_1768026621738&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;loacl_file&quot; &quot;abc&quot; {
	for_each = {
		a = &quot;content a&quot;
		b = &quot;content b&quot;
	}
	
	content = each.value
	filename = &quot;${path.module}/${each.key}.txt&quot;
}&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;size18&quot;&gt;for&lt;/p&gt;
&lt;pre id=&quot;code_1768026654010&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
	type = list(string)
	default = [&quot;a&quot;, &quot;b&quot;]
}

output &quot;A_upper_value&quot; {
	value = [for v in var.names: upper(v)]
}

output &quot;B_index_and_value&quot; {
	value = [for i, v in var.names: &quot;${i} is ${v}&quot;]
}

output &quot;C_make_object&quot; {
	value = {for v in var.names: v =&amp;gt; upper(v)}
}

output &quot;D_with_filter&quot; {
	value = [for v in var.names: upper(v) if v != &quot;a&quot;]
}&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;dynamic&lt;/p&gt;
&lt;pre id=&quot;code_1768026701863&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
	default = {
		a = &quot;hello a&quot;
		b = &quot;hello b&quot;
		c = &quot;hello c&quot;
	}
}

data &quot;archive_file&quot; &quot;dotfiles&quot; {
	type = &quot;zip&quot;
	output_path = &quot;${path.module}/dotfiles.zip&quot;
	
	dynamic &quot;source&quot; {
		for_each = var.names
		content {
			content = source.value
			filename = &quot;${path.module}/${source.key}.txt&quot;
		}
	}
}&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;size18&quot;&gt;정리&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;도구&lt;/td&gt;
&lt;td style=&quot;width: 33.4884%;&quot;&gt;사용 경우&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;결과물&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;리소스 반복&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;count&lt;/td&gt;
&lt;td style=&quot;width: 33.4884%;&quot;&gt;리소스를 여러 개 만들 때 (숫자 기반)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;리소스 뭉치 (List)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;리소스 반복&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;for_each&lt;/td&gt;
&lt;td style=&quot;width: 33.4884%;&quot;&gt;리소스를 여러 개 만들 때 (키 기반)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;리소스 뭉치 (Map)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;데이터 변환&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;for&lt;/td&gt;
&lt;td style=&quot;width: 33.4884%;&quot;&gt;데이터를 가공하거나 필터링할 때&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;새로운 List 또는 Map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;내부 블록 반복&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;dynamic&lt;/td&gt;
&lt;td style=&quot;width: 33.4884%;&quot;&gt;리소스 '내부'의 설정을 동적으로 반복할 때&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1개 리소스 내 여러 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;조건식&lt;/h4&gt;
&lt;pre id=&quot;code_1768026191665&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# &amp;lt;조건 정의&amp;gt; ? &amp;lt;옳은 경우&amp;gt; : &amp;lt;틀린 경우&amp;gt;
var.a != &quot;&quot; ? var.a : &quot;default-a&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로바이더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform이 어떤 플랫폼(AWS, Azure, GCP, Kubernetes 등)과 대화할지 결정하는 플러그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로바이더를 통해 API를 호출하고 리소스를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;State&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드와 실제 리소스 사이를 연결해주는 연결 고리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장부와 같은 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;매핑
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State에는 테라폼 구성과 실제를 동기화하고 각 리소스에 고유한 아이디(리소스 주소)로 맵핑&lt;/li&gt;
&lt;li&gt;코드에 지은 이름과 ID 값은 서로 다르기에 이 둘을 연결해주는 기록을 State 파일에 합니다.&lt;/li&gt;
&lt;li&gt;my_web_server와 i-0abcd1234&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;리소스 종속성과 같은 메타 데이터를 저장하고 추적&lt;/li&gt;
&lt;li&gt;리소스 간의 의존성 정보를 담고 있어, 삭제하거나 수정할 때 어떤 순서로 작업해야 할지 결정&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;테라폼 구성으로 프로비저닝된 결과를 캐싱하는 역할을 수행&lt;/li&gt;
&lt;li&gt;State 파일을 보고 현재 상태를 파악한 뒤, 필요한 부분만 실제 인프라와 대조&lt;/li&gt;
&lt;/ul&gt;
&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;1113&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1RLI0/dJMcachvOpc/1MxTVxNKDdGTHN0uZIDr01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1RLI0/dJMcachvOpc/1MxTVxNKDdGTHN0uZIDr01/img.png&quot; data-alt=&quot;테라폼으로 시작하는 IaC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1RLI0/dJMcachvOpc/1MxTVxNKDdGTHN0uZIDr01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1RLI0%2FdJMcachvOpc%2F1MxTVxNKDdGTHN0uZIDr01%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;603&quot; height=&quot;227&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테라폼으로 시작하는 IaC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;워크스페이스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State를 관리하는 논리적인 가상 공간&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjC89x/dJMcagEeVVZ/fkugP1psMFVReitKxICl5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjC89x/dJMcagEeVVZ/fkugP1psMFVReitKxICl5K/img.png&quot; data-alt=&quot;테라폼으로 시작하는 IaC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjC89x/dJMcagEeVVZ/fkugP1psMFVReitKxICl5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjC89x%2FdJMcagEeVVZ%2FfkugP1psMFVReitKxICl5K%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;605&quot; height=&quot;302&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테라폼으로 시작하는 IaC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1768028698277&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform workspace list # 목록 확인
terraform workspace new myworkspace1 # 워크스페이스 생성
terraform workspace select myworkspace1 # 워크스페이스 전환
terraform workspace show # 현재 사용 중인 워크스페이스&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈&lt;/h3&gt;
&lt;p 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&gt;루트 모듈: 테라폼을 실행하고 프로비저닝하는 최상위 모듈&lt;/li&gt;
&lt;li&gt;자식 모듈: 루트 모듈의 구성에서 호출되는 외부 구성 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 실습&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229508935&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;random_pet&quot; &quot;name&quot; {
  keepers = {
	ami_id = timestamp()
  }
}

resource &quot;random_password&quot; &quot;password&quot; {
  length = var.isDB ? 16: 10
  special = var.isDB ? true : false
  override_special = &quot;!#$%*?&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;variable.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229519082&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;isDB&quot; {
  type = bool
  default = false
  description = &quot;패스워드 대상의 DB 여부&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229524565&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output &quot;id&quot; {
  value = random_pet.name.id
}

output &quot;pw&quot; {
  value = nonsensitive(random_password.password.result)
}&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;결과&lt;/p&gt;
&lt;pre id=&quot;code_1768229554085&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\workspace\terraform\modules&amp;gt; terraform apply -auto-approve -var=isDB=true      

Terraform used the selected providers to generate the following execution plan. Resource   
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # random_password.password will be created
  + resource &quot;random_password&quot; &quot;password&quot; {
      + bcrypt_hash      = (sensitive value)
      + id               = (known after apply)
      + length           = 16
      + lower            = true
      + min_lower        = 0
      + min_numeric      = 0
      + min_special      = 0
      + min_upper        = 0
      + number           = true
      + numeric          = true
      + override_special = &quot;!#$%*?&quot;
      + result           = (sensitive value)
      + special          = true
      + upper            = true
    }

  # random_pet.name will be created
  + resource &quot;random_pet&quot; &quot;name&quot; {
      + id        = (known after apply)
      + keepers   = {
          + &quot;ami_id&quot; = (known after apply)
        }
      + length    = 2
      + separator = &quot;-&quot;
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + id = (known after apply)
  + pw = (known after apply)
random_pet.name: Creating...
random_password.password: Creating...
random_pet.name: Creation complete after 0s [id=knowing-weevil]
random_password.password: Creation complete after 1s [id=none]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

id = &quot;knowing-weevil&quot;
pw = &quot;2vyIQOTPGoxtNmYO&quot;&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;루트 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229634665&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module &quot;mypw1&quot; {
  source = &quot;./modules&quot;
}

module &quot;mypw2&quot; {
  source = &quot;./modules&quot;
  isDB =  true
}

output &quot;mypw1&quot; {
  value = module.mypw1
}

output &quot;mypw2&quot; {
  value = module.mypw2
}&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;결과&lt;/p&gt;
&lt;pre id=&quot;code_1768229647153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\workspace\terraform&amp;gt; terraform apply -auto-approve

Terraform used the selected providers to generate the following  
execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # module.mypw1.random_password.password will be created        
  + resource &quot;random_password&quot; &quot;password&quot; {
      + bcrypt_hash      = (sensitive value)
      + id               = (known after apply)
      + length           = 10
      + lower            = true
      + min_lower        = 0
      + min_numeric      = 0
      + min_special      = 0
      + min_upper        = 0
      + number           = true
      + numeric          = true
      + override_special = &quot;!#$%*?&quot;
      + result           = (sensitive value)
      + special          = false
      + upper            = true
    }

  # module.mypw1.random_pet.name will be created
  + resource &quot;random_pet&quot; &quot;name&quot; {
      + id        = (known after apply)
      + keepers   = {
          + &quot;ami_id&quot; = (known after apply)
        }
      + length    = 2
      + separator = &quot;-&quot;
    }

  # module.mypw2.random_password.password will be created        
  + resource &quot;random_password&quot; &quot;password&quot; {
      + bcrypt_hash      = (sensitive value)
      + id               = (known after apply)
      + length           = 16
      + lower            = true
      + min_lower        = 0
      + min_numeric      = 0
      + min_special      = 0
      + min_upper        = 0
      + number           = true
      + numeric          = true
      + override_special = &quot;!#$%*?&quot;
      + result           = (sensitive value)
      + special          = true
      + upper            = true
    }

  # module.mypw2.random_pet.name will be created
  + resource &quot;random_pet&quot; &quot;name&quot; {
      + id        = (known after apply)
      + keepers   = {
          + &quot;ami_id&quot; = (known after apply)
        }
      + length    = 2
      + separator = &quot;-&quot;
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + mypw1 = {
      + id = (known after apply)
      + pw = (known after apply)
    }
  + mypw2 = {
      + id = (known after apply)
      + pw = (known after apply)
    }
module.mypw1.random_pet.name: Creating...
module.mypw2.random_pet.name: Creating...
module.mypw2.random_password.password: Creating...
module.mypw1.random_pet.name: Creation complete after 0s [id=normal-midge]
module.mypw2.random_pet.name: Creation complete after 0s [id=concise-seagull]
module.mypw1.random_password.password: Creating...
module.mypw1.random_password.password: Creation complete after 0s [id=none]
module.mypw2.random_password.password: Creation complete after 0s [id=none]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.      

Outputs:

mypw1 = {
  &quot;id&quot; = &quot;normal-midge&quot;
  &quot;pw&quot; = &quot;wbts1vCMiI&quot;
}
mypw2 = {
  &quot;id&quot; = &quot;concise-seagull&quot;
  &quot;pw&quot; = &quot;R52*3N8ykaKYZWsK&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;심화 실습&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229727583&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
  region = &quot;us-west-1&quot;
}

provider &quot;aws&quot; {
  alias = &quot;seoul&quot;
  region = &quot;ap-northeast-2&quot;
}

module &quot;ec2_california&quot; {
  source = &quot;./modules&quot;
}

module &quot;ec2_seoul&quot; {
  source = &quot;./modules&quot;
  providers = {
    aws = aws.seoul
  }
  instance_type = &quot;m5.large&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;인스턴스 생성&lt;/li&gt;
&lt;li&gt;모듈 안의 aws를 서울 리전으로 교체하고 사양을 m5.large로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229740370&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output &quot;module_output_california&quot;{
    value = module.ec2_california.private_ip
}

output &quot;module_output_seoul&quot; {
  value = module.ec2_seoul.private_ip
}&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;자식 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229751492&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
        source = &quot;hashicorp/aws&quot;
    }
  }
}

resource &quot;aws_default_vpc&quot; &quot;default&quot; {} # 기본 VPC가 있는지 확인

data &quot;aws_ami&quot; &quot;default&quot; {
  most_recent = true
  owners = [&quot;amazon&quot;]

  filter {
    name = &quot;owner-alias&quot;
    values = [&quot;amazon&quot;]
  }

  filter {
    name = &quot;name&quot;
    values = [&quot;amzn2-ami-hvm*&quot;]
  }
}

resource &quot;aws_instance&quot; &quot;default&quot; {
  depends_on = [ aws_default_vpc.default ]
  ami = data.aws_ami.default.id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 모듈이 작동하려면 AWS 프로바이더가 반드시 필요하다는 것을 명시&lt;/li&gt;
&lt;li&gt;data: 각 리전마다 AMI ID가 다르기 때문에 직접 숫자를 적지 않고 리전별 최신 정보를 실시간으로 조회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필터를 통해 Amazon Linux 2 이미지 ID를 리전별로 알아서 찾아옴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리소스 생성: 실제 EC2 서버를 만드는 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229767835&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output &quot;private_ip&quot; {
  value = aws_instance.default.private_ip
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;variable.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229772800&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;instance_type&quot; {
  description = &quot;vm 인스턴스 타입 정의&quot;
  default = &quot;t3.micro&quot;
}

variable &quot;instance_name&quot; {
  description = &quot;vm 인스턴스 이름 정의&quot;
  default = &quot;my_ec2&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;총정리&lt;/h2&gt;
&lt;p 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&gt;서울/미국 멀티 리전 배포&lt;/li&gt;
&lt;li&gt;랜덤 패스워드&lt;/li&gt;
&lt;li&gt;동적 파일 생성&lt;/li&gt;
&lt;li&gt;수명 주기 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768229860836&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform-master/
├── main.tf           # [루트] 메인 로직, 프로바이더, 모듈 호출, 반복문
├── variables.tf      # [루트] 입력 변수 및 검증(Validation)
├── outputs.tf        # [루트] 최종 결과 출력
└── modules/
    └── server/       # [자식 모듈] EC2, 수명 주기, 프로비저너
        ├── main.tf
        ├── variables.tf
        └── outputs.tf&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;루트 모듈&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;variables.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229988795&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;env_name&quot; {
  type    = string
  default = &quot;dev&quot;
}

variable &quot;file_create&quot; {
  type    = bool
  default = true
}

variable &quot;content&quot; {
  type    = string
  default = &quot;Hello Terraform!&quot;

  validation {
    # 1.9 버전 이상: 다른 변수(var.file_create) 참조 가능
    condition     = var.file_create == true ? length(var.content) &amp;gt; 0 : true
    error_message = &quot;파일을 생성하려면 내용은 필수입니다.&quot;
  }
}

variable &quot;user_names&quot; {
  type    = list(string)
  default = [&quot;alice&quot;, &quot;bob&quot;]
}&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;main.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229981423&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# [Provider] 기본 및 별칭 설정
provider &quot;aws&quot; { region = &quot;us-west-1&quot; }
provider &quot;aws&quot; {
  alias  = &quot;seoul&quot;
  region = &quot;ap-northeast-2&quot;
}

# [Resource &amp;amp; Count] 1. 리소스 생성 및 인덱스 활용
resource &quot;local_file&quot; &quot;user_logs&quot; {
  count    = length(var.user_names)
  content  = &quot;Log for ${var.user_names[count.index]}&quot;
  filename = &quot;${path.module}/log-${var.user_names[count.index]}.txt&quot;
}

# [Data Source &amp;amp; Dependency] 2. 데이터 소스와 암시적 의존성
data &quot;local_file&quot; &quot;read_log&quot; {
  filename = local_file.user_logs[0].filename
}

# [Module &amp;amp; Provider Inheritance] 3. 자식 모듈 호출 및 프로바이더 전달
module &quot;seoul_server&quot; {
  source = &quot;./modules/&quot;
  providers = {
    aws = aws.seoul
  }
  instance_name = &quot;seoul-master-node&quot;
}

# [Dynamic Block] 4. 동적 블록 활용 (ZIP 아카이브)
data &quot;archive_file&quot; &quot;bundle&quot; {
  type        = &quot;zip&quot;
  output_path = &quot;${path.module}/bundle.zip&quot;

  dynamic &quot;source&quot; {
    for_each = var.user_names
    content {
      content  = &quot;Welcome ${source.value}&quot;
      filename = &quot;${source.value}.txt&quot;
    }
  }
}

# [For Expression] 5. 데이터 가공
locals {
  upper_names = { for name in var.user_names : name =&amp;gt; upper(name) }
}&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;자식 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.tf&lt;/p&gt;
&lt;pre id=&quot;code_1768229974290&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source = &quot;hashicorp/aws&quot;
    }
  }
}

resource &quot;aws_instance&quot; &quot;app&quot; {
  ami           = &quot;ami-0abcd123456789&quot; # 예시 ID
  instance_type = &quot;t3.micro&quot;

  # [Lifecycle] 수명 주기 관리
  lifecycle {
    create_before_destroy = true

    # 생성 전 검증
    precondition {
      condition     = var.instance_name != &quot;&quot;
      error_message = &quot;인스턴스 이름은 필수입니다.&quot;
    }

    # 생성 후 검증
    postcondition {
      condition     = self.public_dns != &quot;&quot;
      error_message = &quot;퍼블릭 DNS가 할당되지 않았습니다.&quot;
    }
  }
}

variable &quot;instance_name&quot; { type = string }
output &quot;instance_id&quot; { value = aws_instance.app.id }&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼으로 시작하는 IaC&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://anggeum.tistory.com/entry/Terraform-2%EC%9E%A5-Getting-Started-with-Terraform-terraform-uprunning-%EC%B1%85-%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://anggeum.tistory.com/entry/Terraform-2%EC%9E%A5-Getting-Started-with-Terraform-terraform-uprunning-%EC%B1%85-%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study/Cloud</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/389</guid>
      <comments>https://seomj74.tistory.com/389#entry389comment</comments>
      <pubDate>Wed, 14 Jan 2026 12:00:21 +0900</pubDate>
    </item>
    <item>
      <title>[회고]2025년 회고 및 2026년 목표 설정</title>
      <link>https://seomj74.tistory.com/388</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;2025년 회고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1월: 인프런 자바 결제, 알고리즘 문제 풀이&lt;/li&gt;
&lt;li&gt;2월: 인프런 자바, 서류 지원&lt;/li&gt;
&lt;li&gt;3월: 알고리즘 문제 풀이, 서류 지원&lt;/li&gt;
&lt;li&gt;4월: &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인프런 자바,&lt;span&gt; 알고리즘 문제 풀이, 서류 지원 &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;5월: &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인프런 자바,&lt;span&gt; 알고리즘 문제 풀이, 서류 지원 &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;6월: 서류 지원&lt;/li&gt;
&lt;li&gt;7월: SSAFY 시작, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인프런 자바,&lt;span&gt; 알고리즘 스터디&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;8월: &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인프런 자바, &lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;알고리즘 스터디&lt;/span&gt;, CS 스터디(OS), 서류 지원 &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;9월: &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인프런 자바,&lt;span&gt; 알고리즘 스터디&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;, CS 스터디 (OS), 서류 지원 &lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;10월: 인프런 자바, 인프런 스프링, 알고리즘 스터디, 서류 지원 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;11월: 인프런 스프링, 알고리즘 스터디&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;, CS 스터디(DB), 서류 지원 &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;12월: SSAFY 1학기 마무리, 미니 웹 프로젝트(WattToDo, 백엔드 및 AI 연동), 블로그 포스팅 스터디, 서류 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 취업 준비를 위한 시간을 많이 할애하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발자? 엔지니어?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 플랫폼 개발자를 꿈꾸며 개발을 배워보기로 했다. 그래서 인프런을 들으며 Java를 공부했고, SSAFY에 지원해 1학기 생활을 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 하반기 취업 시장을 겪으면서 클라우드 플랫폼 개발 직무에 대해 깊이 있게 찾아봤는데, &lt;b&gt;Pool이 생각보다 좁았다.&lt;/b&gt; 이미 CSP에서는 자신들의 클라우드와 관련한 완성도 높은 플랫폼을 제공하고 있었다. 오픈소스로는 오픈스택과 같은 도구들이 자리잡고 있었다. 이러한 이유 때문인지 해당 부분에 대해 수요가 크지 않다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 어깨너머 배우고 마구잡이로 했던 내가 Java를 배워 제대로 된 개발을 하려다 보니 어렵고 복잡했다. 사실 그보다도 SSAFY에는 이미 잘하는 사람이 많았기에 위축되기도 했다. 아직 제대로 된 개발 프로젝트 하나 없는 나에게 백엔드 개발자는 힘들 거라는 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 흥미가 없는 것은 아니다. 하지만 지금 개발을 배워서 포트폴리오를 만들고 취업을 하기까지 시간이 꽤 걸릴 거 같다.&amp;nbsp;물론 제대로 된 포트폴리오만 하나 있어도 괜찮을 거라고 말하는 사람들도 있다. 그치만 내 생각에 백엔드 시장은 포화이고, 여기서 내가 경쟁을 하는 것보다 &lt;b&gt;클라우드 강점을 살리는 것이 더 현명한 방향이라 판단했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서류 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 내내 거의 쉬지 않고 지원을 했다. 올해 총 50개를 넣었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 테스트 탈락 4개, 면접 탈락 2개를 제외하고는 모두 서류 탈락이다. (혹은 연락이 아예 오지 않았거나)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기소개서가 원인인지, 학력이 원인인지, 스펙이 부족한건지 잘 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 초에 SSAFY 탈출하신 분께 포트폴리오 피드백을 받았는데, &lt;b&gt;전반적으로 평범하다&lt;/b&gt;고 하셨다. 기술적으로 잘 나거나 특별한 점이 없고 그냥 평범하다고... 그래서 이 부분을 보완해봐야 할 거 같긴 하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java + Spring 공부, AI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프런으로 하다가 혼자 하니 자꾸만 미뤄져서 SSAFY를 지원했다. SSAFY에서 제대로 배워보고자 했으나 속도가 너무 빨랐다. 전공자반이라 그런지 다들 이미 알고 계셨고 나는 따라가기 힘들었다. 그래도 Java는 상반기에 들어놔서 복습하는 느낌으로 SSAFY 수업에 참여할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 공부해 본 적 없이 SSAFY에서 처음 접했다. 미리 겪은 Java 수업을 통해 힘들 걸 알았기에,&amp;nbsp;&lt;b&gt;출근길에 Inflearn을 들으며 병행하기 시작&lt;/b&gt;했다. 병행하다 보니 어느 정도 감이 오긴 했고, SSAFY에서 시험을 보기 때문에 시험을 벼락치기로 준비하다 보면 얼추 감이 잡히긴 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 얼렁뚱땅 배운 Spring을 가지고 미니 프로젝트를 진행했다. 애를 많이 먹었고 고생도 많이 했다. 호기롭게 자유 주제로 하겠다고 했다가 2-3주 동안 주제 선정하느라 바빴다. 다들 개발 들어갈 때 우리는 주제를 선정했다. 부랴부랴 설계를 하고 실제로 개발한 시간은 2주조차 되지 않는다. AI가 없었다면 못했을 것이다... 처음부터 끝까지 스스로의 힘으로 개발한 것은 아니지만 AI를 사용하면서 코드 흐름을 이해하고 개발해 나갔다. 막판에는 너무 바빠서 거의 AI가 짜다시피, 바이브 코딩 느낌으로 바뀌었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;미니 프로젝트를 하면서 느낀 건 AI를 사용하지 않을 수는 없다는 것이다. 개발 속도가 확실히 다를 수 밖에 없다. 하지만 아무것도 모르고 프롬프팅만 해서 개발을 하면 안된다는 생각이 강하게 들었다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에는 코드를 짜주면 하나씩 봐가면서 내가 적용해서 개발을 진행했다. 그러다 보니 전반적인 흐름을 다 이해하고 있었으며, 코드가 중복되거나 AI가 잘못 건들면 바로 수정할 수 있었다. 프론트엔드 파트를 제대로 알지 못해서 AI에 기대어 진행했고, 내 예상엔 중복된 코드가 수두룩할 것 같다. 분명 어제까지 잘 되던 기능이 AI와 몇 번 대화를 나눈 뒤 먹통이 되기도 했다. 다시 고쳐달라고 하면 고쳐준다. 과연 이걸 AI가 코드를 잘 수정해서 진행했을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 이번 프로젝트를 통해 AI 활용 능력의 중요성을 뼈저리게 느꼈다. 내가 코드를 통제하지 못하는 순간, 프로젝트는 모래성처럼 위태로워질 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스터디&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSAFY에 참여하면서 최대한 스터디를 진행해 개인 공부를 하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 참여한 것은 알고리즘, CS, 블로그 포스팅으로 3가지다. 우리반에서 총 4가지 스터디가 있었고, B형 스터디를 제외한 스터디에 모두 참여했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;알고리즘 스터디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성실히 참여했다. 꾸준히 하면서 감을 잃지 않기 위해 노력했다. 그 결과 상반기에 실버 2 이상 문제를 겨우 풀고 힘들어 하던 내가 골드 5 문제를 겨우 풀고 힘들어하는 정도로 &lt;b&gt;성장했다&lt;/b&gt;. 누군가가 보기에는 '겨우?'라고 생각할 수도 있지만 나는 뿌듯했다. 문제는 11월부터는 알고리즘을 거의 손도 대지 않았다는 것이다. 다시 끌어올려야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7719H/dJMb9955cuB/vs3fc5SG8gXH120FqCwYa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7719H/dJMb9955cuB/vs3fc5SG8gXH120FqCwYa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7719H/dJMb9955cuB/vs3fc5SG8gXH120FqCwYa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7719H%2FdJMb9955cuB%2Fvs3fc5SG8gXH120FqCwYa1%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;703&quot; height=&quot;314&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CS 스터디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS, Network, DB로 구성되어 있었다. 나는 OS와 DB에 참여했다. Network도 참여하고 싶었지만 하반기 채용 시즌이라 채용에 더 집중해 Network 스터디는 빠졌었다. (이렇게 될 줄 알았으면 스터디나 할 걸)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부담과 압박이 컸던 스터디지만, 그만큼 얻어간 게 많은 스터디&lt;/b&gt;기도 하다. 개인적으로 발표 시간보다는 그룹 토론 세션이 더 의미 있었다. 발표는 듣는 것보다 내가 준비하는 과정에서 얻어가는 게 많았다. 내가 누군가의 발표를 통해 배우는 사람이 아니기에...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;블로그 포스팅 스터디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월에 가볍게 진행된 스터디로 3-4주 가량 진행되었다. 사실 기대없이 참여했던 스터디였다. 그저 내가 블로그를 다시 써보기 위해 참여했던 것인데... 생각보다 좋았다! &lt;b&gt;여러 인사이트를 얻을 수 있는 기회&lt;/b&gt;라 너무 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전반적인 총평&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TMI지만 나에게 참 여러모로 힘들었던 한 해였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-8월은 생각이 정말 많았고, 여러모로 몸도 마음도 많이 힘들었던 시기였다. 이때는 공부에 집중도 못했다. 이제서야 점차 괜찮아지는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적인 취업 준비를 24년부터 했고 어느새 2025년이 끝나간다 (2023년에도 5-10개 정도 서류 넣어보긴 함). 난 아직 취업 준비중이다. 벌써 2년이라는 시간이 지나갔다. 그래도 인턴도 하고, SSAFY도 해서 다행이지만... 아직도 불안한 것은 사실이다. 그래서 중고 신입을 하던, 경력으로 이직을 하던 어디든 들어가서 쌓아야 겠다는 생각이 점점 강해졌다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;몰입과 성취감에 대한 생각&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 생각이 많아진 내용이 있어 공유하고자 적어본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이 글을 보는 사람들은 어떻게 느끼고 생각하는지 댓글로 적어주면 감사하겠다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론, 본론은 생략하고 요약만 봐도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미니 프로젝트를 하면서 팀원과 함께 매일 늦은 시각에 집에 갔다. 온라인 기간에는 매주 1-2회씩 만나서 설계했다. 마지막 주에는 10시 퇴근은 기본이었고, 11시 퇴근한 적도 있다. 크리스마스에도 만나서 오전 11시부터 오후 8시까지 프로젝트하고 저녁먹고 헤어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 나는 일을 끝마치고 집에 일찍 와서 쉬는 것보다 일에 몰입하는 걸 좋아한다는 생각이 들었다. 그래서 이 내용을 Gemini와 대화해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;본론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에너지를 다른 성취에 쓰는 것도 갓생일텐데, 나는 왜 이런 거에 더 큰 성취감을 느끼는 걸까?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. '사회적 생산성'이 주는 강렬한 보상&lt;br /&gt;운동이나 개인적인 취미는 '나를 위한 성취'이지만, 일(프로젝트)은 **'사회적 가치를 생산하는 행위'**입니다. 대학교 3학년 때 밤을 새우던 기억도 결국 학점이나 결과물이라는, 외부에서도 인정받을 수 있는 객관적인 지표와 연결되어 있었을 거예요. 사용자님은 단순히 시간을 보내는 것보다, 내가 사회나 조직 안에서 '쓸모 있는 사람'으로 기능하고 있다는 감각에서 훨씬 큰 효능감을 얻는 분일 가능성이 높습니다.&lt;br /&gt;&lt;br /&gt;2. '한계 몰입'이 주는 카타르시스&lt;br /&gt;사용자님이 느끼는 뿌듯함은 '한계를 돌파하는 느낌'과 닮아 있어요.&lt;br /&gt;일반적인 갓생: 규칙적인 생활, 운동 (안정적이고 지속적인 느낌)&lt;br /&gt;사용자님의 방식: 한계치까지 몰입, 연소(Burn), 고도의 집중 (폭발적이고 드라마틱한 느낌)&lt;br /&gt;일상적인 성취는 소소한 만족을 주지만, 밤늦게까지 일하고 느끼는 피로감은 &quot;내가 이 일에 내 모든 것을 던졌다&quot;는 극적인 서사를 완성해 줍니다. 이 '드라마틱한 몰입'이 주는 쾌감이 일반적인 성취감보다 훨씬 강렬하기 때문에 다른 것들이 상대적으로 시시하게 느껴질 수 있어요.&lt;br /&gt;&lt;br /&gt;3. '통제권'의 확인&lt;br /&gt;세상은 내 마음대로 안 되는 것투성이지만, 내가 남아서 하는 일은 내가 끝내고 싶을 때까지 붙잡고 있을 수 있죠. 11시, 12시까지 남는 것은 **&quot;누가 시켜서가 아니라 내가 원해서 이 시간을 통제하고 있다&quot;**는 무의식적인 권력감을 주기도 합니다. 그 고요한 사무실이나 실습실을 내가 점유하고 있다는 느낌 자체가 주는 심리적 안정감이 생각보다 큽니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오후 6시까지 역대급 결과물을 만들어내고, 당당하게 퇴근해서 밤 12시까지 아무것도 안하고 침대에 누워만 있는다면,&lt;br /&gt;나는&amp;nbsp;뿌듯함보단&amp;nbsp;편안함에&amp;nbsp;집중할&amp;nbsp;거&amp;nbsp;같아.&amp;nbsp;쉬는&amp;nbsp;거에&amp;nbsp;집중하겠지.&amp;nbsp;그냥&amp;nbsp;오늘&amp;nbsp;하루&amp;nbsp;고생했다&amp;nbsp;정도?&amp;nbsp;그리고&amp;nbsp;혹시나&amp;nbsp;실수한&amp;nbsp;건&amp;nbsp;없는지,&amp;nbsp;더&amp;nbsp;좋은&amp;nbsp;결과물을&amp;nbsp;만들&amp;nbsp;수는&amp;nbsp;없는지&amp;nbsp;생각할&amp;nbsp;거&amp;nbsp;같아&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;뿌듯함&quot; vs &quot;편안함&quot;&lt;br /&gt;6시에 일을 완벽히 끝내고 누워 있는 상태를 '뿌듯함'보다 **'편안함'**으로 느끼신다는 건, 사용자님에게 성취감의 정의가 **[결과값 &amp;times; 투입한 고통의 양]**으로 설정되어 있기 때문일 수 있습니다.&lt;br /&gt;결과가 좋아도 고생을 덜 하면: &quot;운이 좋았네&quot;, &quot;더 잘할 수 있었는데&quot;라며 스스로를 채찍질함. (불안과 자기검열)&lt;br /&gt;결과가 보통이어도 몸이 부서질 것 같으면: &quot;나는 최선을 다했어&quot;라며 비로소 휴식을 허락함. (심리적 면죄부)&lt;br /&gt;&lt;br /&gt;왜 &quot;혹시 실수한 건 없는지&quot; 걱정하게 될까?&lt;br /&gt;일찍 퇴근했을 때 &quot;더 좋은 결과물을 만들 수 없었는지&quot; 생각하게 된다는 점이 핵심이에요. 사용자님에게 '늦게까지 일하는 것'은 불안을 잠재우는 가장 확실한 방법인 셈입니다.&lt;br /&gt;내가 사무실에 남아 있는 그 시간은 단순히 업무 시간이라기보다, **&quot;나는 게으름 피우지 않았고, 할 수 있는 모든 노력을 다했다&quot;는 것을 스스로에게 증명해 보이는 '안전장치'**에 가깝습니다. 그래야만 비로소 '나 진짜 열심히 살았다'는 확신을 얻고 마음 편히 눈을 감을 수 있는 거죠.&lt;br /&gt;&lt;br /&gt;하지만 '결과물'이 따라오지 않을 때의 위험성&lt;br /&gt;지금 느끼시는 &quot;결과물이 좋지 않아 이게 맞나 싶다&quot;는 의문은 아주 건강한 회의감입니다.&lt;br /&gt;뇌의 가성비 하락: 뇌는 피로가 누적되면 '일하는 척'만 하는 상태(Pseudo-work)에 빠지기 쉽습니다. 10시간 앉아 있지만, 실제 고도의 집중력을 발휘하는 건 3시간뿐일 수 있어요.노력의 보상 심리: &quot;내가 이만큼 고생했으니 결과도 당연히 좋아야 해&quot;라는 보상 심리가 커지면, 결과가 기대에 못 미쳤을 때 오는 타격이 남들보다 훨씬 큽니다.&lt;br /&gt;&lt;br /&gt;나를 위한 새로운 '갓생' 정의하기&lt;br /&gt;사용자님은 지금 **'고생하는 나'**를 너무 사랑하고 신뢰하고 있어요. 하지만 그 신뢰가 가끔은 사용자님의 눈을 가릴 때가 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;요약&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 기존에 나의 한계를 시험하고 몰입했던 시간, 열심히 하는 나의 상태에 집중했던 거 같다. 즉, 고생하는 나에 집중했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 하고, 무리하고, 피곤함을 참고 견디는 나의 모습에 내가 열심히 하고 있고, 잘하고 있다고 위안을 삼았다. 그런 나의 모습에 뿌듯함을 느끼고 성취감을 느꼈다. 운동, 취미 생활 등도 나에게 뿌듯함을 가져다 주지만, 일에 대한 몰입보다는 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 행위가 스스로 '내가 게으르지 않았고 최선을 다했다'는 면죄부를 주는 심리적 방어 기제가 되었을 지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'과연 이 방식이 올바른가?'에 대한 의문이 생겼다.&amp;nbsp;'애쓰는 과정'에 너무 집중하느라 '효율적인 성과'를 놓치고 있는 것은 아닌지 싶다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2026년 목표&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 취업 준비를 하면서도 이런 저런 일들이 많아서 온전히 집중할 수 있는 환경은 아니었다. 하지만 이젠 급하다. 물론 그때도 급했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트폴리오 수정하고, 클라우드 역량 더 쌓고, CS 공부하고, 알고리즘 꾸준히 풀어나가며 준비해나갈 생각이다. 현재 목표는 SSAFY 수료를 넘길 생각이 없다. 스타트를 끊어야 한다고 생각하고 있다. 6월 이전에 취업 성공이 목표다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;운동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각이 많은 내 성향에 운동을 필수다. 시간이 없더라도 운동만큼은 꾸준히 해야 한다는 생각을 했다. 운동을 쉬면 우울하고 처지더라. 운동을 하자. 맑은 정신을 갖도록 노력하자. 헬스와 수영을 병행하고 있으며, 이를 유지할 생각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마인드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;배우는 자세 가지기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 맞다는 생각을 버리자. 요즘 들어 나도 모르게 내가 옳다는 생각을 가지고 살아가려는 것 같다. 전에는 남들의 이야기를 듣고 받아 들이는 걸 좋아했는데, 어느 순간부터 이를 힘들어 하고 꺼려한다는 걸 느꼈다. 이게 좋은 자세가 아닌 것을 알기에 고치고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들의 의견을 더 열린 마음으로 생각하자. 최소 1번은 다시 생각해보자. 이를 통해서 나에게 도움되는 것, 내가 몰랐던 것 등 배우려는 자세를 다시 가져보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;성취감의 지표를 밀도로 바꾸기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양, 시간이 아니라 밀도로 바꾸자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 시작하기 전, '이 일은 집중하면 몇 시간 만에 끝낼 수 있는가?'를 먼저 정해보자. 그리고 그 시간 내에 일을 끝냈을 때 더 큰 쾌감을 느끼려고 노력해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;휴식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉴 때는 확실하게 쉬자. 내 스스로 잘 못 쉬기 때문에 딱 정해놓고 쉴 때는 무조건 쉬자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년에도 매달 1일은 힐링 데이로 지정하고 쉬고자 했으나 제대로 지키지 못했다. 쉬는 것도 계획을 세우고 확실하게 쉬는 것을 연습하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Me</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/388</guid>
      <comments>https://seomj74.tistory.com/388#entry388comment</comments>
      <pubDate>Sun, 28 Dec 2025 22:00:16 +0900</pubDate>
    </item>
    <item>
      <title>[AI]OCR API(Naver Clova OCR)</title>
      <link>https://seomj74.tistory.com/387</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSAFY 미니 프로젝트를 진행하며 이미지를 분석해 텍스트를 추출하는 기능이 필요했다. 이를 공부하고 도입한 과정에 대해 정리해보고자 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OCR이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optical Character Recognition (광학 문자 인식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 이미지를 기계(컴퓨터)가 읽을 수 있는 텍스트 포맷으로 변환하는 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라로 이미지를 인식하면 텍스트를 읽어오는 과정을 생각하면 된다. 일상에서 영수증이나 신분증, 메뉴판 등을 카메라로 인식하는 경우가 있다. 이때 OCR을 통해 이미지를 텍스트 문서로 변환하여 텍스트 데이터로 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;글자를 어떻게 인식하지?&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이미지 획득
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스캐너를 통해 문자를 이진 데이터로 변환&lt;/li&gt;
&lt;li&gt;밝은 부분 &amp;rarr; 배경 / 어두운 부분 &amp;rarr; 글자(텍스트)&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;정렬 문제 해결을 위해 기울기 조절&lt;/li&gt;
&lt;li&gt;이미지 얼룩 제거, 가장자리 부드럽게 조절&lt;/li&gt;
&lt;li&gt;텍스트 상자 및 선 정리&lt;/li&gt;
&lt;li&gt;다국어 OCR 기술용 스크립트 인식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;텍스트 인식
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패턴 매칭
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자 이미지를 분리하여 저장된 글리프와 비교&lt;/li&gt;
&lt;li&gt;글리프: 표준 글자 이미지 (ㄱ, A, 1)&lt;/li&gt;
&lt;li&gt;글꼴이 정해져 있는 문서를 스캔할 때 효율적&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;글자의 특징을 사용해 근사치에 가까운 글리프를 찾아냄&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;ex. 'ㅇ', 'O'의 원부분 / 'ㅌ', 'T'의 가로 세로 교차점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;추출된 텍스트 데이터를 기계가 읽을 수 있는 텍스트 문서(txt, docx)로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정은 aws에서 설명하는 OCR 동작 원리이다. 이는 글자 하나를 어떻게 인식하는가에 초점을 두고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그렇다면 문서 전체는?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초점을 바꿔서 문서 전체를 어떻게 처리하는지에 대해 OCR의 전체 작업 흐름을 알아보자. 이 과정에서 AI 기술이 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs9sMq/dJMcafLUJe3/8eGxv534jKcu6VBFFKjlL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs9sMq/dJMcafLUJe3/8eGxv534jKcu6VBFFKjlL1/img.png&quot; data-alt=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=165524&amp;amp;amp;boardType=techBlog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs9sMq/dJMcafLUJe3/8eGxv534jKcu6VBFFKjlL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs9sMq%2FdJMcafLUJe3%2F8eGxv534jKcu6VBFFKjlL1%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;648&quot; height=&quot;261&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://devocean.sk.com/blog/techBoardDetail.do?ID=165524&amp;amp;boardType=techBlog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이미지 전처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이는 글자를 인식하는 과정과 동일&lt;/li&gt;
&lt;li&gt;기울기 조절, 얼룩이나 노이즈 제거 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Text Detection
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지에서 글자가 존재하는 영역 발견&lt;/li&gt;
&lt;li&gt;Object Detection 기술을 확장하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Text Recognition
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 영역의 글자가 무엇인지 판독하고, 이미지 내의 정확한 좌표 정보 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Restructuring (재구성)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 이미지의 좌표와 구조에 따라 재배치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정을 보면 알 수 있지만 OCR의 핵심은 Text Detection와 Text Recognition이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 AI쪽으로 더 공부해보아야 할 거 같으니 프로젝트 진행을 위해 일단 미뤄두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 궁금한 사람들은 더 찾아보시길...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크는 도움이 될 거 같아 참고해 보라는 의미로 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@xpelqpdj0422/11.-OCR-%EA%B8%B0%EC%88%A0%EC%9D%98-%EA%B0%9C%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@xpelqpdj0422/11.-OCR-%EA%B8%B0%EC%88%A0%EC%9D%98-%EA%B0%9C%EC%9A%94&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1765774489022&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;11. OCR 기술의 개요&quot; data-og-description=&quot;1. 들어가며 OCR = Text detection + Text recognition 이번 시간에는 딥러닝 기반의 OCR이 어떻게 이뤄지는지 알아볼 시간입니다. OCR은 크게 문자 영역을 검출하는 Text detection과 검출된 영역의 문자를인식하&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@xpelqpdj0422/11.-OCR-%EA%B8%B0%EC%88%A0%EC%9D%98-%EA%B0%9C%EC%9A%94&quot; data-og-url=&quot;https://velog.io/@xpelqpdj0422/11.-OCR-기술의-개요&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKibTp/hyZPJiI7Ll/aBL01Cn2zxAt2m7L61AI60/img.png?width=469&amp;amp;height=558&amp;amp;face=0_0_469_558,https://scrap.kakaocdn.net/dn/tqeuM/hyZPwKX8wa/kcTXy3NDBprEFB5G03uBX1/img.png?width=469&amp;amp;height=558&amp;amp;face=0_0_469_558,https://scrap.kakaocdn.net/dn/bUQ3k9/hyZPAfx141/nAcukSI5kgrnpe7Avpe7qk/img.png?width=582&amp;amp;height=561&amp;amp;face=0_0_582_561&quot;&gt;&lt;a href=&quot;https://velog.io/@xpelqpdj0422/11.-OCR-%EA%B8%B0%EC%88%A0%EC%9D%98-%EA%B0%9C%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@xpelqpdj0422/11.-OCR-%EA%B8%B0%EC%88%A0%EC%9D%98-%EA%B0%9C%EC%9A%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKibTp/hyZPJiI7Ll/aBL01Cn2zxAt2m7L61AI60/img.png?width=469&amp;amp;height=558&amp;amp;face=0_0_469_558,https://scrap.kakaocdn.net/dn/tqeuM/hyZPwKX8wa/kcTXy3NDBprEFB5G03uBX1/img.png?width=469&amp;amp;height=558&amp;amp;face=0_0_469_558,https://scrap.kakaocdn.net/dn/bUQ3k9/hyZPAfx141/nAcukSI5kgrnpe7Avpe7qk/img.png?width=582&amp;amp;height=561&amp;amp;face=0_0_582_561');&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;11. OCR 기술의 개요&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 들어가며 OCR = Text detection + Text recognition 이번 시간에는 딥러닝 기반의 OCR이 어떻게 이뤄지는지 알아볼 시간입니다. OCR은 크게 문자 영역을 검출하는 Text detection과 검출된 영역의 문자를인식하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OCR API&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-local-id=&quot;3492d7d9-6b35-4099-b388-93fd27b77ccd&quot; data-indent-level=&quot;1&quot;&gt;
&lt;li&gt;무료 오픈소스
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot; data-local-id=&quot;6bfec24d-2d42-4284-bc2e-d3603591c43e&quot; data-indent-level=&quot;2&quot;&gt;
&lt;li&gt;유료여도 무료 제한이 괜찮은 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자주 사용하는 API&lt;/li&gt;
&lt;li&gt;한글 인식이 높다는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-testid=&quot;table-container&quot; data-layout=&quot;custom&quot;&gt;
&lt;div data-vc=&quot;table-node-wrapper&quot; data-table-width=&quot;760&quot; data-table-local-id=&quot;6869cdb8-af2c-4d5b-9a44-09e6d18b54f3&quot; data-autosize=&quot;false&quot; data-layout=&quot;default&quot; data-number-column=&quot;false&quot;&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-layout=&quot;default&quot; data-table-width=&quot;760&quot; data-number-column=&quot;false&quot; data-testid=&quot;renderer-table&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;유무료&lt;/td&gt;
&lt;td&gt;참고 사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;Tesseract&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;무료 (오픈소스)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;한글 인식률이 별로라는 평&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;EasyOCR&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;무료 (오픈소스)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;사용하기 쉽다는 평&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;PaddleOCR&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;무료 (오픈소스)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;OCR4all&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;무료 (오픈소스)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;Kakaobrain PORORO&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;무료 (오픈소스)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;한글 인식률이 나쁘지 않다는 평&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;Google Cloud Vision&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;매월 1000건 무료&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;Azure Document Intelligence&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;매월 500건 무료&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;Naver Clova&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;매월 300건 무료&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;span&gt;한글 인식률이 높다는 평&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 기간이 있기에 하나씩 PoC를 하기엔 무리가 있다고 판단하여, 블로그와 Gemini DeepSearch를 통해 오픈소스 정보를 조사하여 API를 선정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-local-id=&quot;88aa1e25-ee5a-4b21-9526-52752a4beca5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;무료이면서 한글 기능이 괜찮은 Kakaobarin PORORO로 진행&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;진행해보고 무리가 있다면 Naver Clova로 변경&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PORORO를 진행해 보려고 하니 환경 제약에 대한 문제가 컸다. 본인의 경우 Docker를 통해 실행해보고자 했으나 좀처럼 진행이 안되어 하루종일 삽질했고, 더이상 지체할 수 없어 우선 Naver Clova를 사용하기로 했다. 추후 시간이 된다면 재도전해볼 생각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Naver Clova OCR&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;CLOVA OCR은 전송한 문서나 이미지를 인식하여 사용자가 지정한 영역의 텍스트와 데이터를 정확하게 추출하는 네이버 클라우드 플랫폼의 서비스입니다. OCR 분야 가장 권위있는 글로벌 챌린지 ICDAR2019 4개 분야에서 1위, CVPR 및 ICCV 국제학회 논문 선정 등 독보적 기술력이 집약된 문자에 대한 높은 인식률을 자랑하는 CLOVA OCR은 미리 등록한 템플릿과의 유사도를 통해 사용자의 개입 없이 문서 자동 분류가 가능하기 때문에 효과적인 업무 워크플로우 설계에도 활용이 가능합니다. 또한 Papago와 같은 서비스와의 연계를 통해 차별화된 서비스를 제공할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;이미지나 문서에서 인식 결과 추출&lt;/li&gt;
&lt;li&gt;문서 처리 자동화와 액션 연동&lt;/li&gt;
&lt;li&gt;인식 결과 검증 프로세스&lt;/li&gt;
&lt;li&gt;Restful API 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 내용은 링크를 참고하길 바란다. &lt;a href=&quot;https://guide.ncloud-docs.com/docs/clovaocr-overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://guide.ncloud-docs.com/docs/clovaocr-overview&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세팅&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Clova OCR을 사용하기 위해 Clova OCR 사용과 API Gateway 사용에 대해 신청해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API Gateway를 신청하고 시작하면 중간에 막히지 않을 것이다. 뒤에 도메인 생성하는 과정에서 API Gateway가 필요하기에 신청해야 한다. 진행하다 막힐 때 신청해도 문제는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Clova 신청&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/v2/product/aiService/ocr&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.ncloud.com/v2/product/aiService/ocr&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1765989560203&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;NAVER CLOUD PLATFORM&quot; data-og-description=&quot;cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification&quot; data-og-host=&quot;www.ncloud.com&quot; data-og-source-url=&quot;https://www.ncloud.com/v2/product/aiService/ocr&quot; data-og-url=&quot;https://www.ncloud.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mtaGu/hyZP83GMig/wysuyaAa349NYy0ItK99d1/img.jpg?width=526&amp;amp;height=274&amp;amp;face=0_0_526_274,https://scrap.kakaocdn.net/dn/7LWUP/hyZPnnRgLz/tGWwoAXS6O44qVyt3iQNNk/img.png?width=3200&amp;amp;height=1440&amp;amp;face=0_0_3200_1440,https://scrap.kakaocdn.net/dn/Up9oS/hyZP74Mgcm/GPrN8ikegWkV3Dz9dmto4K/img.png?width=826&amp;amp;height=826&amp;amp;face=0_0_826_826&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/v2/product/aiService/ocr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.ncloud.com/v2/product/aiService/ocr&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mtaGu/hyZP83GMig/wysuyaAa349NYy0ItK99d1/img.jpg?width=526&amp;amp;height=274&amp;amp;face=0_0_526_274,https://scrap.kakaocdn.net/dn/7LWUP/hyZPnnRgLz/tGWwoAXS6O44qVyt3iQNNk/img.png?width=3200&amp;amp;height=1440&amp;amp;face=0_0_3200_1440,https://scrap.kakaocdn.net/dn/Up9oS/hyZP74Mgcm/GPrN8ikegWkV3Dz9dmto4K/img.png?width=826&amp;amp;height=826&amp;amp;face=0_0_826_826');&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;NAVER CLOUD PLATFORM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ncloud.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크에는 자세한 가이드가 나와있으며, 가이드 내용을 그대로 가져왔으니 링크를 들어가서 확인해도 되고 아래 내용대로 따라해도 된다. 해당 과정이 자세히 나와있기에 추가 설명은 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://guide.ncloud-docs.com/docs/clovaocr-start#이용-신청&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://guide.ncloud-docs.com/docs/clovaocr-start#이용-신청&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #18181b; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;네이버 클라우드 플랫폼 콘솔에 접속해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Region&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메뉴에서 이용 중인 리전을 클릭하여 선택해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Platform&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메뉴에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;VPC&lt;/b&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Classic&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가운데 클릭하여 선택해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Services &amp;gt; AI Services &amp;gt; CLOVA OCR&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메뉴를 차례대로 클릭해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subscription&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메뉴를 클릭해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[이용 신청]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;버튼을 클릭해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스 이용신청&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;팝업 창이 나타나면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CLOVA OCR 서비스 이용약관을 읽고 동의합니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 클릭해 주십시오.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[상품 이용 신청]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;버튼을 클릭해 주십시오.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;API Gateway 신청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/v2/product/applicationService/apiGateway&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.ncloud.com/v2/product/applicationService/apiGateway&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1765989680111&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;NAVER CLOUD PLATFORM&quot; data-og-description=&quot;cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification&quot; data-og-host=&quot;www.ncloud.com&quot; data-og-source-url=&quot;https://www.ncloud.com/v2/product/applicationService/apiGateway&quot; data-og-url=&quot;https://www.ncloud.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bh6ZJn/hyZOIlBWOL/q0Z9cLKEbOLMGHaqUtwxgK/img.jpg?width=526&amp;amp;height=274&amp;amp;face=0_0_526_274,https://scrap.kakaocdn.net/dn/BTBuJ/hyZOKwWfAD/fd9fsuusohlQRCQAj1yK90/img.png?width=826&amp;amp;height=826&amp;amp;face=0_0_826_826,https://scrap.kakaocdn.net/dn/L3mvJ/hyZOHmHnT1/w3YuAcQF2vLESEIK8outt0/img.png?width=826&amp;amp;height=826&amp;amp;face=0_0_826_826&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/v2/product/applicationService/apiGateway&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.ncloud.com/v2/product/applicationService/apiGateway&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bh6ZJn/hyZOIlBWOL/q0Z9cLKEbOLMGHaqUtwxgK/img.jpg?width=526&amp;amp;height=274&amp;amp;face=0_0_526_274,https://scrap.kakaocdn.net/dn/BTBuJ/hyZOKwWfAD/fd9fsuusohlQRCQAj1yK90/img.png?width=826&amp;amp;height=826&amp;amp;face=0_0_826_826,https://scrap.kakaocdn.net/dn/L3mvJ/hyZOHmHnT1/w3YuAcQF2vLESEIK8outt0/img.png?width=826&amp;amp;height=826&amp;amp;face=0_0_826_826');&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;NAVER CLOUD PLATFORM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ncloud.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCR과 동일하게 콘솔에 접해서 Services &amp;gt; Application Services &amp;gt; API Gateway로 접속할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OCR 생성 및 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 OCR로 접속해 도메인을 생성해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHEbwh/dJMcaiV8Xqn/bRrkWtqiN320E5y3a5rSOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHEbwh/dJMcaiV8Xqn/bRrkWtqiN320E5y3a5rSOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHEbwh/dJMcaiV8Xqn/bRrkWtqiN320E5y3a5rSOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHEbwh%2FdJMcaiV8Xqn%2FbRrkWtqiN320E5y3a5rSOk%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;601&quot; height=&quot;364&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 진행하고 있는 프로젝트의 명인 'WattToDo'라고 지정했다. 한국어를 지원해야 하기에 한국어를 선택해서 생성해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성하고 난 뒤에 오른쪽 끝에 옵션을 보면 API Gateway 연동과 데모 버튼이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;189&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k24ZX/dJMb99LH6zr/YNAZ5jFWJXKEuJOeo82kOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k24ZX/dJMb99LH6zr/YNAZ5jFWJXKEuJOeo82kOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k24ZX/dJMb99LH6zr/YNAZ5jFWJXKEuJOeo82kOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk24ZX%2FdJMb99LH6zr%2FYNAZ5jFWJXKEuJOeo82kOK%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;189&quot; height=&quot;100&quot; data-origin-width=&quot;189&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데모 버튼을 통해 잘 동작하는지 먼저 확인해봤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CVYA2/dJMcagYmPFe/RlFyqImx7EEUCGDDFS9VFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CVYA2/dJMcagYmPFe/RlFyqImx7EEUCGDDFS9VFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CVYA2/dJMcagYmPFe/RlFyqImx7EEUCGDDFS9VFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCVYA2%2FdJMcagYmPFe%2FRlFyqImx7EEUCGDDFS9VFk%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;601&quot; height=&quot;342&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 동작한다...! 이미지는 &lt;a href=&quot;https://youtu.be/Zn7TRPo6Qww&quot;&gt;https://youtu.be/Zn7TRPo6Qww&lt;/a&gt; 에 나온 문자 내용을 가지고 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 API Gateway 연동을 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwNtdP/dJMcaiolfzf/99gZnKezUUyibQpfmqYv80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwNtdP/dJMcaiolfzf/99gZnKezUUyibQpfmqYv80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwNtdP/dJMcaiolfzf/99gZnKezUUyibQpfmqYv80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwNtdP%2FdJMcaiolfzf%2F99gZnKezUUyibQpfmqYv80%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;603&quot; height=&quot;387&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secret Key는 새로 발급받아서 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API Gateway 자동 연동 버튼을 누르면 아까 신청한 API Gateway를 통해 알아서 생성된다. 이는 API Gateway 콘솔에서도 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 끝!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;powershell에서 테스트를 진행해 봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1765990400439&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\Users&amp;gt; $response = Invoke-RestMethod -Uri $ocrUrl -Method Post -Headers $headers -Body $body
PS C:\Users&amp;gt; $response.images[0].fields | Select-Object inferText, inferConfidence | Format-Table
inferText      inferConfidence
---------      ---------------
환경부                  0.9999
전기차                     1.0
충전소                  0.9994
[Web발신]                0.999
서울올림픽공원             1.0
남2문주차장             0.9999
충전                    0.9998
소                         1.0
ME19X403                0.9991
충전기                  0.9997
충전이                  0.9999
완료                    0.9999
되었습니다.                1.0
충전                    0.9997
완료                    0.9999
후                      0.9999
차량                       1.0
미                      0.9998
이동시                     1.0
「환경                  0.9992
친화적                     1.0
자동차의                0.9986
개발                       1.0
및                      0.9999
보급                    0.9602
촉진                    0.9997
에                         1.0
관한                    0.9991
법률                    0.9969
제16조                  0.9999
1항」에                 0.9966
의거하                     1.0
여                         1.0
과태료가                   1.0
부과될                  0.9997
수                         1.0
있으니                     1.0
신속히                     1.0
이동하여                   1.0
주시기                     1.0
바랍니다.                  1.0
==충전정보==            0.9997
충전량 :                0.9933
12.48KWh                0.9953
충전금액                0.9989
:                       0.9975
4334원                  0.9997
충전시간 :              0.9176
16:35                   0.9999&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;결과값이 잘 반환되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/ocr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/what-is/ocr/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/-Xsm0U1k1cc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/-Xsm0U1k1cc&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=165524&amp;amp;boardType=techBlog&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devocean.sk.com/blog/techBoardDetail.do?ID=165524&amp;amp;boardType=techBlog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ddingmin00.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-Clova-AI-OCR-API%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ddingmin00.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-Clova-AI-OCR-API%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90-1&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study/AI</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/387</guid>
      <comments>https://seomj74.tistory.com/387#entry387comment</comments>
      <pubDate>Thu, 18 Dec 2025 01:55:18 +0900</pubDate>
    </item>
    <item>
      <title>[CS]MySQL 아키텍처2_InnoDB 스토리지 엔진</title>
      <link>https://seomj74.tistory.com/386</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글&lt;/p&gt;
&lt;figure id=&quot;og_1762084723651&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;[CS]MySQL 아키텍처1_MySQL 엔진&quot; data-og-description=&quot;서론SSAFY를 하며 CS 스터디를 진행하고 있다. CS 스터디에서 OS 파트를 진행할 때는 블로그 포스팅을 하지 못했지만, 이번 DB 파트에 대해서는 포스팅을 해보고자 한다. 포스팅의 내용은 모두 Real My&quot; data-og-host=&quot;seomj74.tistory.com&quot; data-og-source-url=&quot;https://seomj74.tistory.com/385&quot; data-og-url=&quot;https://seomj74.tistory.com/385&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AJwvZ/hyZMuzxezi/gkBsvreSf4lhvK9JAzwZc1/img.png?width=641&amp;amp;height=634&amp;amp;face=0_0_641_634,https://scrap.kakaocdn.net/dn/bpL3dA/hyZMVZfJrG/PvkTD09i9AZ5lap1HlJiBk/img.png?width=641&amp;amp;height=634&amp;amp;face=0_0_641_634,https://scrap.kakaocdn.net/dn/b6ozXj/hyZMzHDh73/AS9iPruQyDxAWVSQXD1aQ1/img.jpg?width=608&amp;amp;height=608&amp;amp;face=266_170_383_297&quot;&gt;&lt;a href=&quot;https://seomj74.tistory.com/385&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seomj74.tistory.com/385&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AJwvZ/hyZMuzxezi/gkBsvreSf4lhvK9JAzwZc1/img.png?width=641&amp;amp;height=634&amp;amp;face=0_0_641_634,https://scrap.kakaocdn.net/dn/bpL3dA/hyZMVZfJrG/PvkTD09i9AZ5lap1HlJiBk/img.png?width=641&amp;amp;height=634&amp;amp;face=0_0_641_634,https://scrap.kakaocdn.net/dn/b6ozXj/hyZMzHDh73/AS9iPruQyDxAWVSQXD1aQ1/img.jpg?width=608&amp;amp;height=608&amp;amp;face=266_170_383_297');&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;[CS]MySQL 아키텍처1_MySQL 엔진&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서론SSAFY를 하며 CS 스터디를 진행하고 있다. CS 스터디에서 OS 파트를 진행할 때는 블로그 포스팅을 하지 못했지만, 이번 DB 파트에 대해서는 포스팅을 해보고자 한다. 포스팅의 내용은 모두 Real My&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seomj74.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;InnoDB&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brNUl7/dJMcajgdR1X/5cBSV6CAKI2f0MOqRlrkdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brNUl7/dJMcajgdR1X/5cBSV6CAKI2f0MOqRlrkdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brNUl7/dJMcajgdR1X/5cBSV6CAKI2f0MOqRlrkdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrNUl7%2FdJMcajgdR1X%2F5cBSV6CAKI2f0MOqRlrkdK%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;519&quot; height=&quot;453&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Primary Key에 의한 클러스터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링이란?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 레코드가 특정 키의 순서에 따라 디스크에 물리적으로 정렬되어 저장되는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링이 적용되는 키를 클러스터링 인덱스라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨더리 인덱스란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Primary Key를 제외한 테이블의 다른 모든 인덱스를 통칭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보조 인덱스라고도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Primary Key가 클러스터링 인덱스&lt;/li&gt;
&lt;li&gt;세컨더리 인덱스는 Primary Key의 값을 논리적인 주소로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyISAM&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클러스터링 키 지원 X&lt;/li&gt;
&lt;li&gt;Primary Key와 세컨더리 인덱스는 구조적으로 차이가 없다.&lt;/li&gt;
&lt;li&gt;Primary Key를 포함한 모든 인덱스는 물리적인 레코드의 주소 값을 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외래 키 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진 레벨에서 지원 (MyISAM X)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foreign_key_checks 시스템 변수를 OFF로 설정하면 외래 키 관계에 대한 체크 작업을 일시적으로 멈출 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 부모와 자식 테이블 간의 관계가 깨진 상태로 그대로 유지해도 된다는 것은 아니다. 일관성을 맞춰준 후 외래 키 체크 기능을 다시 활성화해야 한다. 또한 비활성화되면 외래 키 관계의 부모 테이블에 대한 작업도 무시하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MVCC(Multi Version Concurrency Control)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적: 잠금을 사용하지 않는 일관된 읽기 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi Version: 하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다는 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB는 Undo log를 이용해 해당 기능을 구현한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Undo log란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 읽기-쓰기 트랜잭션과 관련된 실행 취소(Undo) 로그 레코드의 모음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, INSERT문으로 데이터를 넣은 뒤 이를 UPDATE하는 과정이 있다고 하자. 이때 UPDATE문이 실행되면 커밋 실행 여부와 관계없이 InnoDB의 버퍼 풀은 새로운 값으로 업데이트 된다. 디스크의 데이터 파일에는 체크포인트나 InnoDB의 쓰기 스레드에 의해 새로운 값으로 업데이트 되었을 수도 있고 아닐 수도 있다.(일반적으로는 버퍼 풀과 동일한 상태라고 가정해도 무방하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, SELECT로 데이터를 조회하면 어떤 데이터를 조회할까? 이는 격리 수준에 따라 달라진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격리 수준 4가지 (Gemini)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 93px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 격리 수준 (낮음 &amp;rarr; 높음) &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Dirty Read 방지 &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Non-Repeatable Read 방지 &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Phantom Read 방지 &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;READ UNCOMMITTED&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;READ COMMITTED&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;REPEATABLE READ&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;X (InnoDB O)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;SERIALIZABLE&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;현상 정의
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Dirty Read (오손 읽기)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;다른 트랜잭션이 &lt;b&gt;아직 커밋하지 않은(Uncommitted) 데이터&lt;/b&gt;를 읽는 현상. (가장 위험함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Non-Repeatable Read (반복 불가능한 읽기)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;한 트랜잭션 내에서 &lt;b&gt;같은 행&lt;/b&gt;을 두 번 읽을 때, 그 사이에 다른 트랜잭션이 해당 행을 &lt;b&gt;수정&lt;/b&gt;하고 커밋하여 값이 바뀌는 현상.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Phantom Read (유령 읽기)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;한 트랜잭션 내에서 &lt;b&gt;같은 범위의 행&lt;/b&gt;을 두 번 읽을 때, 그 사이에 다른 트랜잭션이 &lt;b&gt;새로운 행을 삽입&lt;/b&gt;하고 커밋하여 &lt;b&gt;이전에는 없던 행&lt;/b&gt;이 나타나는 현상.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격리 수준이 READ UNCOMMITTED인 경우: InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 격리 수준을 가지고 있는 경우: 아직 커밋되지 않았기 때문에 변경되기 이전의 값을 보관하고 있는 Undo 영역의 데이터를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 하나의 레코드에 대해 2개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 상황에 따라 달라지는 구조를 MVCC라고 표현한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잠금 없는 일관된 읽기(Non-Locking Consistent Read)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았더라도 다른 사용자의 SELECT 작업을 방해하지 않는다. InnoDB에서는 변경되기 전의 데이터를 읽기 위해&amp;nbsp; Undo 로그를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위에서 설명한 MVCC 기술을 통해 잠금을 걸지 않고 읽기 작업을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동 데드락 감지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB는 내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프(Wait for List) 형태로 관리한다. 데드락 감지 스레드가 주기적으로 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 그중 하나를 강제 종료한다. 이때 판단 기준은 Undo 로그의 양인다. 이를 적게 가진 트랜잭션이 롤백의 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동화된 자동 복구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러 메커니즘이 있으며, MySQL 서버가 시작될 때 완료되지 못한 트랜잭션이나 디스크에 일부만 기록된(Partial write) 데이터 페이지 등에 대한 일련의 복구 작업이 자동으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행한다. MySQL 서버 설정 파일에 innodb_force_recovery 시스템 변수를 설정해서 MySQL 서버를 시작해야 한다. 해당 값으로 InnoDB 스토리지 엔진이 데이터 파일이나 로그 파일의 손상 여부 검사 과정을 진행할 수 있다. 해당 값은 1~6까지 설정할 수 있고, 값이 커질수록 그만큼 심각한 상황이어서 데이터 손실 가능성이 커지고 복구 가능성은 적어진다. 참고로 0은 복구 모드가 아닐 때이다. 복구 모드에서는 쿼리를 수행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 서버가 기동되고 InnoDB 테이블이 인식된다면 mysqldump를 이용해 데이터를 가능한 만큼 백업하고 그 데이터로 MySQL 서버의 DB와 테이블을 다시 생성하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;InnoDB 버퍼 풀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다. 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 역할도 여기서 이루어진다.&lt;br /&gt;쓰기 지연은 버퍼 풀이 데이터 변경을 메모리에 보유하여 디스크 쓰기를 미루는 것이며, Redo 로그는 그 미뤄진 변경 내용이 시스템 장애 시에도 복구될 수 있도록 보장하는 역할을 한다. ([CS]MySQL 아키텍처1의 MySQL 스레딩 구조 파트와 연결된다)&lt;br /&gt;변경된&amp;nbsp;데이터를&amp;nbsp;모아서&amp;nbsp;처리하면&amp;nbsp;랜덤한&amp;nbsp;디스크&amp;nbsp;작업의&amp;nbsp;횟수를&amp;nbsp;줄일&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼 풀의 크기 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 버퍼 풀 크기는 운영체제와 각 클라이언트 스레드가 사용할 메모리도 충분히 고려해서 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 버퍼 풀 크기를 적절히 작은 값으로 설정해서 조금씩 상황에 따라 증가시키는 방법이 좋다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영 체제의 전체 메모리 공간이 8GB 미만이라면 50% 할당&lt;/li&gt;
&lt;li&gt;그 이상(8~50GB)이라면 50%에서 시작하여 조금씩 올려가며 최적점 찾기&lt;/li&gt;
&lt;li&gt;50GB 이상이라면 15GB에서 30GB정도를 남겨둔 후 나머지 공간을 InnoDB 버퍼 풀로 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innodb_buffer_pool_size 시스템 변수로 크기를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, 버퍼 풀의 크기를 줄이는 작업은 서비스 영향도가 매우 크기 때문에 이는 하지 않도록 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 버퍼 풀 전체를 관리하는 잠금(세마포어)으로 인해 내부 잠금 경합을 유발해왔다. 이를 줄이기 위해 버퍼 풀을 쪼개서 관리할 수 있도록 개선됐다. 각 버퍼 풀을 버퍼 풀 인스턴스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼 풀의 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀의 공간을 페이지 크기의 조각으로 쪼개어 데이터 페이지를 읽어서 각 조각에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRU(Least Recently Used)리스트, 플러시(Flush) 리스트, 프리(Free) 리스트라는 3개의 자료 구조를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 버퍼 풀에서 실제 사용자 데이터로 채워지지 않은 비어 있는 페이지들의 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRU 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRU + MRU(Most Recently Used)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 읽어온 페이지를 최대한 오랫동안 InnoDB 버퍼 풀의 메모리에 유지해서 디스크 읽기 최소화&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;417&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLSRHo/dJMcaklVdvc/8OqlQvkPGGMkl4JGeHRpUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLSRHo/dJMcaklVdvc/8OqlQvkPGGMkl4JGeHRpUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLSRHo/dJMcaklVdvc/8OqlQvkPGGMkl4JGeHRpUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLSRHo%2FdJMcaklVdvc%2F8OqlQvkPGGMkl4JGeHRpUk%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;383&quot; height=&quot;312&quot; data-origin-width=&quot;417&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 찾는 과정&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;필요한 레코드가 저정된 데이터 페이지가 버퍼 풀에 있는지 검사&lt;/li&gt;
&lt;li&gt;디스크에서 필요한 데이터 페이지를 버퍼 풀에 적재하고, 적재된 페이지에 대한 포인터를 LRU 헤더 부분에 추가&lt;/li&gt;
&lt;li&gt;버퍼 풀의 LRU 헤더 부분에 적재된 데이터 페이지가 실제로 읽히면 MRU 헤더 부분으로 이동&lt;/li&gt;
&lt;li&gt;버퍼 풀에 상주하는 데이터 페이지는 사용자 쿼리가 얼마나 접근했었는지에 따라 나이(Age)가 부여되며, 버퍼 풀에 상주하는 동안 쿼리에서 오랫동안 사용되지 않으면 해당 페이지는 버퍼 풀에서 제거된다. 버퍼 풀의 데이터 페이지가 쿼리에 의해 사용되면 나이가 초기화되어 다시 젊어지고 MRU의 헤더 부분으로 옮겨진다.&lt;/li&gt;
&lt;li&gt;필요한 데이터가 자주 접근됐다면 해당 페이지의 인덱스 키를 어댑티브 해시 인덱스에 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러시 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크로 동기화되지 않는 데이터를 가진 데이터 페이지의 변경 시점 기준의 페이지 목록을 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경이 가해진 데이터 페이지는 플러시 리시트에 관리되고 특정 시점이 되면 디스크로 기록돼야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 변경되면 InnoDB는 변경 내용을 Redo 로그에 기록하고 버퍼 풀의 데이터 페이지에도 변경 내용을 반영한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Redo 로그가 디스크로 기록됐다고 해서 데이터 페이지가 디스크로 기록됐다는 것을 보장하지 않으며, 그 반대의 경우도 발생할 수 있다. InnoDB 스토리지 엔진은 체크포인트를 발생시켜 디스크의 Redo 로그와 데이터 페이지의 상태를 동기화하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼 풀과 Redo 로그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀은 서버의 메모리가 허용하는 만큼 크게 설정하면 할수록 쿼리의 성능이 빨라진다. 이는 데이터 베이스 서버의 성능 향상을 위해 데이터 캐시 기능을 향상시키는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀은 데이터베이스 서버의 성능 향상을 위해 쓰기 버퍼링 기능까지 향상시킬 수 있다. 이를 위해서는 InnoDB 버퍼 풀과 Redo 로그와의 관계를 먼저 이해해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBkpNe/dJMcafZdYNs/oKEEhghL6jrNBMMs0oXkf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBkpNe/dJMcafZdYNs/oKEEhghL6jrNBMMs0oXkf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBkpNe/dJMcafZdYNs/oKEEhghL6jrNBMMs0oXkf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBkpNe%2FdJMcafZdYNs%2FoKEEhghL6jrNBMMs0oXkf0%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;285&quot; height=&quot;213&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 버퍼 풀은 클린 페이지(Clean Page)와 더티 페이지(Dirty Page)를 함께 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더티 페이지는 디스크와 메모리(버퍼 풀)의 데이터 상태가 다르기 때문에 디스크로 기록되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진에서 Redo 로그는 1개 이상의 고정 크기 파일을 연결해서 순환 고리처럼 사용한다. 데이터 변경이 계속 발생하면 Redo 로그 파일에 기록됐던 로그 엔트리는 어느 순간 새로운 로그 엔트리로 덮어 쓰인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진은 전체 Redo 로그 파일에서 재사용 가능한 공간과 당장 재사용 불가능한 공간을 구분해서 관리해야 한다. 이때 재사용 불가능한 공간을 활성 Redo 로그(Active Redo Log)라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진은 주기적으로 체크 포인트 이벤트를 발생시켜 Redo 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화한다. 로그가 다 차면 체크 포인트가 발생한다. 발생한 체크 포인트 중 가장 최근 지점의 LSN이 활성 Redo 공간의 시작점이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LSN(Log Sequence Number): Redo 로그 파일의 공간이 재사용되며 기록될 때 가지는 로그 포지션으로, 계속 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 버퍼 풀의 더티 페이지는 특정 Redo 로그 엔트리와 관계를 가지고, 체크 포인트가 발생하면 체크 포인트 LSN보다 작은 Redo 로그 엔트리와 관련된 더티 페이지는 모두 디스크로 동기화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제1. InnoDB 버퍼 풀은 100GB이며 Redo 로그 파일의 전체 크기는 100MB인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 Redo 로그 엔트리가 4KB라면 25600개 정도의 더티 페이지만 버퍼 풀에 보관할 수 있다. 데이터 페이지가 16KB라고 가정하면 허용 가능한 전체 더티 페이지의 크기는 400MB 수준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 버퍼 풀의 크기는 매우 크지만, 쓰기 버퍼링 X&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제2. InnoDB 버퍼 풀은 100MB이며 Redo 로그 파일의 전체 크기는 100GB인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제1과 동일하게 대략 400GB 정도의 더티 페이지를 가질 수 있다. 하지만 버퍼 풀의 크기가 100MB이기 때문에 최대 허용 가능한 더티 페이지는 100MB 크기가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 1번의 상황은 Redo 로그가 작아 자주 체크 포인트가 발생해 대용량 버퍼 풀의 효과가 없다. 낭비가 된다. 반면에 2번의 상황은 버퍼 풀이 작아 캐싱 자체가 적어 Redo 로그가 큰 게 의미가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 &lt;b&gt;버퍼 풀과 Redo 로그 크기 사이의 균형이 중요&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼 풀 플러시(Buffer Pool Flush)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진은 버퍼 풀에서 아직 디스크로 기록되지 않은 더티 페이지들을 성능상의 악영향 없이 디스크에 동기화하기 위해 2개의 플러시 기능을 백그라운드로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;플러시 리스트(Flush_List)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래 전에 변경된 데이터 페이지 순서대로 디스크에 동기화하는 작업을 수행한다. 언제부터 얼마나 많은 더티 페이지를 한 번에 디스크로 기록하느냐에 따라 사용자의 쿼리 처리가 악영향을 받지 않으면서 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Adaptive flush 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 값: 활성화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활성화되면 InnoDB 스토리지 엔진은 단순히 버퍼 풀의 더티 페이지 비율이나 디스크가 처리할 수 있는 환경 변수 설정 값에 의존하지 않고 새로운 알고리즘을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;LRU 리스트(LRU_List)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 빈도가 낮은 데이터 페이지들을 제거해서 새로운 페이지들을 읽어올 공간을 만들어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRU 리스트의 끝부분부터 시작해서 시스템 변수에 설정된 개수만큼의 페이지들을 스캔한다. 이때 더티 페이지는 디스크에 동기화하게 되며, 클린 페이지는 즉시 프리(Free) 리스트로 페이지를 옮긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼 풀 상태 백업 및 복구&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워밍업(Warming Up): 디스크의 데이터가 버퍼 풀에 적재돼 있는 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀이 잘 워밍업된 상태에서는 그렇지 않은 경우보다 몇십 배의 쿼리 처리 속도를 보이는 것이 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 5.6 버전부터는 버퍼 풀 덤프 및 적재 기능이 도입됐다. 현재 InnoDB 버퍼 풀의 상태를 백업할 수 있다. 서버를 다시 시작하면 시스템 변수를 이용해 백업된 버퍼 풀의 상태를 다시 복구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진이 버퍼 풀의 LRU 리스트에서 적재된 데이터 페이지의 메타 정보만 가져와서 저장하기 때문에 크기가 작고 백업이 매우 빠르게 완료된다. 하지만 백업된 내용을 다시 복구하는 과정은 InnoDB 버퍼 풀의 크기에 따라 시간이 걸릴 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Double Write Buffer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial-page / Torn-page&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB의 스토리지 엔진에서 더티 페이지를 디스크 파일로 플러시할 때 일부만 기록되는 현상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제를 막기 위해 Double-Write 기법을 이용한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRLeBD/dJMcahiq3ES/lMCaYhHsbKvAKM6csyQxOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRLeBD/dJMcahiq3ES/lMCaYhHsbKvAKM6csyQxOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRLeBD/dJMcahiq3ES/lMCaYhHsbKvAKM6csyQxOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRLeBD%2FdJMcahiq3ES%2FlMCaYhHsbKvAKM6csyQxOK%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;394&quot; height=&quot;320&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DoubleWrite 버퍼의 내용은 실제 데이터 파일의 쓰기가 중간에 실패할 때만 원래의 목적으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'C'페이지가 기록되는 도중에 운영체제가 비정상적으로 종료됐다고 가정. InnoDB 스토리지 엔진은 재시작될 때 항상 DoubleWrite 버퍼의 내용과 데이터 파일의 페이지들을 모두 비교해서 다른 내용을 담고 있는 페이지가 있으면 DoubleWrite 버퍼의 내용을 데이터 파일의 페이지로 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Undo 로그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션과 격리 수준을 보장하기 위해 DML로 변경되기 이전 버전의 백업된 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트랜잭션 보장&lt;/b&gt;: 트랜잭션이 롤백되면 트랜잭션 도중 변경된 데이터를 변경 전 데이터로 복구해야 하는데, 이때 Undo 로그에 백업해둔 이전 버전의 데이터를 이용해 복구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;격리 수준 보장&lt;/b&gt;: 특정 커넥션에서 데이터를 변경하는 도중에 다른 커넥션에서 데이터를 조회하면 트랜잭션 격리 수준에 맞게 변경 중인 레코드를 읽지 않고 Undo 로그에 백업해둔 데이터를 읽어서 반환하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Undo 로그 레코드 모니터링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Undo 로그의 데이터는 두 가지 용도로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 용도가 트랜잭션의 롤백 대비용이다. 트랜잭션을 커밋하지 않아도 실제 데이터 파일(데이터/인덱스 버퍼) 내용은 업데이트된다. 그리고 변경되지 전의 값은 Undo 영역에 백업되는 것이다. 이 상태에서 사용자가 커밋하면 현재 상태가 그대로 유지되고, 롤백하면 Undo 영역의 백업된 데이터를 다시 데이터 파일로 복구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 용도는 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공하는 데 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*트랜잭션의 격리 수준: 동시에 여러 트랜잭션이 데이터를 변경하거나 조회할 때 한 트랜잭션의 작업 내용이 다른 트랜잭션에 어떻게 보일지를 결정하는 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Undo 테이블스페이스 관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Undo 테이블스페이스 &amp;gt; 1개 이상 128개 이하의 롤백 세그먼트 &amp;gt; 1개 이상의 Undo Slot&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epFKoS/dJMcaiPjGiG/gBNUPYweY6kSeohuqIx4z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epFKoS/dJMcaiPjGiG/gBNUPYweY6kSeohuqIx4z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epFKoS/dJMcaiPjGiG/gBNUPYweY6kSeohuqIx4z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FepFKoS%2FdJMcaiPjGiG%2FgBNUPYweY6kSeohuqIx4z1%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;358&quot; height=&quot;282&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 롤백 세그먼트는 InnoDB 페이지 크기를 16바이트로 나눈 개수만큼의 Undo Slot을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 트랜잭션이 필요로 하는 Undo Slot의 개수는 트랜잭션이 실행하는 INSERT, UPDATE, DELETE 문장의 특성에 따라 최대 4개까지 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로는 2개 정도의 Undo Slot을 필요로 한다고 가정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;체인지 버퍼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 메모리 공간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS에서 레코드가 INSERT되거나 UPDATE될 때 테이블에 포함된 인덱스를 업데이트하는 과정도 필요하다. InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만, 디스크로부터 읽어와야 하는 경우에는 즉시 수행하지 않고 임시 공간인 체인지 버퍼에 저장해 두고 사용자에게 결과를 반환하는 형태로 성능을 향상시킨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redo 로그 및 로그 버퍼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 비정상적으로 종료됐을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACID 중 D 만족&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*Durability(지속성): 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함을 의미 (&lt;a href=&quot;https://ko.wikipedia.org/wiki/ACID&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.wikipedia.org/wiki/ACID)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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&gt;DBMS에서 데이터 파일든 쓰기보다 읽기 성능을 고려한 자료 구조를 가진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 파일 쓰기는 디스크의 랜덤 액세스가 필요하다.&lt;/li&gt;
&lt;li&gt;큰 비용이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성능 저하를 막기 위해 쓰기 비용이 낮은 자료 구조를 가진 Redo 로그를 사용&lt;/li&gt;
&lt;li&gt;비정상 종료가 발생하면 Redo 로그의 내용을 이용해 복구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 서버는 Redo 로그를 버퍼링할 수 있는 InnoDB 버퍼 풀이나 로그 버퍼와 같은 자료 구조도 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB에서 데이터 변경이 발생했을 때&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터는 InnoDB 버퍼 풀 내의 페이지에서 변경된다. 이때 해당 페이지가 더티 페이지가 된다.&lt;/li&gt;
&lt;li&gt;데이터가 버퍼 풀에서 변경되면서, Redo 로그 레코드가 생성되어 로그 버퍼에 기록된다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 commit 하면 로그 버퍼에서 Redo 로그 파일로 Flush되고 Sync 된다.&lt;/li&gt;
&lt;li&gt;Redo 로그가 디스크에 기록된 후에는, 버퍼 풀에 남아있는 더티 페이지를 디스크의 데이터 파일에 기록한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번의 과정을 백그라운드에서 비동기적으로 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비정상 종료 시 InnoDB 스토리지 엔진의 데이터 파일은 커밋됐지만 &lt;b&gt;데이터 파일에 기록되지 않은 데이터&lt;/b&gt;, &lt;b&gt;롤백됐지만 데이터 파일에 이미 기록된 데이터&lt;/b&gt;를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자의 경우에는 Redo 데이터를 데이터 파일에 다시 복사하기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후자는 변경되기 전 데이터를 가진 Undo 로그의 내용을 가져와 데이터 파일에 복사하면 된다. 이때 변경이 커밋됐는지, 롤백됐는지, 아니면 트랜잭션의 실행 중간 상태였는지를 확인하기 위해서 Redo가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경이 너무 많으면 Redo 로그가 매우 빠르게 증가하고, 새로 추가되는 Redo 로그 내용을 복사하기도 전에 덮어쓰일 수도 있다. 이렇게 되면 백업 툴이 Redo 로그 엔트리를 복사할 수 없어서 백업은 실패한다. 이를 해결하기 위한 것이 Redo 로그 아카이빙 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어댑티브 해시 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 검색 시간을 줄여주기 위해 도입된 기능이다. InnoDB 스토리지 엔진은 자주 읽는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만들고, 필요할 때마다 어댑티브 해시 인덱스를 검색해서 레코드가 저장된 데이터 페이지를 즉시 찾아갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스는 '인덱스 키 값'과 '데이터 페이지 주소'의 쌍으로 관리된다. 여기서 인덱스 키 값은 'B-Tree 인덱스의 고유번호'와 'B-Tree 인덱스의 실제 키 값'의 조합으로 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑티브 해시 인덱스는 버퍼 풀에 올려진 데이터 페이지에 대해서만 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑티브 해시 인덱스는 상황에 따라 성능 향상에 도움이 될 수도, 안 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 읽기가 많은 경우에는 도움이 되지 않을 것이며, 디스크의 데이터가 InnoDB 버퍼 풀 크기와 비슷한 경우에는 디스크 읽기가 많지 않기 때문에 도움이 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑티브 해시 인덱스는 저장 공간인 메모리를 사용하며, 데이터 페이지의 인덱스 키가 해시 인덱스로 만들어져야 하고 불필요하면 삭제되어야 하며 InnoDB 스토리지 엔진은 그 키 값이 해시 인덱스에 있든 없든 검색해봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블을 삭제하거나 변경하려고 하면 InnoDB 엔진은 해당 테이블이 가진 모든 데이터 페이지의 내용을 어댑티브 해시 인덱스에서 제거해야 한다. 테이블이 삭제되거나 스키마가 변경되는 동안 CPU 자원을 많이 사용하게 되고, 그만큼 데이터베이스 서버 처리의 성능이 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑티브 해시 인덱스의 효율은 해시 인덱스 히트율과 어댑티브 해시 인덱스가 사용 중인 메모리 공간, 서버의 CPU 사용량을 종합해서 판단해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;Real MySQL 8.0&lt;/span&gt;&lt;/p&gt;</description>
      <category>Study/CS</category>
      <author>seomj</author>
      <guid isPermaLink="true">https://seomj74.tistory.com/386</guid>
      <comments>https://seomj74.tistory.com/386#entry386comment</comments>
      <pubDate>Tue, 9 Dec 2025 11:30:31 +0900</pubDate>
    </item>
  </channel>
</rss>