Skip to main content

Build MySQL-based 3-Tier web service with Terraform

Deploy a 3-Tier web service by creating a KakaoCloud network, web and application servers, load balancers, and MySQL with Terraform.

Basic information
  • Estimated time: 40–60 minutes, including MySQL provisioning
  • Recommended operating systems: macOS, Ubuntu
  • Terraform CLI version: 1.14.0 or later, the common baseline for both labs
  • Prerequisites
    • IAM access key and resource management permissions in the target project
    • Registered key pair and its private key file
    • Administrator public IP for bastion host access
Related tutorial

If you are new to Terraform, complete Provision and update VM with Terraform first. That tutorial covers CLI installation, IAM credential preparation, and the VM create, update, and delete workflow.

About this scenario​

In this tutorial, you use Terraform to configure the network, servers, load balancers, and MySQL required for the KakaoCloud Library web service.

Create two web servers and two application servers, connect them to their respective load balancers, and deploy containers automatically using user_data. After deployment, access the service through the web load balancer's public IP to verify the result.

The following diagram shows the main resources created with Terraform and the web request flow. The upper section shows service requests, and the lower section shows administrative access through the bastion host.

                         Internet
| HTTP:80
v
+-- VPC: tutorial (10.0.0.0/16) / kr-central-2-a -----+
| |
| tutorial-main [Public ALB: tutorial-web-lb] |
| | HTTP:80 |
| +-------+-------+ |
| v v |
| tutorial-web [Web VM 1] [Web VM 2] |
| | | |
| +-------+-------+ |
| | TCP:8080 |
| v |
| tutorial-alb [Internal NLB: tutorial-app-lb]|
| | TCP:8080 |
| +-------+-------+ |
| v v |
| tutorial-app [App VM 1] [App VM 2] |
| | | |
| +-------+-------+ |
| | TCP:3306 |
| v |
| tutorial-mysql [Managed MySQL: tutorial-mysql]|
| |
| tutorial-main [Bastion: tutorial-bastion] |
+-----------------------------------------------------+

Admin (admin_cidr)
| SSH:22 / Admin UI:81 / Forwarding:10000-10010
v
Bastion -- TCP forwarding --> Web / App VM SSH:22

The web and application servers are targets for their respective load balancers. The bastion host relays administrative connections and is separate from the web request path. In the diagram, Web VM 1/2 represents tutorial-web-1/2, and App VM 1/2 represents tutorial-app-1/2.

Lab configuration
  • The example deploys to a single availability zone and uses one MySQL Primary instance. Although it creates two servers for each of the web and application tiers, it does not include a configuration for availability zone failures.
  • For a walkthrough that creates resources directly in the console, see Configure VM-based web service.

Before you start​

info

If you completed Provision and update VM with Terraform, delete the VM you created and prepare a new working directory for this example.

Prepare these items in the project where you will run the example. Terraform creates the VPC and subnets, so you do not need to create them in advance.

ItemPreparation
Project and permissionsA project and IAM roles that allow creation, lookup, and deletion of VPC, Virtual Machine, Load Balancer, and MySQL resources
IAM access keyAn IAM access key ID and secret access key issued for the target project
Key pairA key pair registered in the target project and its private key file
Local toolsAn environment where you can run git, curl, and ssh
Administrator IPThe public IP of the local environment that will access the bastion, in CIDR notation, such as 203.0.113.10/32
Check key pair

The example looks up a registered key pair and does not create a new one. The default name is kr2-cloudtech. To use a different key pair, set keypair_name in terraform.tfvars.

Resource plan​

With the default variable values, the example creates the following resources in the kr-central-2-a availability zone of the kr-central-2 region. You can change the tutorial name prefix using project_name.

Network​

The example creates one VPC and five subnets for different purposes.

TypeNameCIDRPurpose
VPCtutorial10.0.0.0/16VPC containing all resources
Subnet (main)tutorial-main10.0.0.0/20Bastion and web load balancer
Subnet (web)tutorial-web10.0.16.0/20Web tier instances
Subnet (alb)tutorial-alb10.0.32.0/20Internal application load balancer
Subnet (app)tutorial-app10.0.48.0/20Application tier instances
Subnet (mysql)tutorial-mysql10.0.64.0/20Managed MySQL
Information

The example creates an internet gateway (IGW), attaches it to the VPC, and adds a 0.0.0.0/0 → IGW default route to the route table. Check network.tf for the network and security group rules for each tier.

Instances​

The example creates one bastion host and four web and application servers.

RoleNameInstance typePublic IP  Image
Bastiontutorial-bastionm2a.largeYUbuntu 20.04
WEB1tutorial-web-1m2a.largeNUbuntu 20.04
WEB2tutorial-web-2m2a.largeNUbuntu 20.04
WAS1tutorial-app-1m2a.largeNUbuntu 20.04
WAS2tutorial-app-2m2a.largeNUbuntu 20.04
Information

The web servers serve a React app through Nginx, and the application servers run a Spring Boot app. cloud-init installation and build tasks continue after instance creation, so wait until the containers are ready.

Load balancers​

The example creates two load balancers for external and inter-tier requests.

TypeNamePublic IP  Purpose
EXT-LBtutorial-web-lbYDistribute requests to web servers with ALB, HTTP:80
INT-LBtutorial-app-lbNDistribute requests to application servers with NLB, TCP:8080

Database​

The example creates a managed MySQL instance group with this configuration.

TypeNameConfiguration
MySQLtutorial-mysqlOne Primary instance, 100 GB data disk, 100 GB log disk, backups disabled

Public IPs are attached to the web load balancer and bastion host. Web and application instances do not have public IPs. The bastion's SSH (22), admin UI (81), and port forwarding range (10000–10010) accept connections only from the IP range specified by admin_cidr.

Getting started​

Check the Terraform environment and prepare the example, then create resources and verify the web service. After the lab, delete the resources you created.

Step 1. Check execution environment​

Follow the installation instructions in the VM tutorial to prepare Terraform CLI 1.14.0 or later and git, then check their versions.

terraform version
git --version

Use the Provider version in this example's versions.tf. Do not copy .tf files, the .terraform directory, or state files from the VM tutorial. Run subsequent commands in the downloaded kakaocloud-3tier-terraform directory.

Step 2. Download example and configure inputs​

  1. Download the Terraform 3-Tier example.

    Download example
    git clone --single-branch --branch feat/terraform-3tier-example https://github.com/kakaoenterprise/kakaocloud-tutorials.git kakaocloud-3tier-terraform

    The configuration is divided into these files. Check variables.tf for all variables and terraform.tfvars.example for example input values.

    FileWhat to check
    versions.tf, variables.tfCLI and Provider requirements and input variables
    data.tf, locals.tf, keypair.tfImage, instance type, and key pair lookups and selection of lookup results
    network.tfVPC, subnets, internet gateway, routes, and security groups
    mysql.tfMySQL creation, endpoint wait, and lookup
    lb_app.tf, app.tfInternal NLB and application instances
    lb_web.tf, web.tfPublic ALB and web instances
    bastion.tf, outputs.tfBastion host and deployment outputs
  2. Change to the example directory.

    Change working directory
    cd ./kakaocloud-3tier-terraform
  3. Set KakaoCloud credentials as environment variables. Enter the values prepared in Issue IAM access key. Do not write credentials to terraform.tfvars. Set them again if you unset them after the VM tutorial or opened a new terminal.

    export TF_VAR_application_credential_id="${ACCESS_KEY}"
    export TF_VAR_application_credential_secret="${SECRET_ACCESS_KEY}"
    환경변수설명
    ACCESS_KEY🖌︎IAM access key ID
    SECRET_ACCESS_KEY🖌︎Secret access key
  4. Copy the variable file.

    Prepare variable file
    cp terraform.tfvars.example terraform.tfvars
  5. Edit terraform.tfvars and enter these values. For admin_cidr, use your current public IP followed by /32. Do not reuse the password in the example file.

    admin_cidr = "${ADMIN_CIDR}"
    mysql_database_user_password = "${MYSQL_PASSWORD}"
    keypair_name = "${KEYPAIR_NAME}"
    환경변수설명
    ADMIN_CIDR🖌︎Administrator public IP in CIDR notation, e.g. 203.0.113.10/32
    MYSQL_PASSWORD🖌︎MySQL administrator password
    KEYPAIR_NAME🖌︎Key pair name registered in the project
    VariableValue
    admin_cidrIP range allowed to access the bastion for administration
    mysql_database_user_passwordRequired variable with no default. Enter a value that meets the MySQL password policy
    keypair_nameRegistered key pair name. Defaults to kr2-cloudtech when omitted
Manage local files

terraform.tfvars stores the password in plain text. Terraform state and plan files can also contain sensitive information. Do not include terraform.tfvars, terraform.tfstate*, saved plan files, or private keys in version control.

Step 3. Review connections and create resources​

First, trace how values pass through the configuration. Terraform determines execution order from resource references and depends_on, rather than filenames.

ConnectionWhat to check
Network → instancesEach instance's subnets references a subnet ID created in network.tf
MySQL → applicationThe endpoint looked up in mysql.tf is passed to MYSQL_HOST in app.tf
Internal NLB → web serversSERVER_ENDPOINT in web.tf receives the internal NLB's private VIP and port 8080
Instances → load balancersEach load balancer's target group registers instances from its tier
Created resources → outputsoutputs.tf exposes connection addresses and instance IPs

Validate the configuration, review the execution plan, and create resources from the example directory.

  1. Initialize the current working directory.

    Initialize working directory
    terraform init
  2. Validate the configuration's syntax and references and review the execution plan. Verify that the resources and inputs match your intended configuration.

    Review plan
    terraform fmt -check
    terraform validate
    terraform plan

    Verify that the plan does not delete or replace existing resources. With default values, it creates one VPC, five subnets, four security groups, five VMs, two load balancers, one MySQL instance group, and two public IPs. The plan also includes listeners, target groups, routes, and other resources, so the total count is higher than this list.

  3. Apply the execution plan. Review the changes and enter yes at the approval prompt.

    Apply configuration
    terraform apply
Information

Managed MySQL may reach AVAILABLE before an endpoint is provided. The example waits two minutes after creating the instance group and looks up the endpoint again. If it is empty, execution stops before creating application instances. Check that the endpoint is available in the console, then run terraform apply again.

Step 4. Verify deployment​

After resource creation, check the results in the console and browser. Container builds on web and application instances may continue after terraform apply finishes.

  1. In Load Balancer, verify that tutorial-web-lb (public ALB) and tutorial-app-lb (internal NLB) have been created.

  2. In Virtual Machine, verify that one bastion host, two web instances, and two application instances have been created. For their names, see Instances.

  3. In MySQL, check the state and endpoint of the tutorial-mysql instance group. After a successful terraform apply, the endpoint should match this output.

    Check endpoint output
    terraform output mysql_endpoint
  4. Open the public IP of tutorial-web-lb in a browser. Use this output to open http://{WEB_LOAD_BALANCER_PUBLIC_IP} and verify that the Library page appears. Also test database-dependent operations such as registration and login to check the application and MySQL connections, as well as the web page.

    Check web service address
    terraform output -raw web_load_balancer_public_ip

Step 5. Inspect instances (optional)​

To access web and application instances directly, configure TCP port forwarding on the bastion host.

  1. Check the Nginx Proxy Manager URL with these commands and open it in a browser. Access the bastion admin UI from an IP range specified in admin_cidr.

    Check bastion IP and NPM URL
    terraform output bastion_public_ip
    terraform output nginx_proxy_manager_url
    • Initial email: admin@example.com
    • Initial password: changeme
    Information

    Change the password immediately after logging in with the initial account.

  2. On the Streams tab, assign a port from 10000 to 10010 for each instance you want to access. Add a rule forwarding it to the instance's private IP and port 22. Check private IPs with these commands.

    Check web and application private IPs
    terraform output web_instance_private_ips
    terraform output app_instance_private_ips
  3. After adding the rule, connect with the private key of the registered key pair. Replace {PRIVATE_KEY_PATH}, {FORWARDED_PORT}, and {BASTION_PUBLIC_IP} with the key file path, forwarding port, and bastion public IP.

    Connect through port forwarding
    chmod 400 "{PRIVATE_KEY_PATH}"
    ssh -i "{PRIVATE_KEY_PATH}" -p "{FORWARDED_PORT}" ubuntu@{BASTION_PUBLIC_IP}
  4. On the instance, check cloud-init completion and container state.

    Check initialization and containers
    sudo cloud-init status --wait
    sudo docker ps -a

    The web server should run kakaocloud-library-client, and the application server should run kakaocloud-library-server. If installation or execution fails, inspect /var/log/cloud-init-output.log and the corresponding container logs.

Step 6. Check for changes and delete resources​

After verifying deployment, run terraform plan again in the same directory. If you have not changed the configuration, check that there are no additional changes. If differences appear, review them and do not immediately apply changes you did not intend.

terraform plan

After the lab, delete the resources created in the example directory. Review the deletion plan and enter yes at the approval prompt. If you need the MySQL data, save it separately before deletion.

Delete created resources
terraform destroy

After Terraform completes deletion, check terraform state list and the console for remaining resources. If deletion fails, check the connections and resource states mentioned in the error and run terraform destroy again. The existing key pair, which was only looked up, is not deleted.

caution

Delete the resources after completing the lab. Managed MySQL and load balancers continue to incur charges while running, so delete them promptly after the lab.

Troubleshooting​

Authentication or lookup errors​

See authentication checks in the VM tutorial. This example also requires permissions to look up, create, and delete VPC, Load Balancer, and MySQL resources. Compare the key pair name, image, and instance type with the lookup conditions in keypair.tf, data.tf, and locals.tf and with the values available in the target project.

Web service is unavailable​

Check connections in this order:

  1. Check the web load balancer's public IP, HTTP 80 listener, and web instance target states.
  2. Follow Inspect instances to check cloud-init and container logs on web and application servers.
  3. Check the internal NLB's TCP 8080 listener and application instance target states.
  4. Check the MySQL endpoint, account and password passed to the app, and allowed ports in each tier's security groups.

Some resources may already exist even if execution fails. Keep the state file, fix the cause, and review the plan again, or clean up with terraform destroy.