Ai experimentat vreodată cu cod sau utilitare de sistem în Linux Ca să nu fie nevoie să vă faceți griji cu privire la sistemul de bază și să distrugeți totul dacă există o eroare în codul care trebuie să ruleze cu privilegii de root?
Dar cum rămâne cu faptul că să presupunem că trebuie să testați sau să rulați un întreg cluster de diverse microservicii pe o singură mașină? O sută sau chiar o mie?
Mașinile virtuale gestionate de hypervisor ar putea rezolva astfel de probleme, dar cu ce cost? De exemplu, un container LXD bazat pe distribuția Alpine Linux consumă la minimum din toate 7.60MB RAM și unde ocupă partiția rădăcină după pornire 9.5MB! Cum îți place asta, Elon Musk? Recomand să verificați capacități de bază ale LXD - un sistem de containere în Linux
După ce a devenit clar ce sunt containerele LXD, să mergem mai departe și să ne gândim, ce ar fi dacă ar exista o astfel de platformă de recoltat unde ai putea rula codul în siguranță pentru gazdă, ai putea genera grafice, a lega dinamic (interactiv) widget-urile UI cu codul tău, completati codul cu text cu blackjack... formatare? Un fel de blog interactiv? Wow... o vreau! Vrei! 🙂
Uită-te sub pisică unde vom lansa într-un container laborator jupyter - următoarea generație de interfață cu utilizatorul în locul notebook-ului Jupyter învechit și vom instala și module Python, cum ar fi NumPy, ursi panda, matplotlib, IPyWidgets ceea ce vă va permite să faceți tot ce este enumerat mai sus și să le salvați într-un fișier special - un laptop IPython.

Navigare
- Planul de decolare orbital
- Instalarea și configurarea sistemului de bază
- Instalarea software-ului de bază și configurarea sistemului
- Instalarea și configurarea JupyterLab
- Partajarea datelor cu gazda
- Salut Lume!
- Extinderea capabilităților Python
- Testarea modulelor în JupyterLab
- Ce altceva?
Planul de decolare orbital ^

Să schițăm un scurt plan de acțiune pentru a ne ușura implementarea schemei de mai sus:
- Să instalăm și să lansăm un container bazat pe kitul de distribuție alpin Linux. Vom folosi această distribuție pentru că vizează minimalismul și vom instala în ea doar cel mai necesar software, nimic de prisos.
- Să adăugăm un disc virtual suplimentar în container și să-i dăm un nume -
hostfsși montați-l pe sistemul de fișiere rădăcină. Acest disc va face posibilă utilizarea fișierelor pe gazdă dintr-un anumit director din interiorul containerului. Astfel, datele noastre vor fi independente de container. Dacă containerul este șters, datele vor rămâne pe gazdă. De asemenea, această schemă este utilă pentru partajarea acelorași date între mai multe containere fără a utiliza mecanismele de rețea standard ale distribuției de containere. - Să instalăm Bash, sudo, bibliotecile necesare, să adăugăm și să configurem un utilizator de sistem
- Să instalăm Python, module și să compilăm dependențe binare pentru ele
- Hai să instalăm și să lansăm laborator jupyter, personalizați aspectul, instalați extensii pentru acesta.
În acest articol vom începe cu lansarea containerului, nu vom lua în considerare instalarea și configurarea LXD, toate acestea le puteți găsi într-un alt articol - Capacitățile de bază ale LXD, un sistem de containere în Linux.
Instalarea și configurarea sistemului de bază ^
Creăm un container cu comanda în care specificăm imaginea - alpine3, identificator pentru container - jupyterlab și, dacă este necesar, profiluri de configurare:
lxc init alpine3 jupyterlab --profile=default --profile=hddrootAici folosesc un profil de configurare hddroot care specifică crearea unui container cu o partiție rădăcină în Bazin de depozitare situat pe un disc HDD fizic:
lxc profile show hddroot
config: {}
description: ""
devices:
root:
path: /
pool: hddpool
type: disk
name: hddroot
used_by: []lxc storage show hddpool
config:
size: 10GB
source: /dev/loop1
volatile.initial_source: /dev/loop1
description: ""
name: hddpool
driver: btrfs
used_by:
- /1.0/images/ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
- /1.0/profiles/hddroot
status: Created
locations:
- noneAcest lucru îmi oferă posibilitatea de a experimenta cu containere de pe discul HDD, salvând resursele discului SSD, care este disponibil și în sistemul meu 🙂 pentru care am creat un profil de configurare separat ssdroot.
După ce containerul este creat, acesta este în stare STOPPED, așa că trebuie să-l pornim rulând sistemul init în el:
lxc start jupyterlabSă afișăm o listă de containere în LXD folosind tasta -c care indică care cAfișează coloane:
lxc list -c ns4b
+------------+---------+-------------------+--------------+
| NAME | STATE | IPV4 | STORAGE POOL |
+------------+---------+-------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.198 (eth0) | hddpool |
+------------+---------+-------------------+--------------+La crearea containerului, adresa IP a fost aleasă aleatoriu, deoarece am folosit un profil de configurare default care a fost configurat anterior în articol Capacitățile de bază ale LXD, un sistem de containere în Linux.
Vom schimba această adresă IP cu una mai memorabilă prin crearea unei interfețe de rețea la nivel de container, și nu la nivelul profilului de configurare, așa cum este acum în configurația curentă. Nu trebuie să faci asta, poți sări peste el.
Crearea unei interfețe de rețea eth0 pe care îl conectăm la comutator (punte de rețea) lxdbr0 în care am activat NAT conform articolului anterior și containerul va avea acum acces la Internet și atribuim, de asemenea, o adresă IP statică interfeței - 10.0.5.5:
lxc config device add jupyterlab eth0 nic name=eth0 nictype=bridged parent=lxdbr0 ipv4.address=10.0.5.5După adăugarea unui dispozitiv, containerul trebuie repornit:
lxc restart jupyterlabVerificarea stării containerului:
lxc list -c ns4b
+------------+---------+------------------+--------------+
| NAME | STATE | IPV4 | STORAGE POOL |
+------------+---------+------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.5 (eth0) | hddpool |
+------------+---------+------------------+--------------+Instalarea software-ului de bază și configurarea sistemului ^
Pentru a administra containerul nostru, trebuie să instalați următorul software:
Pachet
Descriere
pocni
Shell-ul GNU Bourne Again
bash-completare
Finalizare programabilă pentru shell-ul bash
sudo
Oferiți anumitor utilizatori posibilitatea de a rula unele comenzi ca root
umbră
Suită de instrumente de gestionare a parolelor și a contului cu suport pentru fișiere umbră și PAM
tzdata
Surse pentru date privind fusul orar și ora de vară
nano
Pico editor clonă cu îmbunătățiri
În plus, puteți instala suport în paginile de manual de sistem instalând următoarele pachete − man man-pages mdocml-apropos less
lxc exec jupyterlab -- apk add bash bash-completion sudo shadow tzdata nanoSă ne uităm la comenzile și tastele pe care le-am folosit:
lxc— Apelați clientul LXDexec- Metoda client LXD care rulează o comandă în containerjupyterlab- ID container--— O cheie specială care specifică să nu se interpreteze alte chei ca chei pentrulxcși treceți restul sforii așa cum este în recipientapk— Manager de pachete de distribuție Alpine Linuxadd— O metodă de gestionare a pachetelor care instalează pachetele specificate după comandă
În continuare, vom seta un fus orar în sistem Europe/Moscow:
lxc exec jupyterlab -- cp /usr/share/zoneinfo/Europe/Moscow /etc/localtimeDupă instalarea fusului orar, pachetul tzdata nu mai este necesar în sistem, va ocupa spațiu, așa că să-l ștergem:
lxc exec jupyterlab -- apk del tzdataVerificarea fusului orar:
lxc exec jupyterlab -- date
Wed Apr 15 10:49:56 MSK 2020Pentru a economisi timp la configurarea Bash pentru noii utilizatori din container, vom copia fișierele skel predefinite din sistemul gazdă în acesta. Acest lucru ne va permite să personalizăm Bash în container în mod interactiv. Sistemul meu gazdă este Manjaro. Linux și fișiere copiate /etc/skel/.bash_profile, /etc/skel/.bashrc, /etc/skel/.dir_colors În principiu, se potrivesc cu Alpine Linux și nu cauzează probleme critice, dar este posibil să aveți o distribuție diferită și va trebui să descoperiți singur dacă există o eroare la rularea Bash în container.
Copiați fișierele skel în container. Cheie --create-dirs va crea directoarele necesare dacă acestea nu există:
lxc file push /etc/skel/.bash_profile jupyterlab/etc/skel/.bash_profile --create-dirs
lxc file push /etc/skel/.bashrc jupyterlab/etc/skel/.bashrc
lxc file push /etc/skel/.dir_colors jupyterlab/etc/skel/.dir_colorsPentru un utilizator root deja existent, copiați fișierele skel tocmai copiate în container în directorul principal:
lxc exec jupyterlab -- cp /etc/skel/.bash_profile /root/.bash_profile
lxc exec jupyterlab -- cp /etc/skel/.bashrc /root/.bashrc
lxc exec jupyterlab -- cp /etc/skel/.dir_colors /root/.dir_colorsÎn Alpin Linux o shell de sistem este instalată pentru utilizatori /bin/sh, îl vom înlocui cu root utilizator în Bash:
lxc exec jupyterlab -- usermod --shell=/bin/bash rootCă root utilizatorul nu a fost fără parolă, trebuie să seteze o parolă. Următoarea comandă va genera și seta o nouă parolă aleatorie pentru el, pe care o veți vedea pe ecranul consolei după executarea acesteia:
lxc exec jupyterlab -- /bin/bash -c "PASSWD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12); echo "root:$PASSWD" | chpasswd && echo "New Password: $PASSWD""
New Password: sFiXEvBswuWADe asemenea, să creăm un nou utilizator de sistem - jupyter pentru care vom configura ulterior laborator jupyter:
lxc exec jupyterlab -- useradd --create-home --shell=/bin/bash jupyterSă generăm și să setăm o parolă pentru aceasta:
lxc exec jupyterlab -- /bin/bash -c "PASSWD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12); echo "jupyter:$PASSWD" | chpasswd && echo "New Password: $PASSWD""
New Password: ZIcbzWrF8tkiÎn continuare, vom executa două comenzi, prima va crea un grup de sistem sudo, iar al doilea va adăuga un utilizator la acesta jupyter:
lxc exec jupyterlab -- groupadd --system sudo
lxc exec jupyterlab -- groupmems --group sudo --add jupyterSă vedem la ce grupuri aparține utilizatorul jupyter:
lxc exec jupyterlab -- id -Gn jupyter
jupyter sudoTotul e ok, hai sa mergem mai departe.
Permiteți tuturor utilizatorilor care sunt membri ai grupului sudo folosește comanda sudo. Pentru a face acest lucru, rulați următorul script, unde sed decommentează linia parametrilor din fișierul de configurare /etc/sudoers:
lxc exec jupyterlab -- /bin/bash -c "sed --in-place -e '/^#[ t]*%sudo[ t]*ALL=(ALL)[ t]*ALL$/ s/^[# ]*//' /etc/sudoers"Instalarea și configurarea JupyterLab ^
laborator jupyter este o aplicație Python, așa că trebuie mai întâi să instalăm acest interpret. De asemenea, laborator jupyter vom instala folosind managerul de pachete Python pip, și nu cel de sistem, deoarece poate fi învechit în depozitul de sistem și, prin urmare, trebuie să rezolvăm manual dependențele pentru acesta instalând următoarele pachete - python3 python3-dev gcc libc-dev zeromq-dev:
lxc exec jupyterlab -- apk add python3 python3-dev gcc libc-dev zeromq-devSă actualizăm modulele Python și managerul de pachete pip la versiunea curentă:
lxc exec jupyterlab -- python3 -m pip install --upgrade pip setuptools wheelSet laborator jupyter prin managerul de pachete pip:
lxc exec jupyterlab -- python3 -m pip install jupyterlabDin moment ce extensiile în laborator jupyter sunt experimentale și nu sunt livrate oficial cu pachetul jupyterlab, așa că trebuie să îl instalăm și să îl configuram manual.
Să instalăm NodeJS și managerul de pachete pentru acesta - NPM, de atunci laborator jupyter le folosește pentru extensiile sale:
lxc exec jupyterlab -- apk add nodejs npmLa extensii pentru laborator jupyter pe care le vom instala au funcționat, acestea trebuie instalate în directorul utilizatorului deoarece aplicația va fi lansată de la utilizator jupyter. Problema este că nu există niciun parametru în comanda de lansare care să poată fi transmis într-un director; aplicația acceptă doar o variabilă de mediu și de aceea trebuie să o definim. Pentru a face acest lucru, vom scrie comanda de export variabilă JUPYTERLAB_DIR în mediul utilizatorului jupyter, la dosar .bashrccare se execută de fiecare dată când utilizatorul se conectează:
lxc exec jupyterlab -- su -l jupyter -c "echo -e "nexport JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab" >> .bashrc"Următoarea comandă va instala o extensie specială - manager de extensii în laborator jupyter:
lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager"Acum totul este gata pentru prima lansare laborator jupyter, dar putem instala în continuare câteva extensii utile:
toc— Cuprins, generează o listă de titluri într-un articol/caietjupyterlab-horizon-theme- Tema interfeței de utilizarejupyterlab_neon_theme- Tema interfeței de utilizarejupyterlab-ubu-theme- Încă unul tema de la autor acest articol :) Dar în acest caz, instalarea din depozitul GitHub va fi afișată
Deci, executați următoarele comenzi secvenţial pentru a instala aceste extensii:
lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyterlab/toc @mohirio/jupyterlab-horizon-theme @yeebc/jupyterlab_neon_theme"lxc exec jupyterlab -- su -l jupyter -c "wget -c https://github.com/microcoder/jupyterlab-ubu-theme/archive/master.zip"lxc exec jupyterlab -- su -l jupyter -c "unzip -q master.zip && rm master.zip"lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build jupyterlab-ubu-theme-master"lxc exec jupyterlab -- su -l jupyter -c "rm -r jupyterlab-ubu-theme-master"După instalarea extensiilor, trebuie să le compilam, deoarece anterior, în timpul instalării, am specificat cheia --no-build pentru a salva timp. Acum vom accelera în mod semnificativ, compilându-le împreună dintr-o singură mișcare:
lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter lab build"Acum rulați următoarele două comenzi pentru a o rula pentru prima dată laborator jupyter. Ar fi posibil să o lansați cu o singură comandă, dar în acest caz, comanda de lansare, care este greu de reținut în minte, va fi amintită prin bash în container, și nu pe gazdă, unde există deja suficiente comenzi sa le inregistrez in istorie :)
Conectați-vă la container ca utilizator jupyter:
lxc exec jupyterlab -- su -l jupyterApoi, fugi laborator jupyter cu cheile și parametrii indicați:
[jupyter@jupyterlab ~]$ jupyter lab --ip=0.0.0.0 --no-browserAccesați adresa din browserul dvs. web http://10.0.5.5:8888 iar pe pagina care se deschide intra semn acces pe care îl veți vedea în consolă. Copiați și inserați-l pe pagină, apoi faceți clic Intră în cont. După autentificare, accesați meniul de extensii din stânga, așa cum se arată în figura de mai jos, unde vi se va solicita, la activarea managerului de extensii, să vă asumați riscuri de securitate instalând extensii de la terți pentru care comanda Dezvoltare JupyterLab nu este responsabil:

Cu toate acestea, izolăm întregul laborator jupyter și plasați-l într-un container astfel încât extensiile terțe care necesită și folosesc NodeJS să nu poată fura măcar date de pe disc, altele decât cele pe care le deschidem în interiorul containerului. Accesați documentele dvs. private pe gazdă în /home este puțin probabil ca procesele din container să reușească, iar dacă o reușesc, atunci trebuie să aveți privilegii asupra fișierelor din sistemul gazdă, deoarece rulăm containerul în modul neprivilegiat. Pe baza acestor informații, puteți evalua riscul includerii extensiilor în laborator jupyter.
S-au creat notebook-uri IPython (pagini în laborator jupyter) va fi acum creat în directorul principal al utilizatorului - /home/jupyter, dar planurile noastre sunt să împărțim datele (partajarea) între gazdă și container, așa că reveniți la consolă și opriți laborator jupyter prin executarea tastei rapide - CTRL+C si raspunzand y la cerere. Apoi închideți sesiunea interactivă a utilizatorului jupyter completând o tastă rapidă CTRL+D.
Partajarea datelor cu gazda ^
Pentru a partaja date cu gazda, trebuie să creați un dispozitiv în container care vă permite să faceți acest lucru și pentru a face acest lucru, rulați următoarea comandă unde specificăm următoarele chei:
lxc config device add— Comanda adaugă configurația dispozitivuluijupyter— ID-ul containerului la care se adaugă configurațiahostfs- Identificatorul dispozitivului. Puteți seta orice nume.disk— Este indicat tipul de dispozitivpath— Specifică calea din container pe care LXD va monta acest dispozitivsource— Specificați sursa, calea către directorul de pe gazdă pe care doriți să-l partajați cu containerul. Specificați calea în funcție de preferințele dvs
lxc config device add jupyterlab hostfs disk path=/mnt/hostfs source=/home/dv/projects/ipython-notebooksPentru catalog /home/dv/projects/ipython-notebooks permisiunea trebuie setată pentru utilizatorul containerului care are în prezent un UID egal cu SubUID + UID, vezi capitolul Siguranță. Privilegii container în articol Capacitățile de bază ale LXD, un sistem de containere în Linux.
Setați permisiunea pe gazdă, unde proprietarul va fi utilizatorul containerului jupyter, și variabila $USER va specifica utilizatorul gazdă ca grup:
sudo chown 1001000:$USER /home/dv/projects/ipython-notebooksSalut Lume! ^
Dacă mai aveți o sesiune de consolă deschisă în container cu laborator jupyter, apoi reporniți-l cu o cheie nouă --notebook-dir prin setarea valorii /mnt/hostfs ca cale către rădăcina laptopurilor din containerul pentru dispozitivul pe care l-am creat în pasul anterior:
jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfsApoi mergi la pagina http://10.0.5.5:8888 și creează primul tău laptop făcând clic pe butonul de pe pagină, așa cum se arată în imaginea de mai jos:

Apoi, în câmpul din pagină, introduceți codul Python care va afișa clasicul Hello World!. Când ați terminat de introdus, apăsați CTRL+ENTER sau butonul „play” din bara de instrumente din partea de sus pentru ca JupyterLab să facă acest lucru:

În acest moment, aproape totul este gata de utilizare, dar va fi neinteresant dacă nu instalăm module Python suplimentare (aplicații cu drepturi depline) care pot extinde semnificativ capacitățile standard ale Python în laborator jupyter, deci, hai sa mergem mai departe :)
PS Lucrul interesant este că vechea implementare jupyter sub nume de cod Jupiter Notebook nu a dispărut și există în paralel cu laborator jupyter. Pentru a trece la versiunea veche, urmați linkul adăugând sufixul în adresă/tree, iar trecerea la noua versiune se realizează cu sufixul /lab, dar nu trebuie specificat:
- Jupyter Notebook - http://10.0.5.5:8888/tree
- Jupyter Lab - http://10.0.5.5:8888/lab
Extinderea capabilităților Python ^
În această secțiune, vom instala module de limbaj Python atât de puternice precum NumPy, ursi panda, matplotlib, IPyWidgets ale căror rezultate sunt integrate în laptopuri laborator jupyter.
Înainte de a instala modulele Python enumerate prin managerul de pachete pip Trebuie mai întâi să rezolvăm dependențele de sistem în Alpine Linux:
g++— Necesar pentru compilarea modulelor, deoarece unele dintre ele sunt implementate în limbaj C ++ și conectați-vă la Python în timpul rulării ca module binarefreetype-dev- dependență pentru modulul Python matplotlib
Instalarea dependențelor:
lxc exec jupyterlab -- apk add g++ freetype-devExistă o problemă cu starea actuală a distribuției alpine. Linux Nu pot compila noua versiune de NumPy; primesc o eroare de compilare pe care nu o pot rezolva:
EROARE: Nu s-au putut construi roți pentru numpy care utilizează PEP 517 și nu pot fi instalate direct
Prin urmare, vom instala acest modul ca un pachet de sistem care distribuie o versiune deja compilată, dar puțin mai veche decât cea disponibilă în prezent pe site:
lxc exec jupyterlab -- apk add py3-numpy py3-numpy-devApoi, instalați modulele Python prin managerul de pachete pip. Vă rugăm să aveți răbdare, deoarece unele module se vor compila și pot dura câteva minute. Pe mașina mea, compilarea a durat aproximativ 15 minute:
lxc exec jupyterlab -- python3 -m pip install pandas ipywidgets matplotlibȘtergerea cache-urilor de instalare:
lxc exec jupyterlab -- rm -rf /home/*/.cache/pip/*
lxc exec jupyterlab -- rm -rf /root/.cache/pip/*Testarea modulelor în JupyterLab ^
Dacă alergi laborator jupyter, reporniți-l astfel încât modulele nou instalate să fie activate. Pentru a face acest lucru, într-o sesiune de consolă, faceți clic CTRL+C unde il ai in functiune si intra y pentru a opri cererea și apoi a începe din nou laborator jupyter prin apăsarea săgeții sus de pe tastatură pentru a nu mai introduce comanda și apoi Enter pentru a o porni:
jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfsAccesați pagina http://10.0.5.5:8888/lab sau reîmprospătați pagina în browser și apoi introduceți următorul cod într-o nouă celulă de blocnotes:
%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
def f(m, b):
plt.figure(2)
x = np.linspace(-10, 10, num=1000)
plt.plot(x, m * x + b)
plt.ylim(-5, 5)
plt.show()
interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plotAr trebui să obțineți un rezultat ca în imaginea de mai jos, unde IPyWidgets generează un element UI pe pagină care interacționează interactiv cu codul sursă și, de asemenea matplotlib afișează rezultatul codului sub forma unei imagini ca grafic al funcției:

Multe exemple IPyWidgets il gasesti in tutoriale aici
Ce altceva? ^
Bravo dacă ai rămas și ai ajuns chiar la sfârșitul articolului. În mod deliberat, nu am postat un script gata făcut la sfârșitul articolului care să se instaleze laborator jupyter cu „un singur clic” pentru a încuraja lucrătorii :) Dar o puteți face singur, deoarece știți deja cum, după ce ați adunat comenzile într-un singur script Bash :)
Poti de asemenea:
- Setați un nume de rețea pentru container în loc de o adresă IP scriind-o într-un mod simplu
/etc/hostsși introduceți adresa în browser http://jupyter.local:8888 - Joacă-te cu limita de resurse pentru container, pentru asta citește capitolul din capabilități de bază LXD sau obțineți mai multe informații pe site-ul pentru dezvoltatori LXD.
- Schimbați tema:

Și multe altele poți face! Asta e tot. Vă doresc succes!
UPDATE: 15.04.2020 18:30 - Erori corectate la capitolul „Bună, lume!”
UPDATE: 16.04.2020 10:00 — Text corectat și adăugat în descrierea activării managerului de extensii laborator jupyter
UPDATE: 16.04.2020/10/40 XNUMX:XNUMX — S-au corectat erorile găsite în text și s-a modificat ușor în bine capitolul „Instalarea software-ului de bază și configurarea sistemului”
Sursa: www.habr.com
