EC2 + PostgreSQL + Spring Boot: SSM Parameter Store로 DB 접속정보 관리하기

By | 2026년 10월 25일
Table of Contents

EC2 + PostgreSQL + Spring Boot: SSM Parameter Store로 DB 접속정보 관리하기

DB 비밀번호를 application.yml에 직접 적으면 Git에 실수로 올라가기 쉽고, 서버마다 파일을 따로 관리해야 합니다.
이 글에서는 AWS Systems Manager(SSM) Parameter Store에 DB 접속정보를 저장하고, Spring Boot 앱이 시작할 때 이 값을 가져오도록 구성합니다.

완성되면 이렇게 동작합니다.

브라우저 ──▶ Spring Boot (EC2:8080) ──▶ PostgreSQL (같은 EC2:5432)
                    │
                    └── 시작 시 SSM Parameter Store에서 DB URL / 계정 / 비밀번호 조회
                        (EC2에 연결된 IAM Role로 인증, Access Key 불필요)

화면에는 PostgreSQL에서 조회한 현재 시간이 표시됩니다.


목차

  1. 준비 사항과 사용 버전
  2. EC2 인스턴스 생성
  3. PostgreSQL 설치 및 설정
  4. Parameter Store에 접속정보 저장
  5. IAM Role 생성 및 EC2에 연결
  6. Spring Boot 앱 작성
  7. 빌드, 배포, 실행
  8. 동작 확인
  9. 트러블슈팅
  10. 정리 및 다음 단계

1. 준비 사항과 사용 버전

항목 버전 / 값
OS Amazon Linux 2023
Java Amazon Corretto 21
PostgreSQL 16
Spring Boot 4.0.x
Spring Cloud AWS 4.0.x
리전 ap-northeast-2 (서울)

버전 호환성 주의
Spring Cloud AWS는 Spring Boot 버전에 맞춰 사용해야 합니다.

  • Spring Boot 4.0.x → Spring Cloud AWS 4.0.x
  • Spring Boot 3.5.x → Spring Cloud AWS 3.4.x

최신 호환표는 Spring Cloud AWS GitHub에서 확인하세요.

이 글에서 사용하는 이름은 다음과 같습니다. 원하는 이름으로 바꿔도 됩니다.

항목 값
DB 이름 timedb
DB 사용자 timeapp
Parameter Store 경로 /timeapp/prod/
IAM Role 이름 timeapp-ec2-role

2. EC2 인스턴스 생성

AWS 콘솔 → EC2 → 인스턴스 시작에서 다음과 같이 설정합니다.

  • AMI: Amazon Linux 2023
  • 인스턴스 유형: t3.small 이상 권장 (Java + PostgreSQL을 한 대에서 실행하므로 t3.micro는 메모리가 빠듯할 수 있습니다)
  • 키 페어: 새로 만들거나 기존 키 사용
  • 보안 그룹 인바운드 규칙
유형 포트 소스 용도
SSH 22 내 IP 서버 접속
사용자 지정 TCP 8080 내 IP 앱 확인

PostgreSQL 포트(5432)는 열지 않습니다. 앱과 DB가 같은 서버에 있으므로 외부에서 DB에 접근할 필요가 없습니다.

IAM Role은 5단계에서 연결하므로 지금은 비워 둬도 됩니다.

인스턴스가 실행되면 SSH로 접속합니다.

ssh -i my-key.pem ec2-user@<EC2_PUBLIC_IP>

3. PostgreSQL 설치 및 설정

3-1. 설치

sudo dnf update -y
sudo dnf install -y postgresql16 postgresql16-server

3-2. 초기화 및 시작

sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
sudo systemctl status postgresql

active (running)이 보이면 정상입니다.

3-3. DB와 사용자 생성

sudo -u postgres psql
CREATE USER timeapp WITH PASSWORD 'ChangeMe-Strong-Pass!';
CREATE DATABASE timedb OWNER timeapp;
\q

3-4. 비밀번호 인증 허용

Amazon Linux의 기본 설정은 로컬 TCP 접속에 ident 인증을 사용합니다. JDBC는 TCP(localhost)로 접속하므로 비밀번호 인증(scram-sha-256)으로 바꿔야 합니다.

sudo vi /var/lib/pgsql/data/pg_hba.conf

아래 두 줄의 마지막 값을 scram-sha-256으로 수정합니다.

# IPv4 local connections:
host    all             all             127.0.0.1/32            scram-sha-256
# IPv6 local connections:
host    all             all             ::1/128                 scram-sha-256

설정을 반영합니다.

sudo systemctl restart postgresql

3-5. 접속 테스트

psql -h localhost -U timeapp -d timedb -c "SELECT now();"

비밀번호를 입력하고 현재 시간이 나오면 DB 준비는 끝났습니다.


4. Parameter Store에 접속정보 저장

핵심 규칙: 파라미터 이름 = Spring 프로퍼티 이름

Spring Cloud AWS는 지정한 경로(prefix) 아래의 파라미터를 읽어서, prefix를 뺀 나머지 이름을 Spring 프로퍼티 키로 사용합니다.

Parameter Store 이름 Spring 프로퍼티
/timeapp/prod/spring.datasource.url spring.datasource.url
/timeapp/prod/spring.datasource.username spring.datasource.username
/timeapp/prod/spring.datasource.password spring.datasource.password

방법 A: AWS 콘솔

AWS 콘솔 → Systems Manager → Parameter Store → 파라미터 생성

이름 유형 값
/timeapp/prod/spring.datasource.url String jdbc:postgresql://localhost:5432/timedb
/timeapp/prod/spring.datasource.username String timeapp
/timeapp/prod/spring.datasource.password SecureString ChangeMe-Strong-Pass!
  • 계층(Tier)은 Standard를 선택합니다. Standard 파라미터는 추가 요금이 없습니다.
  • SecureString의 KMS 키는 기본값 alias/aws/ssm을 그대로 사용합니다.

방법 B: AWS CLI (로컬 PC에서 실행)

aws ssm put-parameter --region ap-northeast-2 \
  --name "/timeapp/prod/spring.datasource.url" \
  --type String \
  --value "jdbc:postgresql://localhost:5432/timedb"

aws ssm put-parameter --region ap-northeast-2 \
  --name "/timeapp/prod/spring.datasource.username" \
  --type String \
  --value "timeapp"

aws ssm put-parameter --region ap-northeast-2 \
  --name "/timeapp/prod/spring.datasource.password" \
  --type SecureString \
  --value "ChangeMe-Strong-Pass!"

CLI로 비밀번호를 입력하면 셸 히스토리에 남을 수 있습니다. 실제 운영 비밀번호는 콘솔에서 입력하는 편이 안전합니다.


5. IAM Role 생성 및 EC2에 연결

EC2에서 실행되는 앱이 Parameter Store를 읽으려면 권한이 필요합니다. Access Key를 서버에 두지 않고, EC2에 IAM Role을 붙이는 방식을 사용합니다.

5-1. 정책 생성

IAM → 정책 → 정책 생성 → JSON 탭에 아래 내용을 입력합니다. <ACCOUNT_ID>는 본인 AWS 계정 ID로 바꿉니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadTimeAppParameters",
      "Effect": "Allow",
      "Action": [
        "ssm:GetParametersByPath",
        "ssm:GetParameters",
        "ssm:GetParameter"
      ],
      "Resource": [
        "arn:aws:ssm:ap-northeast-2:<ACCOUNT_ID>:parameter/timeapp/prod",
        "arn:aws:ssm:ap-northeast-2:<ACCOUNT_ID>:parameter/timeapp/prod/*"
      ]
    }
  ]
}

정책 이름은 timeapp-ssm-read로 지정합니다.

KMS 권한은? SecureString을 기본 키(alias/aws/ssm)로 암호화했다면 별도의 kms:Decrypt 권한이 필요 없습니다. 고객 관리형 KMS 키를 썼다면 해당 키에 대한 kms:Decrypt 권한을 추가해야 합니다.

5-2. Role 생성

IAM → 역할 → 역할 생성

  1. 신뢰할 수 있는 엔터티: AWS 서비스 → EC2
  2. 권한 정책: 방금 만든 timeapp-ssm-read 선택
  3. 역할 이름: timeapp-ec2-role

5-3. EC2에 Role 연결

EC2 콘솔 → 인스턴스 선택 → 작업 → 보안 → IAM 역할 수정 → timeapp-ec2-role 선택 → 저장

5-4. EC2에서 권한 확인

EC2에 SSH로 접속한 상태에서 실행합니다. Amazon Linux 2023에는 AWS CLI가 기본으로 설치되어 있습니다.

aws ssm get-parameters-by-path \
  --region ap-northeast-2 \
  --path /timeapp/prod/ \
  --with-decryption \
  --query "Parameters[].Name"

세 개의 파라미터 이름이 출력되면 권한 설정은 완료입니다.


6. Spring Boot 앱 작성

로컬 PC에서 작업합니다.

6-1. 프로젝트 생성

start.spring.io에서 다음과 같이 생성합니다.

  • Project: Gradle – Groovy
  • Language: Java
  • Spring Boot: 4.0.x (최신 패치 버전)
  • Group: com.example / Artifact: timeapp
  • Java: 21
  • Dependencies: Spring Web, JDBC API, PostgreSQL Driver

6-2. 프로젝트 구조

timeapp
├── build.gradle
└── src/main
    ├── java/com/example/timeapp
    │   ├── TimeappApplication.java
    │   └── TimeController.java
    └── resources
        └── application.yml

6-3. build.gradle

start.spring.io가 만든 파일에 Spring Cloud AWS 의존성을 추가합니다.

plugins {
    id 'java'
    id 'org.springframework.boot' version '4.0.6'   // start.spring.io가 생성한 버전 사용
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        // Spring Boot 4.0.x에 맞는 Spring Cloud AWS 4.0.x
        mavenBom 'io.awspring.cloud:spring-cloud-aws-dependencies:4.0.2'
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    runtimeOnly 'org.postgresql:postgresql'

    // SSM Parameter Store 연동
    implementation 'io.awspring.cloud:spring-cloud-aws-starter-parameter-store'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • Spring Boot 4부터 웹 스타터 이름이 spring-boot-starter-webmvc로 바뀌었습니다. start.spring.io가 생성한 이름을 그대로 쓰면 됩니다.
  • 버전 번호는 글 작성 시점 기준입니다. 최신 패치 버전으로 바꿔 사용하세요.

6-4. application.yml

비밀번호가 어디에도 없다는 점이 핵심입니다.

spring:
  application:
    name: timeapp
  config:
    import: "aws-parameterstore:/timeapp/prod/"
  cloud:
    aws:
      region:
        static: ap-northeast-2

server:
  port: 8080
  • spring.config.import: 앱 시작 시 /timeapp/prod/ 아래의 모든 파라미터를 읽어 Spring 프로퍼티로 등록합니다.
  • 리전은 EC2 메타데이터에서 자동으로 찾을 수도 있지만, 명시해 두면 로컬 실행 시에도 헷갈리지 않습니다.
  • 파라미터를 읽지 못하면 앱이 시작되지 않습니다. 설정 누락을 배포 단계에서 바로 알 수 있으므로 의도된 동작입니다. 로컬 개발처럼 Parameter Store 없이 실행해야 할 때는 optional:aws-parameterstore:/timeapp/prod/처럼 optional:을 붙입니다.

6-5. TimeappApplication.java

start.spring.io가 생성한 파일을 그대로 사용합니다.

package com.example.timeapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TimeappApplication {

    public static void main(String[] args) {
        SpringApplication.run(TimeappApplication.class, args);
    }
}

6-6. TimeController.java

DB에서 현재 시간을 조회해 HTML로 보여줍니다.

package com.example.timeapp;

import java.util.Map;

import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TimeController {

    private final JdbcTemplate jdbcTemplate;

    public TimeController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
    public String now() {
        Map<String, Object> row = jdbcTemplate.queryForMap("""
                SELECT
                  to_char(now() AT TIME ZONE 'Asia/Seoul', 'YYYY-MM-DD HH24:MI:SS') AS seoul_time,
                  to_char(now() AT TIME ZONE 'UTC',        'YYYY-MM-DD HH24:MI:SS') AS utc_time,
                  version()                                                         AS db_version
                """);

        return """
                <!doctype html>
                <html lang="ko">
                <head>
                  <meta charset="utf-8">
                  <title>DB 현재 시간</title>
                  <style>
                    body { font-family: sans-serif; max-width: 640px; margin: 60px auto; padding: 0 16px; }
                    .time { font-size: 2rem; font-weight: bold; margin: 8px 0 24px; }
                    .muted { color: #666; font-size: 0.9rem; }
                  </style>
                </head>
                <body>
                  <h1>PostgreSQL 현재 시간</h1>
                  <p>서울 (KST)</p>
                  <div class="time">%s</div>
                  <p>UTC</p>
                  <div class="time">%s</div>
                  <p class="muted">%s</p>
                  <p class="muted">접속정보 출처: AWS SSM Parameter Store</p>
                </body>
                </html>
                """.formatted(row.get("seoul_time"), row.get("utc_time"), row.get("db_version"));
    }
}

EC2의 기본 시간대는 UTC이므로, PostgreSQL의 now()도 기본적으로 UTC 기준으로 표시됩니다. 여기서는 AT TIME ZONE으로 서울 시간과 UTC를 함께 보여줍니다.


7. 빌드, 배포, 실행

7-1. 로컬에서 빌드

./gradlew clean bootJar

build/libs/timeapp-0.0.1-SNAPSHOT.jar 파일이 생성됩니다.

로컬 PC에는 IAM Role이 없으므로, 이 상태로 로컬에서 실행하면 Parameter Store를 읽지 못해 시작에 실패합니다. 로컬 실행용 설정은 10. 정리 및 다음 단계를 참고하세요.

7-2. EC2에 Java 설치

sudo dnf install -y java-21-amazon-corretto-headless
java -version

7-3. JAR 업로드

로컬 PC에서 실행합니다.

scp -i my-key.pem build/libs/timeapp-0.0.1-SNAPSHOT.jar ec2-user@<EC2_PUBLIC_IP>:/home/ec2-user/timeapp.jar

7-4. 먼저 직접 실행해 보기

java -jar ~/timeapp.jar

로그에 Tomcat started on port 8080이 보이면 성공입니다. 확인 후 Ctrl + C로 종료합니다.

7-5. systemd 서비스로 등록

SSH 접속을 끊어도 앱이 계속 실행되고, 서버가 재부팅되면 자동으로 시작되도록 등록합니다.

sudo mkdir -p /opt/timeapp
sudo mv ~/timeapp.jar /opt/timeapp/timeapp.jar
sudo chown -R ec2-user:ec2-user /opt/timeapp
sudo vi /etc/systemd/system/timeapp.service
[Unit]
Description=Time App (Spring Boot)
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service

[Service]
User=ec2-user
WorkingDirectory=/opt/timeapp
ExecStart=/usr/bin/java -jar /opt/timeapp/timeapp.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

서비스 파일에 DB 비밀번호가 전혀 없다는 점을 확인하세요. 접속정보는 모두 Parameter Store에서 가져옵니다.

sudo systemctl daemon-reload
sudo systemctl enable --now timeapp
sudo systemctl status timeapp

로그 확인:

journalctl -u timeapp -f

8. 동작 확인

EC2 안에서 먼저 확인합니다.

curl http://localhost:8080/

그다음 브라우저에서 접속합니다.

http://<EC2_PUBLIC_IP>:8080/

서울 시간과 UTC, PostgreSQL 버전이 표시되면 완성입니다.

비밀번호 변경 테스트

Parameter Store 방식의 장점은 코드나 서버 파일을 수정하지 않고 접속정보를 바꿀 수 있다는 점입니다.

  1. PostgreSQL 비밀번호 변경

    sudo -u postgres psql -c "ALTER USER timeapp WITH PASSWORD 'New-Strong-Pass!';"
  2. Parameter Store 값 변경 (콘솔에서 /timeapp/prod/spring.datasource.password 편집)

  3. 앱 재시작

    sudo systemctl restart timeapp

JAR와 설정 파일은 그대로인데 새 비밀번호로 정상 접속됩니다.

이 구성에서는 앱이 시작할 때 한 번 값을 읽습니다. 값을 바꾼 뒤에는 앱을 재시작해야 반영됩니다.


9. 트러블슈팅

증상 (로그) 원인 해결
Unable to load credentials from any of the providers EC2에 IAM Role이 연결되지 않음 5-3단계에서 Role 연결 확인. 연결 직후라면 1~2분 기다린 뒤 재시작
AccessDeniedException ... ssm:GetParametersByPath 정책의 Resource ARN이 경로와 다름 리전, 계정 ID, 경로(/timeapp/prod, /timeapp/prod/*) 확인
AccessDeniedException ... kms:Decrypt 고객 관리형 KMS 키로 SecureString 암호화 해당 키에 대한 kms:Decrypt 권한 추가
Failed to configure a DataSource: 'url' attribute is not specified 파라미터 이름이 프로퍼티 키와 다름 파라미터 이름이 /timeapp/prod/spring.datasource.url 형태인지 확인 (오타, 대소문자 포함)
FATAL: Ident authentication failed for user "timeapp" pg_hba.conf가 ident 인증 상태 3-4단계대로 scram-sha-256으로 변경 후 PostgreSQL 재시작
FATAL: password authentication failed Parameter Store의 비밀번호와 DB 비밀번호가 다름 두 값을 일치시키고 앱 재시작
브라우저에서 접속 안 됨 (curl localhost는 성공) 보안 그룹에 8080 미허용 인바운드 규칙에 8080 / 내 IP 추가
로컬 PC에서 실행 시 시작 실패 로컬에는 IAM Role이 없음 아래 "로컬 개발 환경" 참고

어떤 값이 실제로 로드됐는지 확인하고 싶다면, EC2에서 5-4단계의 CLI 명령에 --query "Parameters[].[Name,Value]"를 붙여 실행해 보세요. (비밀번호가 화면에 출력되므로 주의하세요.)


10. 정리 및 다음 단계

정리

  • DB 접속정보는 Parameter Store에 저장하고, 비밀번호는 SecureString으로 암호화했습니다.
  • 앱은 spring.config.import: aws-parameterstore:/timeapp/prod/ 한 줄로 접속정보를 가져옵니다.
  • EC2는 IAM Role로 인증하므로 서버 어디에도 AWS Access Key나 DB 비밀번호가 없습니다.
  • 접속정보를 바꿀 때는 Parameter Store 값만 수정하고 앱을 재시작하면 됩니다.

로컬 개발 환경

로컬에서는 Parameter Store 대신 로컬 DB를 쓰도록 프로파일을 나누는 방법이 간단합니다.

application.yml (공통, prod 기본)

spring:
  application:
    name: timeapp
  config:
    import: "optional:aws-parameterstore:/timeapp/prod/"
  cloud:
    aws:
      region:
        static: ap-northeast-2

application-local.yml (로컬 전용)

spring:
  config:
    import: ""        # Parameter Store를 사용하지 않음
  datasource:
    url: jdbc:postgresql://localhost:5432/timedb
    username: timeapp
    password: local-dev-password
./gradlew bootRun --args='--spring.profiles.active=local'

application-local.yml에는 로컬 개발용 비밀번호만 넣고, 운영 비밀번호는 절대 넣지 마세요.
또는 로컬 PC에 AWS CLI 자격 증명(aws configure)이 설정되어 있다면, 같은 Parameter Store의 개발용 경로(예: /timeapp/dev/)를 읽도록 구성할 수도 있습니다.

다음 단계

  • 환경별 경로 분리: /timeapp/dev/, /timeapp/prod/처럼 나누고 프로파일별로 다른 경로를 import
  • Secrets Manager: 비밀번호 자동 교체(rotation)가 필요하다면 aws-secretsmanager: import로 전환
  • RDS로 이전: DB를 별도 관리형 서비스로 분리할 때도 Parameter Store의 URL 값만 바꾸면 됩니다

리소스 정리

실습 후 비용이 발생하지 않도록 정리합니다.

  1. EC2 인스턴스 종료
  2. Parameter Store의 /timeapp/prod/* 파라미터 삭제
  3. IAM Role timeapp-ec2-role과 정책 timeapp-ssm-read 삭제
  4. 보안 그룹 삭제 (필요 시)

답글 남기기