ΠΠ°ΠΊΡΠΎ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π²ΠΈΠ΄ΠΈΡΠ΅ ΠΎΡ ΠΏΡΠ΅Π΄ΠΈΡΠ½Π°ΡΠ° ΡΡΠ°ΡΠΈΡ, ΡΠ°Π±ΠΎΡΠΈΡ
Ρ ΡΠ°Π·Π»ΠΈΡΠ½ΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠΈ. ΠΡΡΠ²ΠΈΡΠ΅ Π΄Π½ΠΈ Π² Π½ΠΎΠ² Π΅ΠΊΠΈΠΏ ΠΎΠ±ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΎ ΠΏΡΠΎΡΠΈΡΠ°Ρ ΠΏΠΎ ΡΡΡΠΈΡ Π½Π°ΡΠΈΠ½: Π±Π΅ΠΊΠ΅Π½Π΄ΡΡΡΡ ΡΡΠ΄Π° Π΄ΠΎ ΠΌΠ΅Π½ ΠΈ ΠΈΠ·ΠΏΡΠ»Π½ΡΠ²Π° ΠΌΠ°Π³ΠΈΡΠ΅ΡΠΊΠΈΡΠ΅ ΡΡΡΠΏΠΊΠΈ Π·Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ°Π½Π΅ ΠΈ Π²Π½Π΅Π΄ΡΡΠ²Π°Π½Π΅ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ. Docker Π΅ Π½Π΅Π·Π°ΠΌΠ΅Π½ΠΈΠΌ Π·Π° front-end ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΡΠΈΡΠ΅, Ρ.ΠΊ. ΠΠ΅ΠΊΠ΅Π½Π΄ΡΡ ΡΠ΅ΡΡΠΎ Π΅ Π½Π°ΠΏΠΈΡΠ°Π½ Π² ΡΠΈΡΠΎΠΊ Π½Π°Π±ΠΎΡ ΠΎΡ PHP/Java/Python/C# ΡΡΠ΅ΠΊΠΎΠ²Π΅ ΠΈ ΠΏΡΠ΅Π΄Π½Π°ΡΠ° ΡΠ°ΡΡ Π½Π΅ ΡΡΡΠ±Π²Π° Π΄Π° ΠΎΡΠ²Π»ΠΈΡΠ° Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅ΡΠΎ Π½Π° Π·Π°Π΄Π½Π°ΡΠ° Π²ΡΠ΅ΠΊΠΈ ΠΏΡΡ, Π·Π° Π΄Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° ΠΈ Π²Π½Π΅Π΄ΡΠΈ Π²ΡΠΈΡΠΊΠΎ. Π‘Π°ΠΌΠΎ Π½Π° Π΅Π΄Π½ΠΎ ΠΌΡΡΡΠΎ Π²ΠΈΠ΄ΡΡ
ΠΊΡΠΏ Docker-Jenkins Ρ ΠΏΡΠΎΠ·ΡΠ°ΡΠ½ΠΎ ΡΠ°Π·Π³ΡΡΡΠ°Π½Π΅, Π»ΠΎΠ³ΠΎΠ²Π΅, ΠΏΡΠ΅ΡΠ°ΠΊΠ°Π½ΠΈ Ρ Π°Π²ΡΠΎΡΠ΅ΡΡΠΎΠ²Π΅.
ΠΠ° docker ΡΠ° Π½Π°ΠΏΠΈΡΠ°Π½ΠΈ ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΈ ΡΡΠ°ΡΠΈΠΈ. Π’Π°Π·ΠΈ ΡΡΠ°ΡΠΈΡ ΡΠ΅ ΡΠ΅ ΡΠΎΠΊΡΡΠΈΡΠ° Π²ΡΡΡ
Ρ Π²Π½Π΅Π΄ΡΡΠ²Π°Π½Π΅ΡΠΎ Π½Π° Π΅Π΄Π½ΠΎΡΡΡΠ°Π½ΠΈΡΠ½ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Ρ ΠΏΠΎΠΌΠΎΡΡΠ° Π½Π° VueJS / Vue Router, ΡΡΡΠ²ΡΡΠ½Π°ΡΠ° ΡΠ°ΡΡ ΠΏΠΎΠ΄ ΡΠΎΡΠΌΠ°ΡΠ° Π½Π° RESTful API Ρ NodeJS, Π° MongoDB ΡΠ΅ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° ΠΊΠ°ΡΠΎ Π±Π°Π·Π° Π΄Π°Π½Π½ΠΈ. Docker Compose ΡΠ΅ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° Π·Π° ΠΎΠΏΠΈΡΠ²Π°Π½Π΅ ΠΈ ΠΈΠ·ΠΏΡΠ»Π½Π΅Π½ΠΈΠ΅ Π½Π° ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ½ΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
ΠΠ°ΡΠΎ ΠΈΠΌΠ°ΡΠ΅ Π½ΡΠΆΠ΄Π° ΠΎΡ Docker
Docker Π²ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ²Π° Π΄Π° Π°Π²ΡΠΎΠΌΠ°ΡΠΈΠ·ΠΈΡΠ°ΡΠ΅ ΠΏΡΠΎΡΠ΅ΡΠ° Π½Π° Π²Π½Π΅Π΄ΡΡΠ²Π°Π½Π΅ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅. Π Π°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΡΡ Π²Π΅ΡΠ΅ Π½Π΅ ΡΡΡΠ±Π²Π° ΡΠ°ΠΌ Π΄Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΈ, Π΄Π° ΡΠ΅ ΡΠΏΡΠ°Π²Ρ Ρ Π½Π΅ΡΡΠ²ΠΌΠ΅ΡΡΠΈΠΌΠΎΡΡΡΠ° Π½Π° Π²Π΅ΡΡΠΈΠΈΡΠ΅ Π½Π° ΡΠ²ΠΎΡΡΠ° ΠΌΠ°ΡΠΈΠ½Π°. ΠΠΎΡΡΠ°ΡΡΡΠ½ΠΎ Π΅ Π΄Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ°ΡΠ΅ Docker ΠΈ Π΄Π° Π²ΡΠ²Π΅Π΄Π΅ΡΠ΅ 1-2 ΠΊΠΎΠΌΠ°Π½Π΄ΠΈ Π² ΠΊΠΎΠ½Π·ΠΎΠ»Π°ΡΠ°. ΠΠ°ΠΉ-ΡΠ΄ΠΎΠ±Π½ΠΎ Π΅ Π΄Π° Π½Π°ΠΏΡΠ°Π²ΠΈΡΠ΅ ΡΠΎΠ²Π° Π½Π° Linux.
ΠΡΡΠ²ΠΈ ΡΡΡΠΏΠΊΠΈ
ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ°ΠΌ
Π‘ΡΡΡΠΊΡΡΡΠ° Π½Π° ΠΏΠ°ΠΏΠΊΠ°ΡΠ°
Π‘ΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ 2 ΠΏΠ°ΠΏΠΊΠΈ Π·Π° ΠΊΠ»ΠΈΠ΅Π½ΡΡΠΊΠΈ ΠΈ ΡΡΡΠ²ΡΡΠ½ΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ. Π€Π°ΠΉΠ» Ρ ΡΠ°Π·ΡΠΈΡΠ΅Π½ΠΈΠ΅ .yml Π΅ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ ΠΠΎΠΊΠ΅Ρ ΠΠΎΠΌΠΏΠΎΠ·ΠΈΡΠ°Π½Π΅ΠΊΡΠ΄Π΅ΡΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈΡΠ΅ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ ΡΠ° Π΄Π΅ΡΠΈΠ½ΠΈΡΠ°Π½ΠΈ ΠΈ ΡΠ²ΡΡΠ·Π°Π½ΠΈ.
docker-compose.yml:
version: "3"
services:
mongo:
container_name: mongo
hostname: mongo
image: mongo
ports:
- "27017:27017"
server:
build: server/
#command: node ./server.js #Π·Π΄Π΅ΡΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π·Π°ΠΏΠΈΡΠ°ΡΡ CMD ΠΈΠ· Dockerfile Π² /server
ports:
- "3000:3000"
links:
- mongo
client:
build: client/
#command: http-server ./dist #Π·Π΄Π΅ΡΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π·Π°ΠΏΠΈΡΠ°ΡΡ CMD ΠΈΠ· Dockerfile Π² /client
network_mode: host
ports:
- "8089:8089"
depends_on:
- server
Π‘ΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ 3 ΡΡΠ»ΡΠ³ΠΈ Π² docker: Π·Π° NodeJS, MongoDB ΠΈ Π·Π° ΡΡΠ°ΡΠΈΠΊΠ° Π²ΡΠ² Vue. ΠΠΎΠ±Π°Π²Π΅Π½ΠΎ Π΅ ΡΠ²ΡΡΠ·Π²Π°Π½Π΅ Π½Π° ΠΊΠ»ΠΈΠ΅Π½Ρ ΠΊΡΠΌ ΡΡΡΠ²ΡΡ Π·Π°Π²ΠΈΡΠΈ ΠΎΡ ΡΡΡΠ²ΡΡΠ°. ΠΠ° Π΄Π° ΡΠ²ΡΡΠΆΠ΅ΡΠ΅ MongoDB Ρ API Π½Π° ΡΡΡΠ²ΡΡΠ°, ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°ΠΉΡΠ΅ Π²ΡΡΠ·ΠΊΠΈ mongo. Π‘ΡΡΠ²ΡΡ, ΠΊΠ»ΠΈΠ΅Π½Ρ, ΠΌΠΎΠ½Π³ΠΎ ΡΠ° ΠΈΠΌΠ΅Π½Π°ΡΠ° Π½Π° ΡΡΠ»ΡΠ³ΠΈΡΠ΅.
VueJS ΠΊΠ»ΠΈΠ΅Π½Ρ
Π ΠΏΠ°ΠΏΠΊΠ° /ΠΊΠ»ΠΈΠ΅Π½Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ ΡΠ΅ Π½Π°ΠΌΠΈΡΠ° Π½Π° VueJS. ΠΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅, ΡΡΠ·Π΄Π°Π΄Π΅Π½ΠΎ Ρ
FROM node:10
WORKDIR /client
COPY ./package*.json ./
RUN npm install
RUN npm install -g http-server
COPY . .
RUN npm run build
EXPOSE 8081
CMD ["npm", "test:dev"]
ΠΠΌΠ°ΠΉΡΠ΅ ΠΏΡΠ΅Π΄Π²ΠΈΠ΄, ΡΠ΅ package.json ΡΠ΅ ΠΊΠΎΠΏΠΈΡΠ° ΠΈ ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° ΠΎΡΠ΄Π΅Π»Π½ΠΎ ΠΎΡ ΠΎΡΡΠ°Π½Π°Π»ΠΈΡΠ΅ ΡΠ°ΠΉΠ»ΠΎΠ²Π΅ Π½Π° ΠΏΡΠΎΠ΅ΠΊΡΠ°. Π’ΠΎΠ²Π° ΡΠ΅ ΠΏΡΠ°Π²ΠΈ Π·Π° ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»Π½ΠΎΡΡ, ΡΠ°ΠΊΠ° ΡΠ΅ ΡΡΠ΄ΡΡΠΆΠ°Π½ΠΈΠ΅ΡΠΎ Π½Π° ΠΏΠ°ΠΏΠΊΠ°ΡΠ° /node_modules Π΄Π° ΡΠ΅ ΠΊΠ΅ΡΠΈΡΠ° ΠΏΡΠΈ ΠΏΠΎΠ²ΡΠΎΡΠ½ΠΎ ΠΈΠ·Π³ΡΠ°ΠΆΠ΄Π°Π½Π΅. ΠΡΠ΅ΠΊΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Π΅Π½ ΡΠ΅Π΄ ΡΠ΅ ΠΊΠ΅ΡΠΈΡΠ° ΠΎΡΠ΄Π΅Π»Π½ΠΎ.
Π ΠΊΡΠ°Ρ, ΠΊΠΎΠ³Π°ΡΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΡΡ ΡΠ΅ ΡΡΠ°ΡΡΠΈΡΠ°, ΠΊΠΎΠΌΠ°Π½Π΄Π°ΡΠ° ΡΠ΅ ΠΈΠ·ΠΏΡΠ»Π½ΡΠ²Π° npm run dev
. Π’Π°Π·ΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Π° Π΅ ΠΎΠΏΠΈΡΠ°Π½Π° Π² package.json:
"scripts": {
"test:dev": "http-server dist -p 8081 -c 1 --push-state"
}
ΠΠ° ΡΡΠ°ΡΡΠΈΡΠ°Π½Π΅ Π½Π° ΡΠ°ΠΉΠ»ΠΎΠ²Π΅ ΠΎΡ ΠΏΠ°ΠΏΠΊΠ° /Π΄ΠΈΡΡ, Π³Π»ΠΎΠ±Π°Π»Π½ΠΎ Π·Π°Π΄Π°Π΄Π΅Π½ http-server
ΠΈ Π² ΠΏΠ°ΠΊΠ΅ΡΠ° dev-dependencies spa-http-server
Π·Π° Π΄Π° Π½Π°ΠΊΠ°ΡΠ°ΡΠ΅ Vue Router Π΄Π° ΡΠ°Π±ΠΎΡΠΈ ΠΏΡΠ°Π²ΠΈΠ»Π½ΠΎ. Π€Π»Π°Π³ΡΡ --push-state ΠΏΡΠ΅Π½Π°ΡΠΎΡΠ²Π° ΠΊΡΠΌ index.html. ΠΠΎΠ±Π°Π²Π΅Π½ Π΅ ΡΠ»Π°Π³ΡΡ -c ΡΡΡ ΡΡΠΎΠΉΠ½ΠΎΡΡ 1 ΡΠ΅ΠΊΡΠ½Π΄Π° http-ΡΡΡΠ²ΡΡ Π½Π΅ ΠΊΠ΅ΡΠΈΡΠ° ΡΠΊΡΠΈΠΏΡΠΎΠ²Π΅. Π’ΠΎΠ²Π° Π΅ ΡΠ΅ΡΡΠΎΠ² ΠΏΡΠΈΠΌΠ΅Ρ, Π² ΡΠ΅Π°Π»Π΅Π½ ΠΏΡΠΎΠ΅ΠΊΡ Π΅ ΠΏΠΎ-Π΄ΠΎΠ±ΡΠ΅ Π΄Π° ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°ΡΠ΅ nginx.
Π‘ΡΠ·Π΄Π°ΠΉΡΠ΅ ΠΏΠΎΠ»Π΅ Π² ΠΌΠ°Π³Π°Π·ΠΈΠ½Π° Π½Π° Vuex apiHost: 'http://localhost:3000'
, ΠΊΡΠ΄Π΅ΡΠΎ Π΅ Π½Π°ΠΏΠΈΡΠ°Π½ NodeJS Api ΠΏΠΎΡΡ. ΠΠ»ΠΈΠ΅Π½ΡΡΠΊΠ°ΡΠ° ΡΠ°ΡΡ Π΅ Π³ΠΎΡΠΎΠ²Π°. Π‘Π΅Π³Π° Π²ΡΠΈΡΠΊΠΈ Π·Π°ΡΠ²ΠΊΠΈ ΠΎΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° ΠΊΡΠΌ Π·Π°Π΄Π½Π°ΡΠ° ΡΠ°ΡΡ ΠΎΡΠΈΠ²Π°Ρ Π½Π° ΡΠΎΠ·ΠΈ url.
NodeJS Server API
Π ΠΏΠ°ΠΏΠΊΠ° /server
ΡΡΠ·Π΄Π°Π²Π°ΠΉ server.js ΠΈ Dockerfile:
FROM node:10
WORKDIR /server
COPY ./package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Π server.js ΠΏΠΎΡΠΎΡΠ΅Π½ΠΈ URL Π·Π° Π±Π°Π·Π° Π΄Π°Π½Π½ΠΈ const url = 'mongodb://mongo:27017/';
. Π Π°Π·ΡΠ΅ΡΠ°Π²Π°ΠΌΠ΅ ΠΌΠ΅ΠΆΠ΄ΡΠ΄ΠΎΠΌΠ΅ΠΉΠ½ Π·Π°ΡΠ²ΠΊΠΈ ΠΎΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°:
const clientUrl = 'http://localhost:8081';
const corsOptions = {
origin: clientUrl,
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.use(cors());
app.all('/*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', clientUrl);
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
next();
});
app.get('/getProducts', cors(corsOptions), (req, res) => {
products.getContent
.then(data => res.json(data), err => res.json(err));
});
app.get('/getUsers', cors(corsOptions), (req, res) => {
db.getUsers()
.then(data => res.json(data), err => res.json(err));
});
ΠΠ°ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅
Π‘Π΅Π³Π° ΠΎΡΠΈΠ΄Π΅ΡΠ΅ Π² Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡΡΠ° Π½Π° ΠΏΡΠΎΠ΅ΠΊΡΠ° ΠΈ ΡΡΠ°ΡΡΠΈΡΠ°ΠΉΡΠ΅ docker-compose build
Π·Π° ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ ΠΈ docker-compose up
Π·Π° ΠΏΡΡΠΊΠ°Π½Π΅ Π½Π° ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈ. ΠΠΎΠΌΠ°Π½Π΄Π°ΡΠ° ΡΠ΅ ΠΏΠΎΠ²Π΄ΠΈΠ³Π½Π΅ 3 ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°: ΡΡΡΠ²ΡΡ, ΠΊΠ»ΠΈΠ΅Π½Ρ, mongo. ΠΠ° NodeJS ΡΡΡΠ²ΡΡ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄Π° ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠΈΡΠ°ΡΠ΅ Π³ΠΎΡΠ΅ΡΠΎ ΠΏΡΠ΅Π·Π°ΡΠ΅ΠΆΠ΄Π°Π½Π΅, ΠΊΠ°ΡΠΎ Π³ΠΎ ΡΠ²ΡΡΠΆΠ΅ΡΠ΅ ΠΊΡΠΌ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»ΡΠΊΠ°ΡΠ° ΠΏΠ°ΠΏΠΊΠ°. Π ΠΊΠ»ΠΈΠ΅Π½ΡΡΡ Π΅ Π² ΠΏΡΠΎΡΠ΅Ρ Π½Π° ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ°, Π·Π° Π΄Π° ΡΠ°Π±ΠΎΡΠΈ Π»ΠΎΠΊΠ°Π»Π½ΠΎ Ρ Π³ΠΎΡΠ΅ΡΠΎ ΠΏΡΠ΅Π·Π°ΡΠ΅ΠΆΠ΄Π°Π½Π΅, ΠΊΠ°ΡΠΎ ΡΠ°Π±ΠΎΡΠΈ ΠΎΡΠ΄Π΅Π»Π½ΠΎ ΡΡΡΠ²ΡΡ ΠΈ ΠΠΎΠ½Π³ΠΎ. ΠΠ° Π΄Π° ΡΡΠ°ΡΡΠΈΡΠ°ΡΠ΅ ΠΎΡΠ΄Π΅Π»Π½Π° ΡΡΠ»ΡΠ³Π°, ΠΏΡΠΎΡΡΠΎ ΠΏΠΎΡΠΎΡΠ΅ΡΠ΅ Π½Π΅ΠΉΠ½ΠΎΡΠΎ ΠΈΠΌΠ΅ docker-compose up client
. ΠΠ΅ Π·Π°Π±ΡΠ°Π²ΡΠΉΡΠ΅ Π΄Π° Π³ΠΎ ΠΏΡΠ°Π²ΠΈΡΠ΅ ΠΏΠΎΠ½ΡΠΊΠΎΠ³Π° prune
ΠΈ ΠΏΡΠ΅ΠΌΠ°Ρ
Π²Π°Π½Π΅ Π½Π° ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈ (ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈ), ΠΌΡΠ΅ΠΆΠΈ (ΠΌΡΠ΅ΠΆΠΈ) ΠΈ ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ (ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ), Π·Π° Π΄Π° ΡΠ΅ ΠΎΡΠ²ΠΎΠ±ΠΎΠ΄ΡΡ ΡΠ΅ΡΡΡΡΠΈ.
ΠΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π²ΠΈΠ΄ΠΈΡΠ΅ ΠΏΡΠ»Π½ΠΈΡ ΠΊΠΎΠ΄
ΠΠ·ΡΠΎΡΠ½ΠΈΠΊ: www.habr.com