- 비회원은 운영자가 선정한 블로그들의 요약본을 볼 수 있다.
- 회원은 블로그를 검색하여 구독할 수 있고, 없다면 URL로 직접 추가가 가능하다.
- 회원은 블로그에 올라온 새 글의 요약본을 매일 아침 이메일로 받아볼 수 있다.
- 회원은 게시글을 나중에 읽을 수 있도록 저장할 수 있다.
- 서비스 주소 : https://blogzip.co.kr
- Kotlin, Java, Gradle
- Spring Boot, Spring Data JPA, Spring Batch, OpenFeign
- MySQL
- RSS 파싱/HTML 가공(
crawler-client): Rome, Jsoup - OpenAI API
- Spring Mail, Thymeleaf
- React, TypeScript, Material-ui
- Node.js, Express
- Playwright(Chromium)
- OCI Compute, OCIR, OCI Vault, OCI Email Delivery
- Google OAuth2, JWT
- 이미지 빌드/푸시 + 서비스 배포는
이미지 푸시 + 서비스 배포워크플로우에서 수행합니다. master푸시 시api,nginx,batch,crawler,scheduler이미지를 빌드/푸시하고배포재사용 워크플로우를 호출해 VM에 배포합니다.- 수동 실행 시에는 서비스별 이미지 빌드 여부를 선택할 수 있으며, 선택하지 않은 서비스는
latest태그로 배포합니다. - 배치 수동 실행은
배치 잡 실행워크플로우에서 수행하며, 이미지 빌드/푸시 없이 VM에서batch:latest를 실행합니다. - 정기 배치는 GitHub Actions
cron이 아니라 VM 내scheduler컨테이너의 cron으로 실행됩니다. - 정기 실행 시각(KST):
fetch-new-articles매일 00:00,email-send매일 09:00 - OCIR 이미지는 워크플로우에서
latest + 최근 3개 SHA 태그만 유지하도록 정리합니다.
- 운영 VM에 Docker Engine + Docker Compose plugin을 설치합니다.
- 운영 VM에 OCI CLI를 설치합니다.
- 운영 VM이 OCI Vault를 읽을 수 있도록 Instance Principal IAM 정책을 설정합니다.
- 네트워크 보안 규칙에서 80/443 인바운드를 열고, SSH(22)는 운영자 IP로 제한합니다.
nginx컨테이너가 TLS 종료를 수행하므로 VM에 Certbot 인증서가 있어야 합니다.- 아래 파일이 VM에 존재해야 합니다.
/etc/letsencrypt/live/blogzip.co.kr/fullchain.pem/etc/letsencrypt/live/blogzip.co.kr/privkey.pem/etc/letsencrypt/options-ssl-nginx.conf/etc/letsencrypt/ssl-dhparams.pem
- 앱이 직접 Vault를 import 하지 않고, 배포 스크립트(
deploy/scripts/fetch-vault-env.sh)가 Vault Secret을 읽어deploy/compose/.env.runtime을 생성한 뒤 컨테이너에 주입합니다. - VM에 별도
.env.api파일을 두지 않아도 됩니다. OCI_REGION,OCI_VAULT_ID는 GitHub Actions Variables에서 SSH 실행 환경변수로 전달합니다.MYSQL_HOST는 Vault Secret으로 관리하고,MYSQL_DATABASE는blogzip으로 고정됩니다.local프로파일은 Vault를 사용하지 않고, 필요한 값을 로컬 환경변수로 직접 주입해서 실행합니다.- 기본 Vault 인증:
instance_principal(필요 시 GitHub Variables의OCI_CLI_AUTH로 변경 가능) - 필수 환경변수:
OCI_REGION,OCI_VAULT_ID(MYSQL_PORT는 기본값3306) - Vault Secret 이름은 아래 환경변수 키와 동일하게 생성해야 합니다.
JWT_SECRET_KEYGOOGLE_CLIENT_SECRETMYSQL_HOSTMYSQL_USERNAMEMYSQL_PASSWORDOCI_EMAIL_SMTP_USERNAMEOCI_EMAIL_SMTP_PASSWORDOPEN_AI_API_KEYSLACK_WEBHOOK_URL
- Repository Variables
REGISTRY_HOST: 예)ap-chuncheon-1.ocir.ioIMAGE_PREFIX: 예)ap-chuncheon-1.ocir.io/<tenancy-namespace>/blogzipOCI_REGION: 예)ap-chuncheon-1OCI_VAULT_ID- Repository Secrets
REGISTRY_USERNAMEREGISTRY_PASSWORDDEPLOY_HOSTDEPLOY_USERDEPLOY_SSH_KEY
flowchart LR
Client["Client"]
DNS["DNS<br/>(Route53 / Gabia)"]
GH["GitHub Actions"]
OCIR["OCIR"]
Vault["OCI Vault"]
subgraph OCI["OCI Cloud (ap-chuncheon-1)"]
direction LR
IGW["Internet Gateway"]
subgraph VCN["VCN"]
direction LR
subgraph PublicSubnet["Public Subnet"]
direction TB
subgraph VM["VM.Standard.A1.Flex"]
direction TB
subgraph DockerHost["Docker Compose (Container Runtime)"]
direction LR
Nginx["Nginx 컨테이너<br/>(정적 파일 서빙 + /api 프록시)"]
API["API 컨테이너"]
Crawler["Crawler 컨테이너"]
Scheduler["Scheduler 컨테이너"]
Batch["Batch 컨테이너"]
end
end
end
subgraph PrivateSubnet["Private DB Subnet"]
MySQL["MySQL"]
end
end
end
ExternalIntegrations["외부 연동 서비스<br/>(OpenAI API / OCI Email Delivery / Slack)"]
Client -->|"80/443"| DNS --> IGW --> Nginx
Nginx -->|"/api"| API
Scheduler -->|"배치 잡 트리거"| Batch
GH -->|"배치 잡 수동 실행(SSH)"| Batch
API -->|"내부 HTTP 8090"| Crawler
Batch -->|"내부 HTTP 8090"| Crawler
API -->|"3306"| MySQL
Batch -->|"3306"| MySQL
API --> ExternalIntegrations
Batch --> ExternalIntegrations
GH -->|"이미지 빌드/푸시"| OCIR
OCIR -->|"이미지 Pull"| VM
GH -->|"배포 워크플로우(SSH)"| VM
VM -->|"Secret 조회"| Vault
classDef external fill:#f7f7f7,stroke:#7a7a7a,color:#111,stroke-width:1.2px;
classDef infra fill:#f0f7ff,stroke:#2f6feb,color:#111,stroke-width:1.2px;
classDef app fill:#eefaf0,stroke:#2da44e,color:#111,stroke-width:1.2px;
classDef db fill:#fff8e6,stroke:#b08800,color:#111,stroke-width:1.2px;
class Client,DNS,GH,OCIR,Vault,ExternalIntegrations external;
class IGW infra;
class Nginx,API,Crawler,Scheduler,Batch app;
class MySQL db;
blogzip
├── backend : Kotlin/Spring Boot 멀티모듈 백엔드
│ ├── ai : OpenAI Responses 기반 요약/키워드 추출
│ ├── api : API 서버 애플리케이션 모듈
│ ├── batch : 배치 애플리케이션 모듈
│ ├── crawler-client : RSS/HTML 처리 및 크롤링 클라이언트 공용 모듈
│ ├── domain : 도메인 로직과 DB 연결 담당
│ ├── notification : 이메일 발송 담당
│ └── logging : Slack 로그 메시지 발송 담당
├── crawler : Node.js + Playwright 기반 크롤링 HTTP 서버 (metadata/content fetch)
├── frontend : React + TypeScript 웹 애플리케이션
├── deploy : 운영 배포/실행 스크립트 및 인프라 설정
│ ├── compose : Docker Compose 실행 정의
│ ├── docker : 서비스별 Dockerfile
│ ├── nginx : Nginx 설정
│ ├── scheduler : 정기 배치(cron) 실행 설정
│ └── scripts : 배포/배치 실행/Secret 주입 스크립트
└── .github/workflows : CI/CD 파이프라인