-
Notifications
You must be signed in to change notification settings - Fork 1
chore: 기존 prod 인프라 Terraform 관리 대상으로 전환 #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
684d218
chore: 운영 인프라 현황과 Terraform 범위 문서화
issuejong 00ddb6b
chore: Terraform 상태 저장소와 prod 기본 구성 추가
issuejong 7c0e827
chore: 운영 EC2 네트워크와 IAM 리소스 정의
issuejong e2c08de
chore: 운영 RDS 리소스와 DB 보안 그룹 정의
issuejong 85206ff
chore: 운영 S3와 CloudWatch 리소스 정의
issuejong 4fa92ab
chore: 운영 리소스 import 절차와 검증 추가
issuejong 8770d9e
fix: 코드래빗 리뷰 반영
issuejong 6052a1d
fix: Terraform state workspace 조회 권한 보완
issuejong 91bab9d
fix: Terraform bootstrap 계정 제한과 S3 암호화 설정 정정
issuejong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Production Infrastructure Inventory | ||
|
|
||
| ## Purpose | ||
|
|
||
| This document records the existing production infrastructure before it is | ||
| adopted by Terraform. It is an inventory, not an authorization to change AWS | ||
| resources. | ||
|
|
||
| ## Scope | ||
|
|
||
| | Area | Existing resource | Terraform approach | | ||
| | --- | --- | --- | | ||
| | EC2 | `i-0b4fe94824ca7d3c7` (`moru-server`) | Import and manage | | ||
| | Elastic IP | `eipalloc-0423074c40c068102` | Import and manage | | ||
| | EC2 security group | `sg-0ff632924dad2adb6` (`moru-ec2-sg`) | Import and manage | | ||
| | RDS MySQL | `moru-db` | Import and manage | | ||
| | RDS security group | `sg-0056408c9dc50cef4` (`moru-rds-sg`) | Import and manage | | ||
| | S3 assets | `moru-prod-assets-488230509502` | Import and manage | | ||
| | S3 TTS previews | `moru-prod-preview-assets-488230509502` | Import and manage | | ||
| | EC2 IAM role/profile | `moru-server-s3-role` | Import and manage | | ||
| | CloudWatch Logs | `/moru/prod/app` | Import and manage | | ||
| | VPC | `vpc-087c2d76939191694` | Reference with data source | | ||
| | EC2 subnet | `subnet-0a94acc68b254e59f` | Reference with data source | | ||
| | RDS subnet group | `default-vpc-087c2d76939191694` | Reference with data source | | ||
|
|
||
| The VPC and subnets are the account's default network and may be shared by | ||
| other workloads. They must not be imported or changed in this migration. | ||
|
|
||
| ## Observed Production Configuration | ||
|
|
||
| ### Compute and network | ||
|
|
||
| - EC2: `t3.micro`, Amazon Linux AMI `ami-0e4ab31f1847c850c`, in | ||
| `ap-northeast-2c`. | ||
| - The instance uses the `moru-key` key pair, the `moru-server-s3-role` instance | ||
| profile, and an IMDSv2-required metadata configuration. | ||
| - Elastic IP `43.202.84.114` is associated with the instance. | ||
| - The application container publishes only to `127.0.0.1:8080`; Nginx remains | ||
| host-managed and proxies the public HTTP(S) traffic. | ||
|
|
||
| ### Database | ||
|
|
||
| - RDS MySQL `moru-db`: MySQL `8.4.9`, `db.t4g.micro`, 20 GiB `gp2`, encrypted | ||
| storage, one-day backup retention, and no Multi-AZ deployment. | ||
| - The database name is `moru`. Credentials are intentionally excluded from this | ||
| repository and Terraform configuration. | ||
|
|
||
| ### Storage and logging | ||
|
|
||
| - `moru-prod-assets-488230509502` stores private application assets. | ||
| - `moru-prod-preview-assets-488230509502` allows public reads only below | ||
| `tts/previews/*` and `tts/common/*` for preview and common TTS audio. | ||
| - Both production buckets use AES256 default encryption. Versioning is not | ||
| currently enabled. | ||
| - Docker uses the `awslogs` driver and writes to `/moru/prod/app`, which retains | ||
| logs for 14 days. No CloudWatch metric alarms are currently configured. | ||
| - The EC2 role has inline policies for the production and development asset | ||
| buckets. The production policy allows only the `tts/*` and `profiles/*` | ||
| prefixes and CloudWatch log writes. | ||
|
|
||
| ## Out of Scope for This Migration | ||
|
|
||
| - Docker Compose services, the Redis container, and the GitHub Actions deploy | ||
| workflow remain unchanged. | ||
| - Nginx, Certbot, DuckDNS, and files on the EC2 host are documented only. They | ||
| are not changed or managed by Terraform in this phase. | ||
| - Database credentials, application secrets, and GCP credentials remain in the | ||
| existing secret-management flow. | ||
|
|
||
| ## Security Review Items | ||
|
|
||
| These items were observed during the read-only audit. They are intentionally | ||
| not changed by the Terraform adoption work and need separate approval. | ||
|
|
||
| 1. The RDS instance is publicly accessible, and `moru-rds-sg` currently allows | ||
| all traffic from IPv4 and IPv6 addresses. | ||
| 2. `moru-ec2-sg` allows SSH from every IPv4 address. | ||
| 3. The production S3 buckets do not currently use versioning. This is distinct | ||
| from the planned Terraform state bucket, which must use versioning. | ||
|
|
||
| ## Adoption Safety Rules | ||
|
|
||
| - Never run `terraform apply`, `terraform import`, or `terraform destroy` | ||
| without explicit approval. | ||
| - Do not accept a plan that replaces, creates, modifies, or destroys an | ||
| existing production resource during import. | ||
| - Keep Terraform state, plan files, credentials, and application secrets out of | ||
| Git. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # Production Terraform Import Runbook | ||
|
|
||
| ## Purpose | ||
|
|
||
| This runbook adopts existing Moru production AWS resources into Terraform | ||
| state. It must not create, update, replace, or delete an application resource. | ||
| The configuration uses declarative `import` blocks, so the import is reviewed | ||
| through the normal `plan` and `apply` workflow. | ||
|
|
||
| ## Preconditions | ||
|
|
||
| 1. The Terraform state bucket has been created from `infra/bootstrap` after a | ||
| separate approval. | ||
| 2. The approved Terraform operator has the state-access policy created by the | ||
| bootstrap configuration, plus only the AWS permissions needed to manage the | ||
| imported resources. | ||
| 3. The `moru_terraform` discovery user remains read-only. Do not use it for | ||
| import or apply. | ||
| 4. No concurrent Terraform operation is running for prod. | ||
| 5. The production service is healthy before beginning. Record the current | ||
| health result and do not restart Docker, Nginx, or EC2 as part of this work. | ||
|
|
||
| ## Import Order | ||
|
|
||
| The import blocks in `infra/prod/imports.tf` cover the following existing | ||
| resources: | ||
|
|
||
| 1. EC2 security group and its seven rules | ||
| 2. EC2 IAM role, two inline policies, and instance profile | ||
| 3. EC2 instance and its Elastic IP | ||
| 4. RDS security group and its three rules | ||
| 5. RDS parameter group and MySQL instance | ||
| 6. Private production assets S3 bucket and its security settings | ||
| 7. Public TTS preview S3 bucket, its security settings, and its public-read | ||
| bucket policy | ||
| 8. CloudWatch application log group | ||
|
|
||
| The default VPC, default subnets, default DB subnet group, Docker Compose, | ||
| Redis, GitHub Actions, Nginx, Certbot, DuckDNS, and EC2-hosted files are not | ||
| imported in this phase. | ||
|
|
||
| ## Read-Only Validation | ||
|
|
||
| The following commands do not write Terraform state and are safe to run with | ||
| the `moru-prod` read-only profile after login (`moru_terraform` is the | ||
| read-only discovery user, not the profile name): | ||
|
|
||
| ```bash | ||
| cd infra/prod | ||
| AWS_PROFILE=moru-prod terraform init -reconfigure | ||
| AWS_PROFILE=moru-prod terraform validate | ||
| AWS_PROFILE=moru-prod terraform plan -lock=false | ||
| ``` | ||
|
|
||
| The expected result is a plan containing imports only. Stop immediately when | ||
| the plan includes an action other than `import` for an existing resource. | ||
|
|
||
| ## Approved Import Procedure | ||
|
|
||
| 1. Use the approved Terraform operator profile and configure | ||
| `infra/prod/backend.tf` and `infra/prod/backend.hcl` from the committed | ||
| examples. | ||
| 2. Initialize the remote state backend: | ||
|
|
||
| ```bash | ||
| terraform init -reconfigure -backend-config=backend.hcl | ||
| ``` | ||
|
|
||
| 3. Produce a reviewable plan: | ||
|
|
||
| ```bash | ||
| terraform plan -out=prod-import.tfplan -lock-timeout=5m | ||
| terraform show -no-color prod-import.tfplan | ||
| ``` | ||
|
|
||
| 4. Confirm every action is an import and no existing resource has a planned | ||
| update, replacement, or deletion. Obtain explicit approval for the exact | ||
| plan. | ||
| 5. Apply the reviewed plan once: | ||
|
|
||
| ```bash | ||
| terraform apply prod-import.tfplan | ||
| ``` | ||
|
|
||
| 6. Verify Terraform now reports no drift: | ||
|
|
||
| ```bash | ||
| terraform plan -lock-timeout=5m | ||
| terraform state list | ||
| ``` | ||
|
|
||
| 7. Remove the local `prod-import.tfplan` file. It is ignored by Git and must | ||
| never be committed. | ||
|
|
||
| ## Stop Conditions | ||
|
|
||
| Stop and investigate before an apply when any of the following occurs: | ||
|
|
||
| - A plan proposes a create, change, replace, or destroy action for an existing | ||
| production resource. | ||
| - The AWS account check is not `488230509502`. | ||
| - The state bucket cannot be encrypted, versioned, or locked. | ||
| - A resource ID differs from `docs/infrastructure/prod-inventory.md`. | ||
| - The application health check fails before or during the activity. | ||
|
|
||
| ## Post-Import Follow-Up | ||
|
|
||
| The open RDS and SSH network rules are documented security findings, not an | ||
| incidental Terraform migration change. Address them in a separate, reviewed | ||
| security issue with an explicit connectivity test and rollback procedure. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # Terraform Infrastructure | ||
|
|
||
| `bootstrap` creates the dedicated S3 backend resources. It intentionally uses | ||
| local state for this one-time setup. `prod` manages the existing Moru | ||
| production infrastructure after its backend is configured. | ||
|
|
||
| ## Safety Boundary | ||
|
|
||
| The current `moru-prod` AWS profile is read-only. It can run discovery, | ||
| `terraform fmt`, `terraform validate`, and a read-only import plan. It cannot | ||
| create the state bucket or import resources into remote state. | ||
|
|
||
| Do not run `terraform apply`, `terraform import`, or `terraform destroy` until | ||
| the plan is reviewed and explicit approval is given. | ||
|
|
||
| ## State Backend | ||
|
|
||
| The backend is deliberately separate from the existing application buckets. | ||
| The bootstrap configuration creates a private bucket with: | ||
|
|
||
| - AES256 server-side encryption | ||
| - S3 bucket versioning for state recovery | ||
| - S3 lockfiles through `use_lockfile = true` | ||
| - public-access blocking and bucket-owner-enforced ownership | ||
| - a transport-security bucket policy | ||
| - an unattached least-privilege policy for the future Terraform operator | ||
|
|
||
| HashiCorp recommends bucket versioning for S3 state recovery and S3 lockfiles | ||
| for state locking. DynamoDB locking is not used because it is deprecated by the | ||
| S3 backend. | ||
|
|
||
| ### Bootstrap after approval | ||
|
|
||
| 1. Sign in with a separately approved AWS identity that can create the backend | ||
| bucket and IAM policy. Do not use the root account. | ||
| 2. Choose a globally unique state bucket name. The recommended candidate is | ||
| `moru-prod-terraform-state-488230509502`, subject to availability. | ||
| 3. Run the following from `infra/bootstrap`: | ||
|
|
||
| ```bash | ||
| terraform init | ||
| terraform plan -var='state_bucket_name=CHOSEN_BUCKET_NAME' | ||
| ``` | ||
|
|
||
| 4. Review that the plan only creates the state bucket resources and the | ||
| unattached operator policy. After explicit approval, run `terraform apply` | ||
| with the same variable. | ||
| 5. Attach the created state-access policy only to the approved Terraform | ||
| operator identity. Keep the `moru_terraform` discovery user read-only. | ||
|
|
||
| ### Configure the production backend | ||
|
|
||
| 1. Copy `infra/prod/backend.tf.example` to `infra/prod/backend.tf` and copy | ||
| `infra/prod/backend.hcl.example` to `infra/prod/backend.hcl`. | ||
| 2. Replace the bucket name with the approved state bucket name. Do not commit | ||
| either file. | ||
| 3. Run the following from `infra/prod`: | ||
|
|
||
| ```bash | ||
| terraform init -backend-config=backend.hcl | ||
| terraform plan | ||
| ``` | ||
|
|
||
| The first production plan should show imports only. Any planned create, update, | ||
| replacement, or destroy for an existing production resource is a stop signal. | ||
|
|
||
| Before the state bucket exists, leave `backend.tf` absent and use Terraform's | ||
| local backend only for read-only validation. Local state and plan files are | ||
| ignored by Git and must never be committed. | ||
|
|
||
| ## Non-Terraform Host Configuration | ||
|
|
||
| Docker Compose, Redis, GitHub Actions, Nginx, Certbot, DuckDNS, and EC2-hosted | ||
| files remain outside the Terraform scope in this phase. Their existing setup is | ||
| documented in the repository and on the production host; it must not be changed | ||
| as part of adoption. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.