Chicken or Egg: Breaking Down IaC

Chicken or Egg: Breaking Down IaC
Which came first — the chicken or the egg? Quite a strange start for an article about Infrastructure-as-Code, isn't it?

What is an egg?

Most often, Infrastructure-as-Code (IaC) is a declarative way of representing infrastructure. In it, we describe the state we want to achieve, from the hardware to the software configuration. Thus, IaC is used for:

  1. Resource Provisioning. This includes VMs, S3, VPC, etc. The main tools for working are: Terraform and CloudFormation.
  2. Software Configuration. The main tools are: Ansible, Chef, etc.

Any code resides in git repositories. Sooner or later, the team lead will decide that it's time to bring some order to them. And they will refactor. They will create a certain structure. And they will find that this is good.

It's also good that there already exists GitLab and GitHub-a provider for Terraform (and this is Software Configuration). With their help, you can manage the entire project: team members, CI/CD, git-flow, etc.

Where did the egg come from?

Here we gradually approach the main question.

First of all, you need to start with the repository that describes the structure of other repositories, including itself. And of course, within the GitOps framework, you need to add CI for changes to be executed automatically.

What if Git hasn't been created yet?

  1. How to store it in Git?
  2. How to integrate CI?
  3. What if we are also deploying GitLab using IaC, and it's in Kubernetes?
  4. And GitLab Runner is also in Kubernetes?
  5. Is Kubernetes in a cloud provider?

Which came first: GitLab, where I will upload my code, or the code that describes what kind of GitLab I need?

Chicken with Eggs

«Oyako-don3 with a dinosaur» [src]

Let’s try to prepare a dish using the cloud provider Managed Kubernetes Selectel.

TL;DR

Can we do it all in one command?

$ export MY_SELECTEL_TOKEN=
$ curl https://gitlab.com/chicken-or-egg/mks/make/-/snippets/2002106/raw | bash

Ingredients:

  • An account from my.selectel.ru;
  • Token from the account;
  • Kubernetes skills;
  • Helm skills;
  • Terraform skills;
  • Helm chart for GitLab;
  • Helm chart for GitLab Runner.

Recipe:

  1. Get MY_SELECTEL_TOKEN from the panel my.selectel.ru.
  2. Create a Kubernetes cluster, passing the account token to it.
  3. Get the KUBECONFIG from the created cluster.
  4. Install GitLab in Kubernetes.
  5. Get the GitLab-token from the created GitLab for the user. root.
  6. Create the project structure in GitLab using the GitLab-token.
  7. Push the existing code to GitLab.
  8. ???
  9. Profit!

Step 1. The token can be acquired in the section API Keys.

Chicken or Egg: Breaking Down IaCStep 2We are preparing our Terraform for 'baking' a cluster of 2 nodes. If you are sure that you have enough resources, you can enable autoscaling:

provider "selectel" {
 token = var.my_selectel_token
}

variable "my_selectel_token" {}
variable "username" {}
variable "region" {}


resource "selectel_vpc_project_v2" "my-k8s" {
 name = "my-k8s-cluster"
 theme = {
   color = "269926"
 }
 quotas {
   resource_name = "compute_cores"
   resource_quotas {
     region = var.region
     zone = "${var.region}a"
     value = 16
   }
 }
 quotas {
   resource_name = "network_floatingips"
   resource_quotas {
     region = var.region
     value = 1
   }
 }
 quotas {
   resource_name = "load_balancers"
   resource_quotas {
     region = var.region
     value = 1
   }
 }
 quotas {
   resource_name = "compute_ram"
   resource_quotas {
     region = var.region
     zone = "${var.region}a"
     value = 32768
   }
 }
 quotas {
   resource_name = "volume_gigabytes_fast"
   resource_quotas {
     region = var.region
     zone = "${var.region}a"
     # (20 * 2) + 50 + (8 * 3 + 10)
     value = 130
   }
 }
}

resource "selectel_mks_cluster_v1" "k8s-cluster" {
 name         = "k8s-cluster"
 project_id   = selectel_vpc_project_v2.my-k8s.id
 region       = var.region
 kube_version = "1.17.9"
}

resource "selectel_mks_nodegroup_v1" "nodegroup_1" {
 cluster_id        = selectel_mks_cluster_v1.k8s-cluster.id
 project_id        = selectel_mks_cluster_v1.k8s-cluster.project_id
 region            = selectel_mks_cluster_v1.k8s-cluster.region
 availability_zone = "${var.region}a"
 nodes_count       = 2
 cpus              = 8
 ram_mb            = 16384
 volume_gb         = 15
 volume_type       = "fast.${var.region}a"
 labels            = {
   "project": "my",
 }
}

Adding a user to the project:

resource "random_password" "my-k8s-user-pass" {
 length = 16
 special = true
 override_special = "_%@"
}

resource "selectel_vpc_user_v2" "my-k8s-user" {
 password = random_password.my-k8s-user-pass.result
 name = var.username
 enabled  = true
}

resource "selectel_vpc_keypair_v2" "my-k8s-user-ssh" {
 public_key = file("~/.ssh/id_rsa.pub")
 user_id    = selectel_vpc_user_v2.my-k8s-user.id
 name = var.username
}

resource "selectel_vpc_role_v2" "my-k8s-role" {
 project_id = selectel_vpc_project_v2.my-k8s.id
 user_id    = selectel_vpc_user_v2.my-k8s-user.id
}

Output:

output "project_id" {
 value = selectel_vpc_project_v2.my-k8s.id
}

output "k8s_id" {
 value = selectel_mks_cluster_v1.k8s-cluster.id
}

output "user_name" {
 value = selectel_vpc_user_v2.my-k8s-user.name
}

output "user_pass" {
 value = selectel_vpc_user_v2.my-k8s-user.password
}

Starting:

$ env 
TF_VAR_region=ru-3 
TF_VAR_username=diamon 
TF_VAR_my_selectel_token= 
terraform plan -out planfile

$ terraform apply -input=false -auto-approve planfile

Chicken or Egg: Breaking Down IaC
Step 3Obtaining the kubeconfig.

To programmatically download KUBECONFIG, you need to get a token from OpenStack:

openstack token issue -c id -f value > token

Then use this token to make a request to the Managed Kubernetes Selectel API. k8s_id returns terraform:

curl -XGET -H "x-auth-token: $(cat token)" "https://ru-3.mks.selcloud.ru/v1/clusters/$(cat k8s_id)/kubeconfig" -o kubeConfig.yaml

The kubeconfig can also be obtained via the panel.

Chicken or Egg: Breaking Down IaC
Step 4After the cluster has been baked and we have access to it, we can add yaml on top as desired.

I prefer to add:

  • namespace,
  • storage class,
  • pod security policy, and more.

Storage Class For Selectel, you can take from the official repository..

Since I initially chose a cluster in the zone ru-3a, I also need the Storage Class from this zone.

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
 name: fast.ru-3a
 annotations:
   storageclass.kubernetes.io/is-default-class: "true"
provisioner: cinder.csi.openstack.org
parameters:
 type: fast.ru-3a
 availability: ru-3a
allowVolumeExpansion: true

Step 5. We will set up a load balancer.

We will use the standard one for many nginx-ingress. There are already plenty of instructions on how to install it, so we won't dwell on that.

$ helm repo add nginx-stable https://helm.nginx.com/stable
$ helm upgrade nginx-ingress nginx-stable/nginx-ingress -n ingress --install -f ../internal/K8S-cluster/ingress/values.yml

We wait for it to obtain an external IP, which takes about 3-4 minutes:

Chicken or Egg: Breaking Down IaC
Received external IP:

Chicken or Egg: Breaking Down IaC
Step 6. Now we install GitLab.

$ helm repo add gitlab https://charts.gitlab.io
$ helm upgrade gitlab gitlab/gitlab -n gitlab --install -f gitlab/values.yml --set "global.hosts.domain=gitlab.$EXTERNAL_IP.nip.io"

Again, we wait until all the pods are up.

kubectl get po -n gitlab
NAME                                      	READY   STATUS  	RESTARTS   AGE
gitlab-gitaly-0                           	0/1 	Pending 	0      	0s
gitlab-gitlab-exporter-88f6cc8c4-fl52d    	0/1 	Pending 	0      	0s
gitlab-gitlab-runner-6b6867c5cf-hd9dp     	0/1 	Pending 	0      	0s
gitlab-gitlab-shell-55cb6ccdb-h5g8x       	0/1 	Init:0/2	0      	0s
gitlab-migrations.1-2cg6n                 	0/1 	Pending 	0      	0s
gitlab-minio-6dd7d96ddb-zd9j6             	0/1 	Pending 	0      	0s
gitlab-minio-create-buckets.1-bncdp       	0/1 	Pending 	0      	0s
gitlab-postgresql-0                       	0/2 	Pending 	0      	0s
gitlab-prometheus-server-6cfb57f575-v8k6j 	0/2 	Pending 	0      	0s
gitlab-redis-master-0                     	0/2 	Pending 	0      	0s
gitlab-registry-6bd77b4b8c-pb9v9          	0/1 	Pending 	0      	0s
gitlab-registry-6bd77b4b8c-zgb6r          	0/1 	Init:0/2	0      	0s
gitlab-shared-secrets.1-pc7-5jgq4         	0/1 	Completed   0      	20s
gitlab-sidekiq-all-in-1-v1-54dbcf7f5f-qbq67   0/1 	Pending 	0      	0s
gitlab-task-runner-6fd6857db7-9x567       	0/1 	Pending 	0      	0s
gitlab-webservice-d9d4fcff8-hp8wl         	0/2 	Pending 	0      	0s
Waiting gitlab
./wait_gitlab.sh ../internal/gitlab/gitlab/.pods
waiting for pod...
waiting for pod...
waiting for pod...

Pods are up:

Chicken or Egg: Breaking Down IaC
Step 7. We are obtaining the GitLab token.

First, we find out the login password:

kubectl get secret -n gitlab gitlab-gitlab-initial-root-password -o jsonpath='{.data.password}' | base64 --decode

Now we authenticate and get the token:

python3 get_gitlab_token.py root $GITLAB_PASSWORD http://gitlab.gitlab.$EXTERNAL_IP.nip.io

Step 8. We organize the Git repositories into the correct hierarchy using the Gitlab Provider.

cd ../internal/gitlab/hierarchy && terraform apply -input=false -auto-approve planfile

Unfortunately, there is a floating bug. Therefore, you will need to manually delete the conflicting projects to fix the tf.state. Then restart the command `$ make all`

Step 9. We transfer local repositories to the server.

$ make push

[master (root-commit) b61d977]  Initial commit
 3 files changed, 46 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 values.yml
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 770 bytes | 770.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)

Done:

Chicken or Egg: Breaking Down IaC
Chicken or Egg: Breaking Down IaC
Chicken or Egg: Breaking Down IaC

Conclusion

We have achieved the ability to declaratively manage everything from our local machine. Now we want to move all these tasks into CI and just press buttons. For this, we need to transfer our local states (Terraform state) to CI. How to do this will be explained in the next part.

Subscribe to our blog, so you won't miss the release of new articles!

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster