Terraform and OpenTofu on Alibaba Cloud: Getting Started with the Alicloud Provider
Alibaba Cloud with Terraform #
Terraform and OpenTofu manage Alibaba Cloud with the alicloud provider, published by Alibaba Cloud (the namespace is aliyun). The workflow is the same as for AWS, Azure and Google Cloud: only the provider and the resource names change.
Some names differ from other clouds:
| AWS | Alibaba Cloud |
|---|---|
| VPC | VPC (alicloud_vpc) |
| Subnet | vSwitch (alicloud_vswitch), always in one zone |
| EC2 | ECS (alicloud_instance) |
| Security group | Security group (alicloud_security_group) |
| S3 | OSS (alicloud_oss_bucket) |
| IAM user | RAM user |
| Key pair | ECS key pair |
Alibaba Cloud runs two separate sites, the international one (alibabacloud.com) and the one for mainland China (aliyun.com). Accounts, regions and available services are not shared, so create the account on the site that matches your use.
Credentials #
Do not use the AccessKey of the account owner, which has full access. Create a RAM user with programmatic access, give it only the policies you need (for this tutorial ECS, VPC and OSS), and create an AccessKey for it. The provider reads these environment variables:
$ export ALIBABA_CLOUD_ACCESS_KEY_ID="<AccessKey ID>"
$ export ALIBABA_CLOUD_ACCESS_KEY_SECRET="<AccessKey Secret>"
$ export ALIBABA_CLOUD_REGION="eu-central-1"
The provider also supports a shared credentials file with profiles, an ECS instance role, and assuming a RAM role. For pipelines prefer a role to long-lived keys, and keep secrets out of the code (see secrets management).
Provider #
terraform {
required_version = ">= 1.6"
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = "~> 1.293"
}
}
}
provider "alicloud" {
# access key, secret and region are read from ALIBABA_CLOUD_* variables
}
variable "instance_type" {
type = string
default = "ecs.n4.large"
description = "ECS instance type. Availability depends on the region and zone"
}
variable "admin_cidr" {
type = string
description = "Address range that can connect with SSH, for example your public IP as x.x.x.x/32"
}
variable "ssh_public_key_path" {
type = string
default = "~/.ssh/id_ed25519.pub"
}The version constraint ~> 1.293 accepts any 1.x release from 1.293. The provider has a 2.0 version in beta that is not selected by this constraint.
A VPC and a vSwitch #
A vSwitch lives in one zone of the region, so look up a zone that offers the instance type you want:
data "alicloud_zones" "main" {
available_resource_creation = "VSwitch"
available_instance_type = var.instance_type
}
resource "alicloud_vpc" "main" {
vpc_name = "ditwl-demo"
cidr_block = "10.0.0.0/16"
}
resource "alicloud_vswitch" "main" {
vswitch_name = "ditwl-demo"
vpc_id = alicloud_vpc.main.id
cidr_block = "10.0.1.0/24"
zone_id = data.alicloud_zones.main.zones[0].id
}
resource "alicloud_security_group" "web" {
security_group_name = "ditwl-demo-web"
vpc_id = alicloud_vpc.main.id
}
resource "alicloud_security_group_rule" "ssh" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = alicloud_security_group.web.id
cidr_ip = var.admin_cidr
}Notes:
nic_type = "intranet"is the value used for security groups of a VPC.port_rangeis writtenfrom/to, so a single port is22/22.prioritygoes from 1 (highest) to 100. When rules conflict, the one with the lower number wins.
An ECS instance #
Use a data source for the image, and an ECS key pair for SSH instead of a password:
data "alicloud_images" "ubuntu" {
owners = "system"
name_regex = "^ubuntu_22_04_x64"
most_recent = true
}
resource "alicloud_ecs_key_pair" "main" {
key_pair_name = "ditwl-demo"
public_key = file(pathexpand(var.ssh_public_key_path))
}
resource "alicloud_instance" "web" {
instance_name = "ditwl-demo-web"
instance_type = var.instance_type
image_id = data.alicloud_images.ubuntu.images[0].id
vswitch_id = alicloud_vswitch.main.id
security_groups = [alicloud_security_group.web.id]
system_disk_category = "cloud_essd"
key_name = alicloud_ecs_key_pair.main.key_pair_name
internet_max_bandwidth_out = 5
}
output "web_public_ip" {
value = alicloud_instance.web.public_ip
}internet_max_bandwidth_out is the outgoing bandwidth in Mbps, and a value above 0 gives the instance a public IP address, with the bandwidth charged. Set it to 0 for an instance that must stay private. If the apply fails because the instance type or the disk category is not available in the zone, choose another instance_type (the zones data source already filters by it) or a different system_disk_category. The image name filter is a regular expression: check that it returns an image with plan before you apply, because image names change over time.
An OSS bucket #
OSS bucket names are unique across all users, so make yours specific. The ACL is a separate resource:
resource "alicloud_oss_bucket" "data" {
bucket = "ditwl-demo-data-change-me"
}
resource "alicloud_oss_bucket_acl" "data" {
bucket = alicloud_oss_bucket.data.bucket
acl = "private"
}Run it #
$ tofu init
$ tofu plan -var admin_cidr=203.0.113.25/32
$ tofu apply -var admin_cidr=203.0.113.25/32
$ tofu destroy -var admin_cidr=203.0.113.25/32
Use your own public IP in admin_cidr and use terraform instead of tofu if you prefer HashiCorp Terraform. Connect with ssh root@<public ip>: the default user of the system images is root, so a more careful setup creates another user with cloud-init and disables root login. For remote state use a backend of your choice.
Read next project structure and best practices.