(agile-aleph-203917) $ rm variables.tf
(agile-aleph-203917) $ sed -i 's / terraform-repo2 / terraform-repo3 /' main.tf
./terraform apply -var = "github_token = f7602b82e02efcbae7fc915c16eeee518280cf2a"
Building infrastructure in GCP with Terraform
Each cloud has its own set of services, its own APIs for them. To simplify the transition from one cloud for both employees in terms of learning and rewriting, there are universal libraries that implement the Facade pattern. A facade is understood as a universal API that disrupts the features of the systems behind it.
One representative of the cloud API facades is KOPS. KOPS is a tool for deploying Kubernetes to GCP, AWS and Azure. KOPS is similar to Kubectl – it is a binary, it can create both commands and the YML config, has a similar syntax, but unlike Kubectl, it creates not a POD, but a cluster node. Another example is Terraform, which specializes in deployment by configuration to adhere to the IasC concept.
To create the infrastructure, we need a token, it is created in GCP for the service account to which access is issued. To do this, I went along the path: IAM and administration -> Service accounts -> Create a service account and upon creation I dropped the Owner role (full access for test purposes), created a key with the Create key button in JSON format and renamed the downloaded key to Key. JSON. To describe the infrastructure, I used the documentation www.terraform.io/docs/providers/google/index.html :
(agil7e-aleph-20391) $ cat main.tf
provider "google" {
credentials = "$ {file (" key.json ")}"
project = "agile-aleph-203917"
region = "us-central1"
}
resource "google_compute_instance" "terraform" {
name = "terraform"
machine_type = "n1-standard-1"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud / debian-9"
}
}
network_interface {
network = "default"
}
}
Let's check the user rights:
(agile-aleph-203917) $ gcloud auth list
Credentialed Accounts
ACTIVE ACCOUNT
* esschtolts@gmail.com
To set the active account, run:
$ gcloud config set account `ACCOUNT`
Let's select the project as the current one (you can create the current one by default):
$ gcloud config set project agil7e-aleph-20391;
(agil7e-aleph-20391) $ ./terraform init | grep success
Terraform has been successfully initialized!
Now let's create one instance in the WEB console, after copying the key to the key.json file in the Terraform directory:
machine_type: "" => "n1-standard-1"
metadata_fingerprint: "" => "
name: "" => "terraform"
network_interface. #: "" => "1"
network_interface.0.address: "" => "
network_interface.0.name: "" => "
network_interface.0.network: "" => "default"
network_interface.0.network_ip: "" => "
network_interface.0.network: "" => "default"
project: "" => "
scheduling. #: "" => "
self_link: "" => "
tags_fingerprint: "" => "
zone: "" => "us-central1-a"
google_compute_instance.terraform: Still creating … (10s elapsed)
google_compute_instance.terraform: Still creating … (20s elapsed)
google_compute_instance.terraform: Still creating … (30s elapsed)
google_compute_instance.terraform: Still creating … (40s elapsed)
google_compute_instance.terraform: Creation complete after 40s (ID: terraform)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
That's it, we have created a server instance. Now let's remove it:
~ / terraform (agil7e-aleph-20391) $ ./terraform apply
google_compute_instance.terraform: Refreshing state … (ID: terraform)
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
– destroy
Terraform will perform the following actions:
– google_compute_instance.terraform
Plan: 0 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
google_compute_instance.terraform: Destroying … (ID: terraform)
google_compute_instance.terraform: Still destroying … (ID: terraform, 10s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 20s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 30s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 40s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 50s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 1m0s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 1m10s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 1m20s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 1m30s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 1m40s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 1m50s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 2m0s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 2m10s elapsed)
google_compute_instance.terraform: Still destroying … (ID: terraform, 2m20s elapsed)
google_compute_instance.terraform: Destruction complete after 2m30s
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
Building infrastructure in AWS
To create an AWS cluster configuration, create a separate folder for it, and the previous one in a parallel one:
esschtolts @ cloudshell: ~ / terraform (agil7e-aleph-20391) $ mkdir gcp
esschtolts @ cloudshell: ~ / terraform (agil7e-aleph-20391) $ mv main.tf gcp / main.tf
esschtolts @ cloudshell: ~ / terraform (agil7e-aleph-20391) $ mkdir aws
esschtolts @ cloudshell: ~ / terraform (agil7e-aleph-20391) $ cd aws
Role is an analogue of a user, only not for people, but for services such as AWS, and in our case, these are EKS servers. But I do not see users as an analogue of roles, but groups, for example, a group for creating a cluster, a group for working with a database, etc. Only one role can be assigned to a server, and a role can contain multiple rights (Polices). As a result, we do not need to work with logins and passwords, or with tokens, or with certificates: store, transfer, restrict access, transfer – we only indicate in the WEB toolbar (IMA) or using the API (and derivatively in the configuration) the rights … Our cluster needs these rights in order for it to self-configure and replicate as it consists of standard AWS services. To manage the components of the AWS EC2 cluster (server), AWS ELB (Elastic Load Balancer, balancer) and AWS KMS (Key Management Service, key manager and encryption), you need AmazonEKSClusterPolicy access, to monitor AmazonEKSServicePolicy using CloudWatch Logs components (monitoring by logs) , Route 53 (creating a network in the zone), IAM (rights management). I did not describe the role in the config and created it through IAM according to the documentation: https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role. html # create-service-role.
For greater reliability, the nodes of the Kubernetes cluster should be located in different zones, that is, data centers. Each region contains several zones to maintain fault tolerance, while maintaining minimal letency (server response time) for the local population. It is important to note that some regions may be represented in several copies within the same country, for example, US-east-1 in US East (N. Virginia) and US-east-2 in US East (Ohio) – regions are designated in numbers. So far, the creation of an EKS cluster is available only to the US-east zone.
The VPC for the developer, at its simplest, boils down to naming a subnet as a specific resource.
Let's write the configuration according to the documentation www.terraform.io/docs/providers/aws/r/eks_cluster. html :
esschtolts @ cloudshell: ~ / terraform / aws (agile-aleph-203917) $ cat main.tf
provider "aws" {
access_key = "$ {var.token}"
secret_key = "$ {var.key}"
region = "us-east-1"
}
# Params
variable "token" {
default = ""
}
variable "key" {
default = ""
}
# EKS
resource "aws_eks_cluster" "example" {
enabled_cluster_log_types = ["api", "audit"]
name = "exapmle"
role_arn = "arn: aws: iam :: 177510963163: role / ServiceRoleForAmazonEKS2"
vpc_config {
subnet_ids = ["$ {aws_subnet.subnet_1.id}", "$ {aws_subnet.subnet_2.id}"]
}
}
output "endpoint" {
value = "$ {aws_eks_cluster.example.endpoint}"
}
output "kubeconfig-certificate-authority-data" {
value = "$ {aws_eks_cluster.example.certificate_authority.0.data}"
}
# Role
data "aws_iam_policy_document" "eks-role-policy" {
statement {
actions = ["sts: AssumeRole"]
principals {
type = "Service"
identifiers = ["eks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "tf_role" {
name = "tf_role"
assume_role_policy = "$ {data.aws_iam_policy_document.eks-role-policy.json}"
tags = {
tag-key = "tag-value"
}
}
resource "aws_iam_role_policy_attachment" "attach-cluster" {
role = "tf_role"
policy_arn = "arn: aws: iam :: aws: policy / AmazonEKSClusterPolicy"
}
resource "aws_iam_role_policy_attachment" "attach-service" {
role = "tf_role"
policy_arn = "arn: aws: iam :: aws: policy / AmazonEKSServicePolicy"
}
# Subnet
resource "aws_subnet" "subnet_1" {
vpc_id = "$ {aws_vpc.main.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "Main"
}
}
resource "aws_subnet" "subnet_2" {
vpc_id = "$ {aws_vpc.main.id}"
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "Main"
}
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
After 9 minutes 44 seconds, I got a ready-made self-supporting infrastructure for a Kubernetes cluster:
esschtolts @ cloudshell: ~ / terraform / aws (agile-aleph-203917) $ ./../terraform apply -var = "token = AKIAJ4SYCNH2XVSHNN3A" -var = "key = huEWRslEluynCXBspsul3AkKlin1ViR9 + Mo
Now let's delete (it took me 10 minutes 23 seconds):
esschtolts @ cloudshell: ~ / terraform / aws (agile-aleph-203917) $ ./../terraform destroy -var = "token = AKIAJ4SYCNH2XVSHNN3A" -var = "key = huEWRslEluynCXBspsul3AkKlin1ViR9 + Mo
Destroy complete! Resources: 7 destroyed.
Establishing the CI / CD process
Amazon provides (aws.amazon.com/ru/devops/) a wide range of DevOps tools designed in a cloud infrastructure:
* AWS Code Pipeline – the service allows you to create a chain of stages from a set of services in a visual editor, through which the code must go before it goes to production, for example, assembly and testing.
* AWS Code Build – the service provides an auto-scaling build queue, which may be required for compiled programming languages, when adding features or making changes requires a long re-compilation of the entire application, when using one server it becomes a bottleneck when rolling out the changes.
* AWS Code Deploy – Automates deployment and rollback in case of errors.
* AWS CodeStar – the service combines the main features of the previous services.
Setting up remote control
artifact server
aws s3 ls s3: // name_backet aws s3 sync s3: // name_backet name_fonder –exclude * .tmp # files from the bucket will be downloaded to the folder, for example, a website
Now, we need to download the AWS plugin:
esschtolts @ cloudshell: ~ / terraform / aws (agile-aleph-203917) $ ./../terraform init | grep success
Terraform has been successfully initialized!
Now we need to get access to AWS, for that we click on the name of your user in the header of the WEB interface, in addition to My account , the My Security Credentials item will appear , by selecting which, we go to Access Key -> Create New Access Key . Let's create EKS (Elastic Kuberntes Service):
esschtolts @ cloudshell: ~ / terraform / aws (agile-aleph-203917) $ ./../terraform apply
–var = "token = AKIAJ4SYCNH2XVSHNN3A" -var = "key = huEWRslEluynCXBspsul3AkKlinAlR9 + MoU1ViY7"
Delete everything:
$ ../terraform destroy
Creating a cluster in GCP
node pool – combining nodes into a cluster with
resource "google_container_cluster" "primary" {
name = "tf"
location = "us-central1"
$ cat main.tf # configuration state
terraform {
required_version = "> 0.10.0"
}
terraform {
backend "s3" {
bucket = "foo-terraform"
key = "bucket / terraform.tfstate"
region = "us-east-1"
encrypt = "true"
}
}
$ cat cloud.tf # cloud configuration
provider "google" {
token = "$ {var.hcloud_token}"
}
$ cat variables.tf # variables and getting tokens
variable "hcloud_token" {}
$ cat instances.tf # create resources
resource "hcloud_server" "server" {....
$ terraform import aws_acm_certificate.cert arn: aws: acm: eu-central-1: 123456789012: certificate / 7e7a28d2-163f-4b8f-b9cd-822f96c08d6a
$ terraform init # Initialize configs
$ terraform plan # Check actions
$ terraform apply # Running actions
Debugging:
essh @ kubernetes-master: ~ / graylog $ sudo docker run –name graylog –link graylog_mongo: mongo –link graylog_elasticsearch: elasticsearch \
–p 9000: 9000 -p 12201: 12201 -p 1514: 1514 \
–e GRAYLOG_HTTP_EXTERNAL_URI = "http://127.0.0.1:9000/" \
–d graylog / graylog: 3.0
0f21f39192440d9a8be96890f624c1d409883f2e350ead58a5c4ce0e91e54c9d
docker: Error response from daemon: driver failed programming external connectivity on endpoint graylog (714a6083b878e2737bd4d4577d1157504e261c03cb503b6394cb844466fb4781): Bind for 0.0.0.0:9000 failed: port is already allocated.
essh @ kubernetes-master: ~ / graylog $ sudo netstat -nlp | grep 9000
tcp6 0 0 ::: 9000 ::: * LISTEN 2505 / docker-proxy
essh @ kubernetes-master: ~ / graylog $ docker rm graylog
graylog
essh @ kubernetes-master: ~ / graylog $ sudo docker run –name graylog –link graylog_mongo: mongo –link graylog_elasticsearch: elasticsearch \
–p 9001: 9000 -p 12201: 12201 -p 1514: 1514 \
–e GRAYLOG_HTTP_EXTERNAL_URI = "http://127.0.0.1:9001/" \
–d graylog / graylog: 3.0
e5aefd6d630a935887f494550513d46e54947f897e4a64b0703d8f7094562875
https://blog.maddevs.io/terrafom-hetzner-a2f22534514b
For example, let's create one instance:
$ cat aws / provider.tf
provider "aws" {
region = "us-west-1"
}
resource "aws_instance" "my_ec2" {
ami = "$ {data.aws_ami.ubuntu.id}"
instance_type = "t2.micro"
}
$ cd aws
$ aws configure
$ terraform init
$ terraform apply –auto-approve
$ cd ..
provider "aws" {
region = "us-west-1"
}
resource "aws_sqs_queue" "terraform_queue" {
name = "terraform-queue"
delay_seconds = 90
max_message_size = 2048
message_retention_seconds = 86400
receive_wait_time_seconds = 10
}
data "aws_route53_zone" "vuejs_phalcon" {
name = "test.com."
private_zone = true
}
resource "aws_route53_record" "www" {
zone_id = "$ {data.aws_route53_zone.vuejs_phalcon.zone_id}"
name = "www. $ {data.aws_route53_zone.selected.name}"
type = "A"
ttl = "300"
records = ["10.0.0.1"]
}
resource "aws_elasticsearch_domain" "example" {
domain_name = "example"
elasticsearch_version = "1.5"
cluster_config {
instance_type = "r4.large.elasticsearch"
}
snapshot_options {
automated_snapshot_start_hour = 23
}
}
resource "aws_eks_cluster" "eks_vuejs_phalcon" {
name = "eks_vuejs_phalcon"
role_arn = "$ {aws_iam_role.eks_vuejs_phalcon.arn}"
vpc_config {
subnet_ids = ["$ {aws_subnet.eks_vuejs_phalcon.id}", "$ {aws_subnet.example2.id}"]
}
}
output "endpoint" {
value = "$ {aws_eks_cluster.eks_vuejs_phalcon.endpoint}"
}
output "kubeconfig-certificate-authority-data" {
value = "$ {aws_eks_cluster.eks_vuejs_phalcon.certificate_authority.0.data}"
}
provider "google" {
credentials = "$ {file (" account.json ")}"
project = "my-project-id"
region = "us-central1"
}
resource "google_container_cluster" "primary" {
name = "my-gke-cluster"
location = "us-central1"
remove_default_node_pool = true
initial_node_count = 1
master_auth {
username = ""
password = ""
}
}
output "client_certificate" {
value = "$ {google_container_cluster.primary.master_auth.0.client_certificate}"
}
output "client_key" {
value = "$ {google_container_cluster.primary.master_auth.0.client_key}"
}
output "cluster_ca_certificate" {
value = "$ {google_container_cluster.primary.master_auth.0.cluster_ca_certificate}"
}
$ cat deployment.yml
apiVersion: apps / v1
kind: Deployment
metadata:
name: phalcon_vuejs
namespace: development
spec:
selector:
matchLabels:
app: vuejs
replicas: 1
template:
metadata:
labels:
app: vuejs
spec:
initContainers:
– name: vuejs_build
image: vuejs / ci
volumeMounts:
– name: app
mountPath: / app / public
command:
– / bin / bash
– -c
– |
cd / app / public
git clone essch / vuejs_phalcon: 1.0.
npm test
npm build
containers:
– name: healtcheck
image: mileschou / phalcon: 7.2-cli
args:
– / bin / sh
– -c
– cd / usr / src / app && git clone essch / app_phalcon: 1.0 && touch / tmp / healthy && sleep 10 && php script.php
readinessProbe:
exec:
command:
– cat
– / tmp / healthy
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
exec:
command:
– cat
– / tmp / healthy
initialDelaySeconds: 15
periodSeconds: 5
voumes:
– name: app
emptyDir: {}
So we created an AWS EC2 instance. We omitted specifying the keys because the AWS API is already authorized and this authorization will be used by Terraform.
Also, for code use, Terraform supports variables, data, and modules.
Let's create a separate network:
resource "aws_vpc" "my_vpc" {
cidr_block = "190.160.0.0/16"
instance_target = "default"
}
resource "aws_subnet" "my_subnet" {
vpc_id = "$ {aws_vpc.my_vpc.id}"
cidr_block = "190.160.1.0/24"
}
$ cat gce / provider.tf
provider "google" {
credentials = "$ {file (" account.json ")}"
project = "my-project-id"
region = "us-central1"
}
resource "google_compute_instance" "default" {
name = "test"
machine_type = "n1-standard-1"
zone = "us-central1-a"
}
$ cd gce
$ terraform init
$ terraform apply
$ cd ..
For distributed work, let's put the state in AWS S3 the state of the infrastructure (you can also put other data), but for security in a different region:
terraform {
backend "s3" {
bucket = "tfstate"
key = "terraform.tfstate"
region = "us-state-2"
}
}
provider "kubernetes" {
host = "https://104.196.242.174"
username = "ClusterMaster"
password = "MindTheGap"
}
resource "kubernetes_pod" "my_pod" {
spec {
container {
image = "Nginx: 1.7.9"
name = "Nginx"
port {
container_port = 80
}
}
}
}
Commands:
terraform init # downloading dependencies according to configs, checking them
terraform validate # syntax check
terraform plan # to see in detail how the infrastructure will be changed and why exactly so, for example,
whether only the service meta information will be changed or the service itself will be re-created, which is often unacceptable for databases.
terraform apply # applying changes
The common part for all providers is the core.
$ which aws
$ aws fonfigure # https://www.youtube.com/watch?v=IxA1IPypzHs
$ cat aws.tf
# https://www.terraform.io/docs/providers/aws/r/instance.html
resource "aws_instance" "ec2instance" {
ami = "$ {var.ami}"
instance_type = "t2.micro"
}
resource "aws_security_group" "instance_gc" {
…
}
$ cat run.js
export AWS_ACCESS_KEY_ID = "anaccesskey"
export AWS_SECRET_ACCESS_KEY = "asecretkey"
export AWS_DEFAULT_REGION = "us-west-2"
terraform plan
terraform apply
$ cat gce.tf # https://www.terraform.io/docs/providers/google/index.html#
# Google Cloud Platform Provider
provider "google" {
credentials = "$ {file (" account.json ")}"
project = "phalcon"
region = "us-central1"
}
#https: //www.terraform.io/docs/providers/google/r/app_engine_application.html
resource "google_project" "my_project" {
name = "My Project"
project_id = "your-project-id"
org_id = "1234567"
}
resource "google_app_engine_application" "app" {
project = "$ {google_project.my_project.project_id}"
location_id = "us-central"
}
# google_compute_instance
resource "google_compute_instance" "default" {
name = "test"
machine_type = "n1-standard-1"
zone = "us-central1-a"
tags = ["foo", "bar"]
boot_disk {
initialize_params {
image = "debian-cloud / debian-9"
}
}
// Local SSD disk
scratch_disk {
}
network_interface {
network = "default"
access_config {
// Ephemeral IP
}
}
metadata = {
foo = "bar"
}
metadata_startup_script = "echo hi> /test.txt"
service_account {
scopes = ["userinfo-email", "compute-ro", "storage-ro"]
}
}
Extensibility using an external resource, which can be a BASH script:
data "external" "python3" {
program = ["Python3"]
}
Building a cluster of machines with Terraform
Clustering with Terraform is covered in Building Infrastructure in GCP. Now let's pay more attention to the cluster itself, and not to the tools for creating it. I will create a project through the GCE admin panel (displayed in the interface header) node-cluster. I downloaded the key for Kubernetes IAM and administration -> Service accounts -> Create a service account and when creating it, I selected the Owner role and put it in a project called kubernetes_key.JSON:
eSSH @ Kubernetes-master: ~ / node-cluster $ cp ~ / Downloads / node-cluster-243923-bbec410e0a83.JSON ./kubernetes_key.JSON
Downloaded terraform:
essh @ kubernetes-master: ~ / node-cluster $ wget https://releases.hashicorp.com/terraform/0.12.2/terraform_0.12.2_linux_amd64.zip> / dev / null 2> / dev / null
essh @ kubernetes-master: ~ / node-cluster $ unzip terraform_0.12.2_linux_amd64.zip && rm -f terraform_0.12.2_linux_amd64.zip
Archive: terraform_0.12.2_linux_amd64.zip
inflating: terraform
essh @ kubernetes-master: ~ / node-cluster $ ./terraform version
Terraform v0.12.2
Added the GCE provider and started downloading the "drivers" to it:
essh @ kubernetes-master: ~ / node-cluster $ cat main.tf
provider "google" {
credentials = "$ {file (" kubernetes_key.json ")}"
project = "node-cluster"
region = "us-central1"
} essh @ kubernetes-master: ~ / node-cluster $ ./terraform init
Initializing the backend …
Initializing provider plugins …
– Checking for available provider plugins …
– Downloading plugin for provider "google" (terraform-providers / google) 2.8.0 …
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking