ΠΠ° Π₯Π°Π±ΡΠ΅ ΠΌΠ½ΠΎΠ³ΠΎ ΡΡΠ°ΡΠ΅ΠΉ ΠΎ Jenkins, Π½ΠΎ ΠΌΠ°Π»ΠΎ Π³Π΄Π΅ ΠΎΠΏΠΈΡΡΠ²Π°Π΅ΡΡΡ ΠΏΡΠΈΠΌΠ΅Ρ ΡΠ°Π±ΠΎΡΡ Jenkins ΠΈ Π΄ΠΎΠΊΠ΅Ρ Π°Π³Π΅Π½ΡΠΎΠ². ΠΡΠ΅ ΠΏΠΎΠΏΡΠ»ΡΡΠ½ΡΠ΅ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ ΡΠ±ΠΎΡΠΊΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠΎΠ² ΡΠΈΠΏΠ°
ΠΠ° ΡΠ΅Π³ΠΎΠ΄Π½ΡΡΠ½ΠΈΠΉ Π΄Π΅Π½Ρ Π΅ΡΡΡ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ: Jenkins 2 Π·Π°ΠΌΠ΅ΡΠ°ΡΠ΅Π»ΡΠ½ΠΎ ΡΠΌΠ΅Π΅Ρ ΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ
ΠΠΎΡΠ΅ΠΌΡ Ρ Π·Π°Π½ΡΠ»ΡΡ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ΠΌ ΡΡΠΎΠΉ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ?
Π’Π°ΠΊ ΠΊΠ°ΠΊ ΠΌΡ Π² ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ
- Π±ΠΎΠ»ΡΡΠΎΠΉ ΠΎΠ±ΡΠ΅ΠΌ ΡΠ°Π½ΡΠ°ΠΉΠΌΠΎΠ², ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΠΈ Π·Π°Π±ΡΠ²Π°ΡΡ ΡΠΈΡΡΠΈΡΡ;
- Π΅ΡΡΡ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡΡ ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ°Π·Π½ΡΠΌΠΈ Π²Π΅ΡΡΠΈΡΠΌΠΈ ΠΎΠ΄Π½ΠΈΡ ΡΠ°Π½ΡΠ°ΠΉΠΌΠΎΠ²;
- ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΡ Π½ΡΠΆΠ΅Π½ ΡΠ°Π·Π½ΡΠΉ Π½Π°Π±ΠΎΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ².
ΠΡΡΡ ΠΈ Π΄ΡΡΠ³ΠΈΠ΅ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ, Π½ΠΎ Π΄Π°Π²Π°ΠΉΡΠ΅ Ρ Π»ΡΡΡΠ΅ ΡΠ°ΡΡΠΊΠ°ΠΆΡ ΠΏΡΠΎ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅.
Jenkins Π² Docker
Π’Π°ΠΊ ΠΊΠ°ΠΊ ΡΠ΅ΠΉΡΠ°Ρ Docker ΡΠΆΠ΅ Ρ
ΠΎΡΠΎΡΠΎ ΡΠΊΠΎΡΠ΅Π½ΠΈΠ»ΡΡ Π² ΡΡΠ΅ΡΠ΅ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ, ΡΠΎ ΠΏΠΎΡΡΠΈ Π²ΡΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΏΡΡΡΠΈΡΡ ΠΏΡΠΈ ΠΏΠΎΠΌΠΎΡΠΈ Docker. ΠΠΎΠ΅ ΠΆΠ΅ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ Π² ΡΠΎΠΌ, ΡΡΠΎΠ±Ρ Jenkins Π±ΡΠ» Π² Docker ΠΈ ΠΌΠΎΠ³ Π·Π°ΠΏΡΡΠΊΠ°ΡΡ Π΄ΡΡΠ³ΠΈΠ΅ Docker ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΡ. ΠΡΠΈΠΌ Π²ΠΎΠΏΡΠΎΡΠΎΠΌ ΡΡΠ°Π»ΠΈ Π·Π°Π΄Π°Π²Π°ΡΡΡΡ Π΅ΡΠ΅ Π² 2013 Π³ΠΎΠ΄Ρ Π² ΡΡΠ°ΡΡΠ΅ «
ΠΡΠ»ΠΈ Π²ΠΊΡΠ°ΡΡΠ΅ ΠΏΡΠΎΡΡΠΎ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ Π² ΡΠ°Π±ΠΎΡΠΈΠΉ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅Ρ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ ΡΠ°ΠΌ Docker ΠΈ ΠΏΡΠΈΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°ΡΡ ΡΠ°ΠΉΠ» /var/run/docker.sock
.
ΠΠΎΡ ΠΏΡΠΈΠΌΠ΅Ρ Dockerfile, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ»ΡΡΠΈΠ»ΡΡ Π΄Π»Ρ Jenkins.
FROM jenkins/jenkins:lts
USER root
RUN apt-get update &&
apt-get -y install apt-transport-https
ca-certificates
curl
gnupg2
git
software-properties-common &&
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey &&
add-apt-repository
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")
$(lsb_release -cs)
stable" &&
apt-get update &&
apt-get -y install docker-ce &&
usermod -aG docker jenkins
RUN curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
RUN apt-get clean autoclean && apt-get autoremove βyes && rm -rf /var/lib/{apt,dpkg,cache,log}/
USER jenkins
Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ ΠΌΡ ΠΏΠΎΠ»ΡΡΠΈΠ»ΠΈ Docker ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅Ρ ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΠΎΠΆΠ΅Ρ Π²ΡΠΏΠΎΠ»Π½ΡΡΡ Docker ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π½Π° Ρ ΠΎΡΡΠΎΠ²ΠΎΠΉ ΠΌΠ°ΡΠΈΠ½Π΅.
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΠ±ΠΎΡΠΊΠΈ
ΠΠ΅ ΡΠ°ΠΊ Π΄Π°Π²Π½ΠΎ Jenkins ΠΏΠΎΠ»ΡΡΠΈΠ» Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ ΠΎΠΏΠΈΡΡΠ²Π°ΡΡ ΡΠ²ΠΎΠΈ ΠΏΡΠ°Π²ΠΈΠ»Π° ΠΏΡΠΈ ΠΏΠΎΠΌΠΎΡΠΈ
Π’Π°ΠΊ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΆΠ΅ ΠΌΡ ΠΏΠΎΠΌΠ΅ΡΡΠΈΠΌ Π² ΡΠ°ΠΌ ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΉ ΡΠΏΠ΅ΡΠΈΠ°Π»ΡΠ½ΡΠΉ Dockerfile, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ Π² ΡΠ΅Π±Π΅ Π²ΡΠ΅ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡΠ΅ Π΄Π»Ρ ΡΠ±ΠΎΡΠΊΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ ΡΠ°ΠΌ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊ ΠΌΠΎΠΆΠ΅Ρ ΠΏΠΎΠ΄Π³ΠΎΡΠΎΠ²ΠΈΡΡ ΠΏΠΎΠ²ΡΠΎΡΡΠ΅ΠΌΡΡ ΡΡΠ΅Π΄Ρ ΠΈ Π½Π΅ Π½ΡΠΆΠ½ΠΎ Π±ΡΠ΄Π΅Ρ OPS ΠΏΡΠΎΡΠΈΡΡ ΠΏΠΎΡΡΠ°Π²ΠΈΡΡ Π½Π° Ρ ΠΎΡΡ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΡ Π²Π΅ΡΡΠΈΡ Node.JS.
FROM node:12.10.0-alpine
RUN npm install yarn -g
Π’Π°ΠΊΠΎΠΉ ΡΠ±ΠΎΡΠΎΡΠ½ΡΠΉ ΠΎΠ±ΡΠ°Π· ΠΏΠΎΠ΄Ρ ΠΎΠ΄ΠΈΡ Π΄Π»Ρ Π±ΠΎΠ»ΡΡΠΈΠ½ΡΡΠ²Π° Node.JS ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ. Π Π΅ΡΠ»ΠΈ Π²Π°ΠΌ, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, Π½ΡΠΆΠ΅Π½ ΠΎΠ±ΡΠ°Π· Π΄Π»Ρ JVM ΠΏΡΠΎΠ΅ΠΊΡΠ° ΡΠΎ Π²ΠΊΠ»ΡΡΠ΅Π½Π½ΡΠΌ Π²Π½ΡΡΡΡ Sonar ΡΠΊΠ°Π½Π΅ΡΠΎΠΌ? ΠΡ ΡΠ°ΠΌΠΈ Π²ΠΎΠ»ΡΠ½Ρ Π²ΡΠ±ΠΈΡΠ°ΡΡ Π½ΡΠΆΠ½ΡΠ΅ Π΄Π»Ρ ΡΠ±ΠΎΡΠΊΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ.
FROM adoptopenjdk/openjdk12:latest
RUN apt update
&& apt install -y
bash unzip wget
RUN mkdir -p /usr/local/sonarscanner
&& cd /usr/local/sonarscanner
&& wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.3.0.1492-linux.zip
&& unzip sonar-scanner-cli-3.3.0.1492-linux.zip
&& mv sonar-scanner-3.3.0.1492-linux/* ./
&& rm sonar-scanner-cli-3.3.0.1492-linux.zip
&& rm -rf sonar-scanner-3.3.0.1492-linux
&& ln -s /usr/local/sonarscanner/bin/sonar-scanner /usr/local/bin/sonar-scanner
ENV PATH $PATH:/usr/local/sonarscanner/bin/
ENV SONAR_RUNNER_HOME /usr/local/sonarscanner/bin/
ΠΡ ΠΎΠΏΠΈΡΠ°Π»ΠΈ ΡΠ±ΠΎΡΠΎΡΠ½ΠΎΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅, Π½ΠΎ ΠΏΡΠΈ ΡΠ΅ΠΌ ΡΡΡ Jenkins? Π Jenkins Π°Π³Π΅Π½ΡΡ ΡΠΌΠ΅ΡΡ ΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ ΡΠ°ΠΊΠΈΠΌΠΈ Docker ΠΎΠ±ΡΠ°Π·Π°ΠΌΠΈ ΠΈ ΠΏΡΠΎΠ²ΠΎΠ΄ΠΈΡΡ ΡΠ±ΠΎΡΠΊΡ Π²Π½ΡΡΡΠΈ.
stage("Build project") {
agent {
docker {
image "project-build:${DOCKER_IMAGE_BRANCH}"
args "-v ${PWD}:/usr/src/app -w /usr/src/app"
reuseNode true
label "build-image"
}
}
steps {
sh "yarn"
sh "yarn build"
}
}
ΠΠΈΡΠ΅ΠΊΡΠΈΠ²Π° agent
ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ docker
, Π³Π΄Π΅ Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΡΠΊΠ°Π·Π°ΡΡ:
- ΠΈΠΌΡ ΡΠ±ΠΎΡΠΎΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ° ΡΠΎΠ³Π»Π°ΡΠ½ΠΎ Π²Π°ΡΠ΅ΠΉ ΠΏΠΎΠ»ΠΈΡΠΈΠΊΠ΅ Π½Π΅ΠΉΠΌΠΈΠ½Π³Π°;
- Π°ΡΠ³ΡΠΌΠ΅Π½ΡΡ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡΠ΅ Π΄Π»Ρ Π·Π°ΠΏΡΡΠΊΠ° ΡΠ±ΠΎΡΠΎΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°, Π³Π΄Π΅ Π² Π½Π°ΡΠ΅ΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΌΡ ΠΌΠΎΠ½ΡΠΈΡΡΠ΅ΠΌ ΡΠ΅ΠΊΡΡΡΡ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡ ΠΊΠ°ΠΊ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡ Π²Π½ΡΡΡΠΈ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°.
Π ΡΠΆΠ΅ Π² ΡΠ°Π³Π°Ρ ΡΠ±ΠΎΡΠΊΠΈ ΠΌΡ ΡΠΊΠ°Π·ΡΠ²Π°Π΅ΠΌ, ΠΊΠ°ΠΊΠΈΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΡ Π²Π½ΡΡΡΠΈ ΡΠ±ΠΎΡΠΎΡΠ½ΠΎΠ³ΠΎ Docker Π°Π³Π΅Π½ΡΠ°. ΠΡΠΎ ΠΌΠΎΠΆΠ΅Ρ Π²ΡΠ΅ ΡΡΠΎ ΡΠ³ΠΎΠ΄Π½ΠΎ, ΡΠ°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ Ρ ΡΠ°ΠΊ ΠΆΠ΅ Π·Π°ΠΏΡΡΠΊΠ°Ρ Π΄Π΅ΠΏΠ»ΠΎΠΉ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ ΠΏΡΠΈ ΠΏΠΎΠΌΠΎΡΠΈ ansible.
ΠΠΈΠΆΠ΅ Ρ Ρ ΠΎΡΡ ΠΏΠΎΠΊΠ°Π·Π°ΡΡ ΠΎΠ±ΡΠΈΠΉ Jenkinsfile, ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΠΎΠΆΠ΅Ρ ΡΠΎΠ±ΡΠ°ΡΡ ΠΏΡΠΎΡΡΠΎΠ΅ Node.JS ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅.
def DOCKER_IMAGE_BRANCH = ""
def GIT_COMMIT_HASH = ""
pipeline {
options {
buildDiscarder(
logRotator(
artifactDaysToKeepStr: "",
artifactNumToKeepStr: "",
daysToKeepStr: "",
numToKeepStr: "10"
)
)
disableConcurrentBuilds()
}
agent any
stages {
stage("Prepare build image") {
steps {
sh "docker build -f Dockerfile.build . -t project-build:${DOCKER_IMAGE_BRANCH}"
}
}
stage("Build project") {
agent {
docker {
image "project-build:${DOCKER_IMAGE_BRANCH}"
args "-v ${PWD}:/usr/src/app -w /usr/src/app"
reuseNode true
label "build-image"
}
}
steps {
sh "yarn"
sh "yarn build"
}
}
post {
always {
step([$class: "WsCleanup"])
cleanWs()
}
}
}
Π§ΡΠΎ ΠΆΠ΅ Π²ΡΡΠ»ΠΎ?
ΠΠ»Π°Π³ΠΎΠ΄Π°ΡΡ ΡΠ°ΠΊΠΎΠΌΡ ΡΠΏΠΎΡΠΎΠ±Ρ ΠΌΡ ΡΠ΅ΡΠΈΠ»ΠΈ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ:
- Π²ΡΠ΅ΠΌΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ ΡΠ±ΠΎΡΠΊΠΈ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΡΠ²ΠΎΠ΄ΠΈΡΡΡ ΠΊ 10 β 15 ΠΌΠΈΠ½ΡΡΠ°ΠΌ Π½Π° ΠΏΡΠΎΠ΅ΠΊΡ;
- ΠΏΠΎΠ»Π½ΠΎΡΡΡΡ ΠΏΠΎΠ²ΡΠΎΡΡΠ΅ΠΌΠΎΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅ ΡΠ±ΠΎΡΠΊΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΌΠΎΠΆΠ½ΠΎ ΡΠ°ΠΊ ΡΠΎΠ±ΠΈΡΠ°ΡΡ ΠΈ Π½Π° Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠΌ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ΅;
- Π½Π΅Ρ ΠΏΡΠΎΠ±Π»Π΅ΠΌ Ρ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡΠ°ΠΌΠΈ ΡΠ°Π·Π½ΡΡ Π²Π΅ΡΡΠΈΠΉ ΡΠ±ΠΎΡΠΎΡΠ½ΡΡ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠΎΠ²;
- Π²ΡΠ΅Π³Π΄Π° ΡΠΈΡΡΡΠΉ Π²ΠΎΡΠΊΡΠΏΠ΅ΠΉΡ, ΠΊΠΎΡΠΎΡΡΠΉ Π½Π΅ Π·Π°Π±ΠΈΠ²Π°Π΅ΡΡΡ.
Π‘Π°ΠΌΠΎ ΠΏΠΎ ΡΠ΅Π±Π΅ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ ΠΏΡΠΎΡΡΠΎΠ΅ ΠΈ ΠΎΡΠ΅Π²ΠΈΠ΄Π½ΠΎΠ΅ ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΠΎΠ΄Π½ΠΈ ΠΏΠ»ΡΡΡ. ΠΠ°, ΠΏΠΎΡΠΎΠ³ Π²Ρ ΠΎΠ΄Π° ΡΡΡΠΎΡΠΊΡ ΠΏΠΎΠ΄Π½ΡΠ»ΡΡ ΠΏΠΎ ΡΡΠ°Π²Π½Π΅Π½ΠΈΡ Ρ ΠΏΡΠΎΡΡΡΠΌΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Π°ΠΌΠΈ Π΄Π»Ρ ΡΠ±ΠΎΡΠΎΠΊ, Π½ΠΎ Π·Π°ΡΠΎ ΡΠ΅ΠΏΠ΅ΡΡ Π΅ΡΡΡ Π³Π°ΡΠ°Π½ΡΠΈΡ, ΡΡΠΎ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ±ΠΈΡΠ°ΡΡΡΡ Π²ΡΠ΅Π³Π΄Π° ΠΈ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊ ΡΠ°ΠΌ ΠΌΠΎΠΆΠ΅Ρ Π²ΡΠ±ΡΠ°ΡΡ Π²ΡΠ΅, ΡΡΠΎ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎ Π΄Π»Ρ Π΅Π³ΠΎ ΠΏΡΠΎΡΠ΅ΡΡΠ° ΡΠ±ΠΎΡΠΊΠΈ.
Π’Π°ΠΊ ΠΆΠ΅ Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΡΠΎΠ±ΡΠ°Π½Π½ΡΠΌ ΠΌΠ½ΠΎΡ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ
Π Ρ
ΠΎΠ΄Π΅ Π½Π°ΠΏΠΈΡΠ°Π½ΠΈΡ ΡΡΠ°ΡΡΠΈ ΠΏΠΎΡΠ²ΠΈΠ»Π°ΡΡ Π΄ΠΈΡΠΊΡΡΡΠΈΡ ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ Π°Π³Π΅Π½ΡΠΎΠ² Π½Π° ΡΠ΄Π°Π»Π΅Π½Π½ΡΡ
ΡΠ΅ΡΠ²Π΅ΡΠ°Ρ
, ΡΡΠΎΠ±Ρ Π½Π΅ Π³ΡΡΠ·ΠΈΡΡ ΠΌΠ°ΡΡΠ΅Ρ Π½ΠΎΠ΄Ρ ΠΏΡΠΈ ΠΏΠΎΠΌΠΎΡΠΈ ΠΏΠ»Π°Π³ΠΈΠ½Π°
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com