Быстрый Π΄Π΅ΠΏΠ»ΠΎΠΉ vm ESXi с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Terraform

ВсСм ΠΏΡ€ΠΈΠ²Π΅Ρ‚, мСня Π·ΠΎΠ²ΡƒΡ‚ Иван ΠΈ я Π°Π»ΠΊΠΎΠ³ΠΎΠ»ΠΈΠΊ систСмный администратор (OPS).

Π― Π±Ρ‹ Ρ…ΠΎΡ‚Π΅Π» Ρ€Π°ΡΡΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΊΠ°ΠΊ Ρ€Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°ΡŽ Π²ΠΈΡ€Ρ‚ΡƒΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΌΠ°ΡˆΠΈΠ½Ρ‹ Π½Π° ESXi Π±Π΅Π· vCenter с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Terraform.

Π”ΠΎΠ²ΠΎΠ»ΡŒΠ½ΠΎ часто приходится Ρ€Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ/ΠΏΠ΅Ρ€Π΅ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ Π²ΠΈΡ€Ρ‚ΡƒΠ°Π»ΠΊΠΈ, Ρ‡Ρ‚ΠΎ Π±Ρ‹ ΠΏΡ€ΠΎΡ‚Π΅ΡΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΎ ΠΈΠ»ΠΈ ΠΈΠ½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅. Π’ силу Π»Π΅Π½ΠΈ я ΠΏΠΎΠ΄ΡƒΠΌΠ°Π» ΠΎΠ± Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΠ·Π°Ρ†ΠΈΠΈ процСсса. Мои поиски ΠΏΡ€ΠΈΠ²Π΅Π»ΠΈ мСня ΠΊ Π·Π°ΠΌΠ΅Ρ‡Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚Ρƒ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ hashicorp, terraform.

Π”ΡƒΠΌΠ°ΡŽ ΠΌΠ½ΠΎΠ³ΠΈΠ΅ Π·Π½Π°ΡŽΡ‚ Ρ‡Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ Terraform, Π° ΠΊΡ‚ΠΎ Π½Π΅ Π·Π½Π°Π΅Ρ‚, это ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для управлСния Π»ΡŽΠ±Ρ‹ΠΌ ΠΎΠ±Π»Π°ΠΊΠΎΠΌ, инфраструктурой ΠΈΠ»ΠΈ слуТбой ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ ΠΊΠΎΠ½Ρ†Π΅ΠΏΡ†ΠΈΡŽ IasC (Π˜Π½Ρ„Ρ€Π°ΡΡ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΊΠ°ΠΊ ΠΊΠΎΠ΄).

Π’ качСствС срСды Π²ΠΈΡ€Ρ‚ΡƒΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ я ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽ ESXi. Π”ΠΎΠ²ΠΎΠ»ΡŒΠ½ΠΎ простая, удобная ΠΈ надёТная.
ΠŸΡ€Π΅Π΄Π²ΠΈΠΆΡƒ вопрос.

Π—Π°Ρ‡Π΅ΠΌ terraform Ссли ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ vCenter Server?

МоТно ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎ, Π½ΠΎ. Π’ΠΎ-ΠΏΠ΅Ρ€Π²Ρ‹Ρ…, это Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ лицСнзия, Π²ΠΎ-Π²Ρ‚ΠΎΡ€Ρ‹Ρ…, Π΄Π°Π½Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ ΠΎΡ‡Π΅Π½ΡŒ рСсурсоСмкий ΠΈ просто Π½Π΅ помСстится Π½Π° ΠΌΠΎΡ‘ΠΌ домашнСм сСрвСрС, ΠΈ Π²-Ρ‚Ρ€Π΅Ρ‚ΡŒΠΈΡ… Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΏΡ€ΠΎΠΊΠ°Ρ‡Π°Ρ‚ΡŒ скиллы.

Π’ качСствС сСрвСра выступаСт ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ° Intel NUC:

CPU: 2 CPUs x Intel(R) Core(TM) i3-4010U CPU @ 1.70GHz
RAM: 8Gb
HDD: 500Gb
ESXi version: ESXi-6.5.0-4564106-standard (VMware, Inc.)

И Ρ‚Π°ΠΊ, ΠΎΠ±ΠΎ всём ΠΏΠΎ порядку.

Пока Π΄Π°Π²Π°ΠΉΡ‚Π΅ настроим esxi, Π° ΠΈΠΌΠ΅Π½Π½ΠΎ ΠΎΡ‚ΠΊΡ€ΠΎΠ΅ΠΌ VNC ΠΏΠΎΡ€Ρ‚ Π² настройках Ρ„Π°ΠΉΡ€Π²ΠΎΠ»Π°.

По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ, Ρ„Π°ΠΉΠ» Π·Π°Ρ‰ΠΈΡ‰Ρ‘Π½ ΠΎΡ‚ записи. ΠŸΡ€ΠΎΠ²ΠΎΠ΄ΠΈΠΌ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ манипуляции:

chmod 644 /etc/vmware/firewall/service.xml
chmod +t /etc/vmware/firewall/service.xml
vi /etc/vmware/firewall/service.xml

дописываСм Π² ΠΊΠΎΠ½Π΅Ρ† Ρ„Π°ΠΉΠ»Π° ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΉ Π±Π»ΠΎΠΊ:

<service id="1000">
  <id>packer-vnc</id>
  <rule id="0000">
    <direction>inbound</direction>
    <protocol>tcp</protocol>
    <porttype>dst</porttype>
    <port>
      <begin>5900</begin>
      <end>6000</end>
    </port>
  </rule>
  <enabled>true</enabled>
  <required>true</required>
</service>

Π’Ρ‹Ρ…ΠΎΠ΄ΠΈΠΌ, сохраняСм. МСняСм ΠΏΡ€Π°Π²Π° ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ ΠΈ ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ сСрвис:

chmod 444 /etc/vmware/firewall/service.xml
esxcli network firewall refresh

ΠΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½ΠΎ Π΄ΠΎ ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ хоста. ПослС ΠΆΠ΅, Π΄Π°Π½Π½ΡƒΡŽ ΠΌΠ°Π½ΠΈΠΏΡƒΠ»ΡΡ†ΠΈΡŽ придётся ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚ΡŒ.

Π”Π°Π»ΡŒΡˆΠ΅ всю Ρ€Π°Π±ΠΎΡ‚Ρƒ Π±ΡƒΠ΄Ρƒ ΠΏΡ€ΠΎΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ Π² Π²ΠΈΡ€Ρ‚ΡƒΠ°Π»ΡŒΠ½ΠΎΠΉ машинС Π½Π° этом ΠΆΠ΅ сСрвСрС.

Π₯арактСристики:

OS: Centos 7 x86_64 minimal
RAM: 1GB
HDD: 20GB
Selinux: disable
firewalld: disable

Π”Π°Π»Π΅Π΅ Π½Π°ΠΌ потрСбуСтся packer, Ρ‚Π°ΠΊ ΠΆΠ΅ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ HashiCorp.

НуТСн ΠΎΠ½, для автоматичСской сборки Β«Π·ΠΎΠ»ΠΎΡ‚ΠΎΠ³ΠΎΒ» ΠΎΠ±Ρ€Π°Π·Π°. ΠšΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² Π±ΡƒΠ΄ΡƒΡ‰Π΅ΠΌ.

yum install unzip git -y
curl -O https://releases.hashicorp.com/packer/1.5.5/packer_1.5.5_linux_amd64.zip
unzip packer_1.5.5_linux_amd64.zip -d /usr/bin && rm -rf packer_1.5.5_linux_amd64.zip
packer version
Packer v1.5.5

На шагС packer version ΠΌΠΎΠΆΠ΅Ρ‚ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΡƒΡ‚ΡŒ ошибка, Ρ‚Π°ΠΊ ΠΊΠ°ΠΊ Π² RedHat-based ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒΡΡ ΠΏΠ°ΠΊΠ΅Ρ‚ с Ρ‚Π°ΠΊΠΈΠΌ ΠΆΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ.

which -a packer
/usr/sbin/packer

Для Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ симлинк, Π»ΠΈΠ±ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π°Π±ΡΠΎΠ»ΡŽΡ‚Π½Ρ‹ΠΉ ΠΏΡƒΡ‚ΡŒ /usr/bin/packer.

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π½Π°ΠΌ потрСбуСтся ovftool download link. Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅ΠΌ, ΠΊΠ»Π°Π΄Ρ‘ΠΌ Π½Π° сСрвСр ΠΈ устанавливаСм:

chmod +x VMware-ovftool-4.4.0-15722219-lin.x86_64.bundle
./VMware-ovftool-4.4.0-15722219-lin.x86_64.bundle
Extracting VMware Installer...done.
You must accept the VMware OVF Tool component for Linux End User
License Agreement to continue.  Press Enter to proceed.
VMWARE END USER LICENSE AGREEMENT
Do you agree? [yes/no]:yes
The product is ready to be installed.  Press Enter to begin
installation or Ctrl-C to cancel. 
Installing VMware OVF Tool component for Linux 4.4.0
    Configuring...
[######################################################################] 100%
Installation was successful.

ДвиТСмся дальшС.

На Π³ΠΈΡ‚Π΅ я ΠΏΠΎΠ΄Π³ΠΎΡ‚ΠΎΠ²ΠΈΠ» всё Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎΠ΅.

git clone https://github.com/letnab/create-and-deploy-esxi.git && cd create-and-deploy-esxi

Π’ ΠΏΠ°ΠΏΠΊΡƒ iso Π½ΡƒΠΆΠ½ΠΎ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚ΡŒ дистрибутив ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠΉ систСмы. Π’ ΠΌΠΎΡ‘ΠΌ случаС это centos 7.

Π’Π°ΠΊ ΠΆΠ΅ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» centos-7-base.json:

variables: ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ свои Π΄Π°Π½Π½Ρ‹Π΅ для ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ
iso_urls: ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Π°ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½Ρ‹ΠΉ
iso_checksum: чСксумма вашСго ΠΎΠ±Ρ€Π°Π·Π° 

ПослС всСх ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ запускаСм сборку:

/usr/bin/packer build centos-7-base.json

Если всё настроСно ΠΈ ΡƒΠΊΠ°Π·Π°Π½ΠΎ Π²Π΅Ρ€Π½ΠΎ, Ρ‚ΠΎ Π²Ρ‹ ΡƒΠ²ΠΈΠ΄ΠΈΡ‚Π΅ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½Ρƒ автоматичСской инсталляции ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠΉ систСмы. Π”Π°Π½Π½Ρ‹ΠΉ процСсс Ρƒ мСня Π·Π°Π½ΠΈΠΌΠ°Π΅Ρ‚ 7-8 ΠΌΠΈΠ½ΡƒΡ‚.

ПослС ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠ³ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ Π² ΠΏΠ°ΠΏΠΊΠ΅ output-packer-centos7-x86_64 Π±ΡƒΠ΄Π΅Ρ‚ находится ova Ρ„Π°ΠΉΠ».

УстанавливаСм Terraform:

curl -O https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip
unzip terraform_0.12.24_linux_amd64.zip -d /usr/bin/ && rm -rf terraform_0.12.24_linux_amd64.zip
terraform version
Terraform v0.12.24

Π’Π°ΠΊ ΠΊΠ°ΠΊ Ρƒ Terraform Π½Π΅Ρ‚ ΠΏΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€Π° ΠΏΠΎΠ΄ ESXi, Π½ΡƒΠΆΠ½ΠΎ Π΅Π³ΠΎ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ.

Π‘Ρ‚Π°Π²ΠΈΠΌ go:

cd /tmp
curl -O https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.14.2.linux-amd64.tar.gz && rm -rf go1.14.2.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version
go version go1.14.2 linux/amd64

Π”Π°Π»Π΅Π΅ собираСм ΠΏΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€:

go get -u -v golang.org/x/crypto/ssh
go get -u -v github.com/hashicorp/terraform
go get -u -v github.com/josenk/terraform-provider-esxi
export GOPATH="$HOME/go"
cd $GOPATH/src/github.com/josenk/terraform-provider-esxi
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-w -extldflags "-static"' -o terraform-provider-esxi_`cat version`
cp terraform-provider-esxi_`cat version` /usr/bin

ΠœΡ‹ Π½Π° Ρ„ΠΈΠ½ΠΈΡˆΠ½ΠΎΠΉ прямой. ΠŸΠΎΠ΅Ρ…Π°Π»ΠΈ Ρ€Π°ΡΠΊΠ°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ наш ΠΎΠ±Ρ€Π°Π·.

ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ Π² ΠΏΠ°ΠΏΠΊΡƒ:

cd /root/create-and-deploy-esxi/centos7

ΠŸΠ΅Ρ€Π²Ρ‹ΠΌ Π΄Π΅Π»ΠΎΠΌ, Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠ΅ΠΌ Ρ„Π°ΠΉΠ» variables.tf. НуТно ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ сСрвСру ESXi.

Π’ Ρ„Π°ΠΉΠ»Π΅ network_config.cfg ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒΡΡ сСтСвыС настройки Π±ΡƒΠ΄ΡƒΡ‰Π΅ΠΉ Π²ΠΈΡ€Ρ‚ΡƒΠ°Π»ΡŒΠ½ΠΎΠΉ ΠΌΠ°ΡˆΠΈΠ½Ρ‹. МСняСм ΠΏΠΎΠ΄ свои Π½ΡƒΠΆΠ΄Ρ‹ ΠΈ запускаСм однострочник:

sed -i -e '2d' -e '3i "network": "'$(gzip < network_config.cfg| base64 | tr -d 'n')'",' metadata.json

Ну ΠΈ Π² Ρ„Π°ΠΉΠ»Π΅ main.tf мСняСм ΠΏΡƒΡ‚ΡŒ ΠΊ ova Ρ„Π°ΠΉΠ»Ρƒ Π½Π° свой, Ссли отличаСтся.

ΠœΠΎΠΌΠ΅Π½Ρ‚ истины.

terraform init
Initializing the backend...

Initializing provider plugins...

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
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.esxi: version = "~> 1.6"
* provider.template: version = "~> 2.1"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.template_file.Default: Refreshing state...
data.template_file.network_config: Refreshing state...

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # esxi_guest.Default will be created
  + resource "esxi_guest" "Default" {
      + boot_disk_size         = (known after apply)
      + disk_store             = "datastore1"
      + guest_name             = "centos7-test"
      + guest_shutdown_timeout = (known after apply)
      + guest_startup_timeout  = (known after apply)
      + guestinfo              = {
          + "metadata"          = "base64text"
          + "metadata.encoding" = "gzip+base64"
          + "userdata"          = "base64text"
          + "userdata.encoding" = "gzip+base64"
        }
      + guestos                = (known after apply)
      + id                     = (known after apply)
      + ip_address             = (known after apply)
      + memsize                = "1024"
      + notes                  = (known after apply)
      + numvcpus               = (known after apply)
      + ovf_properties_timer   = (known after apply)
      + ovf_source             = "/root/create-and-deploy-esxi/output-packer-centos7-x86_64/packer-centos7-x86_64.ova"
      + power                  = "on"
      + resource_pool_name     = (known after apply)
      + virthwver              = (known after apply)

      + network_interfaces {
          + mac_address     = (known after apply)
          + nic_type        = (known after apply)
          + virtual_network = "VM Network"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Ѐиниш:

terraform apply

Если всё сдСлано Π²Π΅Ρ€Π½ΠΎ, Ρ‚ΠΎ Ρ‡Π΅Ρ€Π΅Π· 2-3 ΠΌΠΈΠ½ΡƒΡ‚Ρ‹ Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π·Π²Ρ‘Ρ€Π½ΡƒΡ‚Π° новая Π²ΠΈΡ€Ρ‚ΡƒΠ°Π»ΡŒΠ½Π°Ρ машина ΠΈΠ· Ρ€Π°Π½Π΅Π΅ сдСланного ΠΎΠ±Ρ€Π°Π·Π°.

Π’Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹ использования всСго этого, ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°ΡŽΡ‚ΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ„Π°Π½Ρ‚Π°Π·ΠΈΠ΅ΠΉ.

Π― всСго лишь Ρ…ΠΎΡ‚Π΅Π» ΠΏΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ Π½Π°Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°ΠΌΠΈ ΠΈ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ основныС ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹ ΠΏΡ€ΠΈ Ρ€Π°Π±ΠΎΡ‚Π΅ с Π΄Π°Π½Π½Ρ‹ΠΌΠΈ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚Π°ΠΌΠΈ.

Π‘Π»Π°Π³ΠΎΠ΄Π°Ρ€ΡŽ Π·Π° Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅!

P.S.: Π±ΡƒΠ΄Ρƒ Ρ€Π°Π΄ конструктивной ΠΊΡ€ΠΈΡ‚ΠΈΠΊΠ΅.

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com

Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ