• OS
  • 물리서버
  • 보안
  • 네트워크
  • 클라우드
  • 자격증
카테고리

Terraform 활용한 Oracle Cloud 인스턴스 생성

2026. 4. 2. 05:06·Cloud
반응형

 

OCI는 장기적인 인프라 관리의 어려움과 장기간 미사용에 대한 서버처리로 인해서 
지속적으로 프로비저닝 해야하는 큰 불편이 있습니다.

해당 글은 Terraform를 활용하여 Oracle Cloud 인스턴스 생성하는 과정을 작성해보았습니다.
오라클 클라우드 계정이 없다면 아래 링크를 통하여 가입을 진행합니다.


가입 시 별도의 신용카드가 필요합니다.

 

https://signup.cloud.oracle.com/

 

Oracle Cloud Free Tier Signup

 

signup.cloud.oracle.com

 

무료 계정이 사용할 수 있는 사양은 다음과 같습니다.

AMD 기반 표준 인스턴스 (최대 2개) - 1/8 OCPU, 1GB RAM

ARM 기반 Ampere A1 인스턴스 - 최대 4 OCPU, 24GB RAM


ARM을 통하여 인스턴스 생성을 다뤄 보도록하겠습니다.

ARM은 스마트폰 기반 아키텍처이므로 호환성 문제와 도커 사용이 무리가 있습니다.
하지만, OpenClaw 같은 오픈소스 활용하기에는 제약은 없습니다.

OpenClaw 이용해서 도커나 다양한 걸 시도하고 싶으신 분은
hostinger 원클릭 서비스를 이용해보시거나 로컬, 맥미니 이용하시길 바랍니다.
https://www.hostinger.com/kr?REFERRALCODE=SN6DNLDP5WL6

 

 

 

Terraform 설치

 

https://developer.hashicorp.com/terraform/install

 

Install | Terraform | HashiCorp Developer

Explore Terraform product documentation, tutorials, and examples.

developer.hashicorp.com

 

공식 홈페이지 혹은 아래 코드를 통하여 Terraform를 설치하도록합니다.

winget install HashiCorp.Terraform
terraform -version

 

 

Terraform이 OCI API를 호출하려면 API Signing Key가 필요합니다.

 

# Windows PS에서 PEM 키 쌍 생성
ssh-keygen -t rsa -b 2048 -f C:\Users\(사용자이름)\.ssh\my_key.pem -N ""

OCI 콘솔 → 우측 상단 프로필 아이콘 → 사용자 설정
좌측 메뉴 → API 키 → API 키 추가
퍼블릭 키 붙여넣기 선택 → my_key.pem.pub 내용 붙여넣기

 

my_key.pem의 텍스트를 전부 복사합니다.
(-----BEGIN PUBLIC KEY-----부터 -----END PUBLIC KEY-----까지 전부 포함해야 합니다.)

 

추가 후 표시되는 구성 파일 미리보기에서 다음 값 확인합니다.:
   - tenancy (Tenancy OCID)
   - user (User OCID)
   - fingerprint
   - region

 

최종적으로 필요한 OCID 정보는 다음과 같습니다.
Tenancy OCID: 관리 → 테넌시 세부정보 
User OCID: 프로필 → 사용자 설정 
Compartment OCID: ID → 구획 (root compartment = tenancy OCID) 
Availability Domain: 컴퓨트 → 인스턴스 → 인스턴스 생성 화면에서 확인 

 

 

 

OCI CLI 설치

 

 

테라폼이 오라클 계정에 로그인할 수 있도록 OCI CLI를 설치하고 설정해야 합니다.

PowerShell를 실행합니다.

# 1. 스크립트 실행 권한 허용
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

# 2. 설치 스크립트 다운로드 및 실행
powershell -ExecutionPolicy RemoteSigned -Command "iex ([(Array)(New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.ps1')])"

oci -v

oci setup config

 

설치 후 oci setup config 명령어를 입력합니다.

이때 오라클 클라우드 콘솔에서 내 계정의 User OCID와 Tenancy OCID

그리고 현재 사용 중인 리전 정보를 입력해야 합니다.

설정 과정에서 API 서명 키는 이미 생성했으므로 스킵합니다.

 

 

Terraform 적용

 

이후 부터는 IDE 사용하여 AI 에이전트를 통해 바이브코딩을 구성하여도 좋습니다.
저는 다음과 같이 적용하였습니다.

oci-project/
├── infra/
│   ├── provider.tf      # OCI 프로바이더 인증 설정
│   ├── variables.tf     # compartment_id, availability_domain
│   └── main.tf          # VCN, 서브넷, 보안 목록, 인스턴스
├── apps/
│   ├── provider.tf
│   └── install_apps.tf  # 앱 설치 (openclaw, paperclip 기타 오픈소스)
└── connect.rdp          # RDP 접속 파일

 

provider.tf 

provider.tf는 OCI의 계정 정보와 인증 정보를 담습니다.
실제 운영 시에는 OCID, fingerprint를 terraform.tfvars나 환경변수로 분리하는것을 권고합니다.

# infra/provider.tf
provider "oci" {
  tenancy_ocid     = "ocid1.tenancy.oc1..aaaa..."   # Tenancy OCID
  user_ocid        = "ocid1.user.oc1..aaaa..."       # User OCID
  fingerprint      = "9d:bd:22:ef:f8:b7:45:ff:..."   # API 키 fingerprint
  private_key_path = "C:/Users/(사용자이름)/.ssh/my_key.pem"   # API 서명용 PEM 키
  region           = "ap-seoul-1"                     # 홈 리전
}

 

variables.tf

variables.tf는 입력하는 변수에 대해서 지정합니다.
상시 변화되는 값을 이곳에 분리 하여 저장하도록 관리 하였습니다.

여기서 인스턴스 가용성 도메인(AD)은 인스턴스 생성할 리전을 의미합니다.
국내에서 생성 할 수 있는 무료 인스턴스가 한정 되어있기 때문에 오류가 발생한다면
일본으로 변경하거나 다른 지역으로 변경해야합니다.

 

variable "compartment_id" {
  description = "OCI Compartment OCID (기본값: root tenancy)"
  default     = "ocid1.tenancy.oc1..aaaa..."
}

variable "availability_domain" {
  description = "인스턴스 가용성 도메인"
  default     = "GQyV:AP-SEOUL-1-AD-1"
}

 

가용성 도메인 확인 방법

OCI 콘솔 또는 CLI로 확인:

oci iam availability-domain list --compartment-id <테넌시_ocid>

 

main.rf

main은 VCN, 서브넷, 보안 목록, 인스턴스 항목을 정의 합니다.

 

1. 프로바이더 설정 (OCI)
2. VCN 및 서브넷 생성
3. 인터넷 게이트웨이 및 라우팅 테이블 연결
4. 보안 목록(Security List) 생성 (SSH, RDP 포트 개방)
5. Compute Instance (ARM) 생성 및 user_data (설치 스크립트) 적용

 

사용자 요구사항에 맞게 설정 하도록 합니다. 

 

 

 

# 1. 가상 네트워크 및 기본 보안 설정
resource "oci_core_vcn" "paperclip_vcn" {
  compartment_id = var.compartment_id
  cidr_block     = "10.0.0.0/16"
  display_name   = "paperclip-vcn"
  dns_label      = "paperclip"
}

resource "oci_core_internet_gateway" "ig" {
  compartment_id = var.compartment_id
  vcn_id         = oci_core_vcn.paperclip_vcn.id
  display_name   = "internet-gateway"
}

resource "oci_core_default_route_table" "rt" {
  manage_default_resource_id = oci_core_vcn.paperclip_vcn.default_route_table_id
  route_rules {
    destination       = "0.0.0.0/0"
    network_entity_id = oci_core_internet_gateway.ig.id
  }
}

resource "oci_core_security_list" "paperclip_sl" {
  compartment_id = var.compartment_id
  vcn_id         = oci_core_vcn.paperclip_vcn.id
  display_name   = "paperclip-security-list"

  ingress_security_rules { # SSH
    protocol = "6"
    source   = "0.0.0.0/0"
    tcp_options {
      min = 22
      max = 22
    }
  }

  ingress_security_rules { # Paperclip UI
    protocol = "6"
    source   = "0.0.0.0/0"
    tcp_options {
      min = 3100
      max = 3100
    }
  }

  # RDP 접속을 위한 3389 포트
  ingress_security_rules {
    protocol    = "6"
    source      = "0.0.0.0/0"
    description = "Allow RDP"
    tcp_options {
      min = 3389
      max = 3389
    }
  }

  ingress_security_rules { # OpenClaw Gateway
    protocol    = "6"
    source      = "0.0.0.0/0"
    description = "Allow OpenClaw Gateway"
    tcp_options {
      min = 18789
      max = 18789
    }
  }

  ingress_security_rules { # HTTPS — OpenClaw Control UI
    protocol    = "6"
    source      = "0.0.0.0/0"
    description = "Allow HTTPS (OpenClaw)"
    tcp_options {
      min = 443
      max = 443
    }
  }

  ingress_security_rules { # HTTPS — Paperclip
    protocol    = "6"
    source      = "0.0.0.0/0"
    description = "Allow HTTPS (Paperclip)"
    tcp_options {
      min = 3443
      max = 3443
    }
  }

  egress_security_rules {
    protocol    = "all"
    destination = "0.0.0.0/0"
  }
}

resource "oci_core_subnet" "paperclip_subnet" {
  cidr_block        = "10.0.1.0/24"
  compartment_id    = var.compartment_id
  vcn_id            = oci_core_vcn.paperclip_vcn.id
  display_name      = "paperclip-subnet"
  dns_label         = "subnet1"
  security_list_ids = [oci_core_security_list.paperclip_sl.id]
}

# 인스턴스 생성 (Ubuntu 24.04 ARM)
resource "oci_core_instance" "paperclip_arm" {
  availability_domain = var.availability_domain
  compartment_id      = var.compartment_id
  shape               = "VM.Standard.A1.Flex"

  shape_config {
    ocpus         = 4
    memory_in_gbs = 24
  }

  source_details {
    source_type = "image"
    source_id   = "<OCI 콘솔에서 확인한 Ubuntu 24.04 ARM 이미지 OCID>"
  }

  create_vnic_details {
    subnet_id        = oci_core_subnet.paperclip_subnet.id
    assign_public_ip = true
  }

  metadata = {
    ssh_authorized_keys = file("<SSH 공개키 경로, 예: ~/.ssh/my_key.pem.pub>")

    # Ubuntu 24.04 ARM용 자동 설치 스크립트 (오류 대비 강화)
    # ubuntu-desktop + xrdp 전용 구성
    user_data = base64encode(<<-EOT
      #!/bin/bash
      # ── 로그 설정: 모든 출력을 파일 + 콘솔에 동시 기록 ──
      LOG_FILE="/var/log/setup-script.log"
      exec > >(tee -a "$LOG_FILE") 2>&1
      echo "=== 설치 시작: $(date '+%Y-%m-%d %H:%M:%S') ==="

      # ════════════════════════════════════════════════════════════
      # 유틸리티 함수 정의
      # ════════════════════════════════════════════════════════════

      # apt 재시도 함수 (최대 5회, 실패마다 apt-get update 재실행)
      apt_install_with_retry() {
        local pkgs="$*"
        local max=5
        local n=1
        while [ $n -le $max ]; do
          echo "[apt] 시도 $n/$max: $pkgs"
          if apt-get install -y --fix-missing \
              -o Dpkg::Options::="--force-confold" \
              -o Dpkg::Options::="--force-confdef" \
              -o Acquire::Retries=3 \
              $pkgs; then
            echo "[apt] 성공: $pkgs"
            return 0
          fi
          echo "[apt] 실패($n/$max). 저장소 재갱신 후 60초 대기..."
          # broken 패키지 복구 후 재갱신
          dpkg --configure -a 2>/dev/null || true
          apt-get -f install -y 2>/dev/null || true
          apt-get update --fix-missing -o Acquire::Retries=3 || true
          sleep 60
          n=$((n + 1))
        done
        echo "[apt] 최종 실패: $pkgs → dpkg 직접 설치 방식으로 전환"
        return 1  # 호출부에서 dpkg 폴백 분기 처리
      }

      # dpkg 직접 설치 함수 (공식 Launchpad/GitHub releases에서 .deb 다운로드)
      dpkg_install_from_url() {
        local name="$1"
        local url="$2"
        local deb="/tmp/$${name}.deb"
        echo "[dpkg] $name .deb 직접 다운로드: $url"
        local max=5
        local n=1
        while [ $n -le $max ]; do
          if curl -fsSL --retry 3 --retry-delay 10 -o "$deb" "$url"; then
            echo "[dpkg] 다운로드 완료 → dpkg -i 설치 시도"
            dpkg -i "$deb" && echo "[dpkg] 설치 성공: $name" && return 0
            echo "[dpkg] dpkg 실패, 의존성 해결 시도..."
            apt-get -f install -y || true
            dpkg -i "$deb" && echo "[dpkg] 재시도 성공: $name" && return 0
          fi
          echo "[dpkg] 다운로드 실패($n/$max). 30초 후 재시도..."
          sleep 30
          n=$((n + 1))
        done
        echo "[dpkg] 최종 실패: $name"
        return 1
      }

      # systemctl 안전 실행 (서비스 존재 여부 먼저 확인)
      safe_systemctl() {
        local action="$1"
        local svc="$2"
        if systemctl list-unit-files --type=service 2>/dev/null | grep -q "^$${svc}"; then
          systemctl "$action" "$svc" \
            && echo "[systemctl] $svc $action 완료" \
            || echo "[systemctl] $svc $action 실패 (무시)"
        else
          echo "[systemctl] '$svc' 서비스 없음 → 건너뜀"
        fi
      }

      # ════════════════════════════════════════════════════════════
      # 1. 환경 변수 (팝업 방지)
      # ════════════════════════════════════════════════════════════
      export DEBIAN_FRONTEND=noninteractive
      export UCF_FORCE_CONFOLD=1
      export NEEDRESTART_MODE=a

      timedatectl set-timezone Asia/Seoul || echo "[tz] 시간대 설정 실패 (무시)"

      # ════════════════════════════════════════════════════════════
      # 2. 스왑 메모리 (4GB)
      # ════════════════════════════════════════════════════════════
      echo "=== 스왑 설정 ==="
      if [ ! -f /swapfile ]; then
        fallocate -l 4G /swapfile \
          || dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
        chmod 600 /swapfile
        mkswap /swapfile
        swapon /swapfile
        echo '/swapfile none swap sw 0 0' >> /etc/fstab
        echo "[swap] 완료"
      else
        echo "[swap] 스왑파일 이미 존재 → 건너뜀"
      fi
      sysctl vm.swappiness=10 || true
      echo 'vm.swappiness=10' >> /etc/sysctl.conf

      # ════════════════════════════════════════════════════════════
      # 3. iptables 방화벽 규칙 설정 (OCI 기본 정책: INPUT DROP 대비)
      #    - OCI Ubuntu 이미지는 기본 iptables 정책이 DROP이므로
      #      SSH(22), RDP(3389) 포트를 명시적으로 허용해야 함
      # ════════════════════════════════════════════════════════════
      echo "=== iptables 방화벽 규칙 설정 ==="

      # SSH(22) 허용 - 최우선 삽입 (잠기지 않도록 보장)
      iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT \
        && echo "[iptables] SSH(22) 허용 완료" \
        || echo "[iptables] SSH(22) 규칙 추가 실패"

      # RDP(3389) 허용
      iptables -I INPUT 2 -p tcp --dport 3389 -j ACCEPT \
        && echo "[iptables] RDP(3389) 허용 완료" \
        || echo "[iptables] RDP(3389) 규칙 추가 실패"

      # ESTABLISHED/RELATED 허용 (응답 패킷 허용)
      iptables -I INPUT 3 -m state --state ESTABLISHED,RELATED -j ACCEPT \
        || true

      # loopback 허용
      iptables -I INPUT 4 -i lo -j ACCEPT \
        || true

      # 규칙 영구 저장 (재부팅 후에도 유지)
      mkdir -p /etc/iptables
      iptables-save > /etc/iptables/rules.v4 \
        && echo "[iptables] 규칙 영구 저장 완료 (/etc/iptables/rules.v4)" \
        || echo "[iptables] 규칙 저장 실패"

      # 재부팅 시 자동 복원을 위한 rc.local 등록
      if [ ! -f /etc/rc.local ]; then
        echo '#!/bin/bash' > /etc/rc.local
        chmod +x /etc/rc.local
      fi
      grep -q 'iptables-restore' /etc/rc.local \
        || echo 'iptables-restore < /etc/iptables/rules.v4' >> /etc/rc.local

      echo "[iptables] 현재 INPUT 규칙:"
      iptables -L INPUT -n --line-numbers 2>/dev/null | head -20 || true

      # ════════════════════════════════════════════════════════════
      # 4. apt-get update (최대 5회 재시도)
      # ════════════════════════════════════════════════════════════
      echo "=== apt-get update ==="
      for i in 1 2 3 4 5; do
        if apt-get update --fix-missing -o Acquire::Retries=3; then
          echo "[update] 성공"
          break
        fi
        echo "[update] 실패($i/5). 30초 후 재시도..."
        sleep 30
      done

      # 선행 필수 도구 설치
      apt_install_with_retry ca-certificates curl gnupg lsb-release apt-transport-https \
        || true

      # 기존 패키지 업그레이드
      apt-get upgrade -y \
        -o Dpkg::Options::="--force-confold" \
        -o Dpkg::Options::="--force-confdef" \
        || echo "[upgrade] 경고 발생 (무시하고 계속)"

      # ════════════════════════════════════════════════════════════
      # 4. ubuntu-desktop 설치
      #    실패 시: universe 저장소 추가 후 재시도 → 최종 실패 시 기록
      # ════════════════════════════════════════════════════════════
      echo "=== ubuntu-desktop 설치 (10~20분 소요) ==="

      DESKTOP_OK=false

      # 1차: 기본 저장소로 시도
      if apt_install_with_retry ubuntu-desktop; then
        DESKTOP_OK=true
        echo "[desktop] apt(기본 저장소) 설치 성공"
      else
        echo "[desktop] 기본 저장소 실패 → universe/PPA 저장소 추가 후 재시도"

        # 2차: universe 저장소 활성화 후 재시도
        add-apt-repository -y universe || true
        add-apt-repository -y multiverse || true
        apt-get update --fix-missing -o Acquire::Retries=3 || true

        if apt_install_with_retry ubuntu-desktop; then
          DESKTOP_OK=true
          echo "[desktop] apt(universe 저장소) 설치 성공"
        else
          echo "[desktop] 저장소 방식 모두 실패 → 최종 실패로 기록"
          DESKTOP_OK=false
        fi
      fi

      # ════════════════════════════════════════════════════════════
      # 5. xrdp 설치
      #    실패 시 1: Launchpad PPA(neutrinolabs) 추가 후 재시도
      #    실패 시 2: GitHub releases에서 .deb 직접 다운로드 → dpkg 설치
      # ════════════════════════════════════════════════════════════
      echo "=== xrdp 설치 ==="

      XRDP_OK=false

      # 1차: 기본 apt 시도
      if apt_install_with_retry xrdp; then
        XRDP_OK=true
        echo "[xrdp] apt(기본) 설치 성공"
      else
        echo "[xrdp] 기본 apt 실패 → neutrinolabs PPA 추가 후 재시도"

        # 2차: 공식 PPA 추가
        add-apt-repository -y ppa:neutrinolabs/xrdp || true
        apt-get update --fix-missing -o Acquire::Retries=3 || true

        if apt_install_with_retry xrdp; then
          XRDP_OK=true
          echo "[xrdp] PPA 설치 성공"
        else
          echo "[xrdp] PPA도 실패 → GitHub releases에서 .deb 직접 설치"

          # 3차: GitHub releases에서 최신 .deb 직접 다운로드
          # ARM64(aarch64) 대상 .deb 파일을 neutrinolabs/xrdp 공식 릴리스에서 획득
          XRDP_VER="0.10.1"
          XRDP_DEB_URL="https://github.com/neutrinolabs/xrdp/releases/download/v$${XRDP_VER}/xrdp_$${XRDP_VER}-1_arm64.deb"

          if dpkg_install_from_url "xrdp" "$XRDP_DEB_URL"; then
            XRDP_OK=true
            echo "[xrdp] .deb 직접 설치 성공"
          else
            echo "[xrdp] 모든 설치 방법 실패 → 수동 확인 필요"
            XRDP_OK=false
          fi
        fi
      fi

      # xrdp 서비스 활성화 (설치 성공 시)
      if [ "$XRDP_OK" = true ] && command -v xrdp > /dev/null 2>&1; then
        safe_systemctl enable xrdp
        safe_systemctl start xrdp
        # ssl-cert 그룹에 xrdp 추가 (TLS 인증서 접근 권한)
        usermod -aG ssl-cert xrdp 2>/dev/null \
          || adduser xrdp ssl-cert 2>/dev/null \
          || echo "[xrdp] ssl-cert 그룹 추가 실패 (무시)"
        echo "[xrdp] 서비스 활성화 완료"
      fi

      # ════════════════════════════════════════════════════════════
      # 6. RDP 접속용 비밀번호 설정
      # ════════════════════════════════════════════════════════════
      echo "ubuntu:<RDP 접속용 비밀번호>" | chpasswd \
        && echo "[passwd] ubuntu 비밀번호 설정 완료" \
        || echo "[passwd] 비밀번호 설정 실패"

      # ════════════════════════════════════════════════════════════
      # 7. Docker 설치
      #    1차: get.docker.com 공식 스크립트
      #    2차: apt docker.io (Ubuntu 공식 패키지)
      #    3차: GitHub releases에서 docker-ce .deb 직접 설치
      # ════════════════════════════════════════════════════════════
      echo "=== Docker 설치 ==="

      DOCKER_OK=false

      if command -v docker > /dev/null 2>&1; then
        DOCKER_OK=true
        echo "[docker] 이미 설치됨 → 건너뜀"
      else
        # 1차: 공식 install 스크립트
        echo "[docker] get.docker.com 스크립트 시도..."
        if curl -fsSL --retry 5 --retry-delay 10 https://get.docker.com -o /tmp/get-docker.sh \
            && sh /tmp/get-docker.sh; then
          DOCKER_OK=true
          echo "[docker] 공식 스크립트 설치 성공"
        else
          echo "[docker] 공식 스크립트 실패 → apt docker.io 시도"

          # 2차: Ubuntu 공식 패키지
          if apt_install_with_retry docker.io docker-compose-plugin; then
            DOCKER_OK=true
            echo "[docker] apt(docker.io) 설치 성공"
          else
            echo "[docker] apt 실패 → Docker CE 공식 저장소 추가 후 재시도"

            # 3차: Docker 공식 apt 저장소 직접 구성
            install -m 0755 -d /etc/apt/keyrings
            curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
              | gpg --dearmor -o /etc/apt/keyrings/docker.gpg || true
            chmod a+r /etc/apt/keyrings/docker.gpg

            DISTRO_CODENAME=$(lsb_release -cs 2>/dev/null || echo "noble")
            echo \
              "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.gpg] \
              https://download.docker.com/linux/ubuntu $DISTRO_CODENAME stable" \
              > /etc/apt/sources.list.d/docker.list

            apt-get update --fix-missing -o Acquire::Retries=3 || true

            if apt_install_with_retry docker-ce docker-ce-cli containerd.io docker-compose-plugin; then
              DOCKER_OK=true
              echo "[docker] Docker CE 공식 저장소 설치 성공"
            else
              echo "[docker] 모든 방법 실패 → 수동 확인 필요"
            fi
          fi
        fi
      fi

      if [ "$DOCKER_OK" = true ] && command -v docker > /dev/null 2>&1; then
        usermod -aG docker ubuntu || true
        safe_systemctl enable docker
        safe_systemctl start docker
        echo "[docker] 완료: $(docker --version)"
      fi

      # ════════════════════════════════════════════════════════════
      # 8. 설치 완료 상태 기록
      # ════════════════════════════════════════════════════════════
      STATUS_FILE="/home/ubuntu/setup_done.txt"
      {
        echo "=== 설치 완료: $(date '+%Y-%m-%d %H:%M:%S') ==="
        echo ""
        echo "[ubuntu-desktop] $(dpkg -l ubuntu-desktop 2>/dev/null | grep '^ii' | awk '{print "installed: "$3}' || echo 'not installed')"
        echo "[xrdp]           $(systemctl is-active xrdp 2>/dev/null || echo 'not found') / $(dpkg -l xrdp 2>/dev/null | grep '^ii' | awk '{print $3}' || echo 'not installed')"
        echo "[docker]         $(docker --version 2>/dev/null || echo 'not installed')"
        echo ""
        echo "전체 로그: $LOG_FILE"
        echo ""
        echo "접속 확인 명령어 (SSH 접속 후):"
        echo "  systemctl status xrdp"
        echo "  tail -f /var/log/setup-script.log"
        echo "  tail -f /var/log/cloud-init-output.log"
      } > "$STATUS_FILE"
      chown ubuntu:ubuntu "$STATUS_FILE"

      echo "=== 스크립트 완료: $(date '+%Y-%m-%d %H:%M:%S') ==="
    EOT
    )
  }
}

# ════════════════════════════════════════════════════════════
# Outputs
# ════════════════════════════════════════════════════════════
output "instance_public_ip" {
  description = "인스턴스 공인 IP (apps/install_apps.tf의 server_ip에 입력)"
  value       = oci_core_instance.paperclip_arm.public_ip
}

output "instance_id" {
  description = "인스턴스 OCID"
  value       = oci_core_instance.paperclip_arm.id
}

 

 

제가 구성했던 포트는 다음과 같습니다.

 

22 - SSH 접속 
443 - OpenClaw HTTPS (nginx 프록시)
3100 - Paperclip HTTP (직접 또는 내부) 
3389 - RDP 원격 데스크톱
3443 - Paperclip HTTPS (nginx 프록시)
18789 -OpenClaw Gateway (원본 HTTP)

 

인스턴스 이미지의 OCID 찾는 방법은 다음과 같습니다.

 

OCI 콘솔 → 컴퓨트 → 이미지 → 플랫폼 이미지 필터 → Ubuntu 24.04 aarch64 선택 → OCID 복사

또는

oci compute image list --compartment-id <compartment_ocid> `
  --operating-system "Canonical Ubuntu" `
  --shape "VM.Standard.A1.Flex" `
  --sort-by TIMECREATED --sort-order DESC `
  --query "data[0].id" --raw-output

 

cloud-init 서버 초기 설정

핵심 설계 원칙은 모든 설치 단계에 3중 폴백을 적용했습니다.
OCI ARM 인스턴스는 패키지 미러 불안정이 잦아서, 한 가지 방법만 쓰면 실패 확률이 높기 때문입니다.

 

로그 설정: `/var/log/setup-script.log`에 전체 기록
유틸리티 함수: `apt_install_with_retry` (5회 재시도), `dpkg_install_from_url` (폴백), `safe_systemctl` 
환경 변수: `DEBIAN_FRONTEND=noninteractive` (팝업 방지) 
스왑: 4GB 스왑파일 생성 (메모리 부족 대비) 
iptables: SSH(22), RDP(3389) 포트 열기 + 영구 저장 
apt 업데이트: 5회 재시도, 의존성 자동 복구 
ubuntu-desktop: GUI 데스크톱 설치 (RDP 접속용) 
xrdp: RDP 서버 (1차 apt → 2차 PPA → 3차 .deb 직접 설치) 
Docker: 1차 get.docker.com → 2차 apt → 3차 공식 저장소 
비밀번호: ubuntu 계정 비밀번호 설정 (RDP 로그인용) 


로그 확인:

# SSH 접속 후
tail -f /var/log/setup-script.log       # 커스텀 스크립트 로그
tail -f /var/log/cloud-init-output.log  # cloud-init 전체 로그
cat /home/ubuntu/setup_done.txt         # 설치 완료 요약

 

 

 

Terraform 실행

 

 

cd C:\oci-project\infra

# 프로바이더 다운로드
terraform init

# 변경 사항 미리 확인
terraform plan

# 인프라 생성 (VCN + 서브넷 + 인스턴스)
terraform apply

 

apply 완료까지 약 3-5분 소요되며.

cloud-init은 백그라운드에서 10-20분 추가 실행됩니다.

 

# 생성된 IP 확인

terraform output instance_public_ip

# SSH 접속 확인

ssh -i C:\Users\jh\.ssh\my_key.pem ubuntu@<IP>

 

기타 사항

 

OCI Always Free ARM 인스턴스가 안 만들어질 때

Out of capacity 에러가 뜬다면 해당 리전에 ARM 자원이 없다는 것입니다.

- 다른 Availability Domain 시도 (서울은 1개뿐이라 해당 없음)
- 시간대를 바꿔서 재시도 (새벽 시간에 자원 반납이 많음)
- shape_config에서 OCPU/메모리를 줄여서 시도 (1 OCPU / 6GB로 시작)
- 자동 재시도 스크립트 활용

 

iptables

OCI Ubuntu 이미지는 기본 iptables 정책이 INPUT DROP 입니다.
Security List에서 포트를 열어도 OS 레벨에서 차단되므로, 반드시 iptables도 설정해야 합니다.

# REJECT 규칙보다 앞에 ACCEPT 삽입
sudo iptables -I INPUT 1 -p tcp --dport 443 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables.rules

 

 

Terraform 상태 파일

terraform.tfstate는 로컬에 저장되므로, 이 파일이 없으면 Terraform은 기존 리소스를 인식하지 못합니다.

# .gitignore에 반드시 추가
*.tfstate
*.tfstate.backup
.terraform/

 

 

다음글은 완전 무료로 사용이 가능한
OpenClaw +  Github Copilot (GPT5-mini) 모델 설치와 Paperclip 연동에 대해서 작성해보겠습니다.

반응형

'Cloud' 카테고리의 다른 글

codespace 원격 확장 호스트가 지난 5분 동안 3번 예기치 않게 종료되었습니다.  (0) 2026.04.09
무료 추천 클라우드 활용, 총 700$ 크레딧 4개월 혜택 받는 법  (0) 2021.11.15
오라클 클라우드 사용법  (0) 2021.11.05
Oracle Cloud 가상 머신 생성 및 접속  (0) 2021.11.02
'Cloud' 카테고리의 다른 글
  • codespace 원격 확장 호스트가 지난 5분 동안 3번 예기치 않게 종료되었습니다.
  • 무료 추천 클라우드 활용, 총 700$ 크레딧 4개월 혜택 받는 법
  • 오라클 클라우드 사용법
  • Oracle Cloud 가상 머신 생성 및 접속
wogho
wogho
    반응형
  • wogho
    눙이의 인프라 메모장
    wogho
  • 전체
    오늘
    어제
    • 분류 전체보기
      • OS
        • Linux
        • Windows Server
      • Server
        • Xenserver
        • Equipment
      • Network
        • Cisco
      • Cloud
        • GCP
        • AZURE
        • AWS
      • Security
        • Basic
        • CTF
        • Solution
      • AI
        • Agent
        • LLM
        • ROS2
      • Language
      • Certificate
  • 블로그 메뉴

    • OS
    • 물리서버
    • 보안
    • 네트워크
    • 클라우드
    • 자격증
  • 링크

  • 공지사항

    • Tistory 추천 스킨 및 폰트 (hELLO & d2co⋯
  • 인기 글

  • 태그

    윈도우
    리눅스
    RAID
    terraform
    debian
    github
    Ai
    windows
    SMB
    openclaw
    paperclip
    MEGARAID
    lsi
    네트워크
    Windows Server
    CentOS
    Linux
    copilot
    윈도우서버
    megacli
    PowerShell
    mdadm
    서버
    ubuntu
    데비안
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
wogho
Terraform 활용한 Oracle Cloud 인스턴스 생성
상단으로

티스토리툴바