Llançament de Jupyter a LXD Orbit

Alguna vegada has hagut d'experimentar amb codi o utilitats del sistema a Linux per no preocupar-te pel sistema base i no desfer-ho tot en cas d'error en el codi que s'hauria d'executar amb privilegis de root?

Però, què passa amb el fet que suposem que necessiteu provar o executar tot un clúster de diversos microserveis en una màquina? Cent o fins i tot mil?

Amb les màquines virtuals gestionades per un hipervisor, aquests problemes es poden resoldre i es resoldran, però a quin preu? Per exemple, un contenidor en LXD basat en la distribució Alpine Linux només consumeix 7.60MB RAM i on ocupa la partició arrel després de l'inici 9.5MB! Com t'agrada això, Elon Musk? Recomano fer una ullada capacitats bàsiques de LXD, un sistema de contenidors a Linux

Després que quedés clar en general què són els contenidors LXD, anem més enllà i pensem, què passaria si hi hagués una plataforma recol·lectora on poguessis executar codi per a l'amfitrió de manera segura, generar gràfics, enllaçar de manera dinàmica (interactiva) els widgets d'IU amb el teu codi? complementar el codi amb text amb blackjack... format? Algun tipus de bloc interactiu? Vaja... ho vull! Voler! 🙂

Mireu sota el gat on llançarem en un contenidor JupyterLab - la propera generació d'interfície d'usuari en lloc de l'obsolet Jupyter Notebook, i també instal·larem mòduls Python com ara numpy, pandes, matplotlib, IPyWidgets que us permetrà fer tot el que s'esmenta anteriorment i desar-ho tot en un fitxer especial: un ordinador portàtil IPython.

Llançament de Jupyter a LXD Orbit

Pla d'enlairament orbital ^

Llançament de Jupyter a LXD Orbit

Descrivim un breu pla d'acció per facilitar-nos la implementació de l'esquema anterior:

  • Instal·lem i llançam un contenidor basat en el kit de distribució Alpine Linux. Utilitzarem aquesta distribució perquè està dirigida al minimalisme i només hi instal·larem el programari més necessari, res superflu.
  • Afegim un disc virtual addicional al contenidor i donem-li un nom: hostfs i muntar-lo al sistema de fitxers arrel. Aquest disc permetrà utilitzar fitxers a l'amfitrió des d'un directori determinat dins del contenidor. Així, les nostres dades seran independents del contenidor. Si s'elimina el contenidor, les dades romandran a l'amfitrió. A més, aquest esquema és útil per compartir les mateixes dades entre molts contenidors sense utilitzar els mecanismes de xarxa estàndard de la distribució de contenidors.
  • Instal·lem Bash, sudo, les biblioteques necessàries, afegim i configurem un usuari del sistema
  • Instal·lem Python, mòduls i compilem dependències binàries per a ells
  • Instal·lem i engeguem JupyterLab, personalitzar l'aparença, instal·lar-hi extensions.

En aquest article començarem per llançar el contenidor, no ens plantejarem instal·lar i configurar LXD, tot això ho trobareu en un altre article: Característiques bàsiques dels sistemes de contenidors LXD - Linux.

Instal·lació i configuració del sistema bàsic ^

Creem un contenidor amb l'ordre en què especifiquem la imatge - alpine3, identificador del contenidor - jupyterlab i, si cal, perfils de configuració:

lxc init alpine3 jupyterlab --profile=default --profile=hddroot

Aquí estic fent servir un perfil de configuració hddroot que especifica crear un contenidor amb una partició arrel Piscina d'emmagatzematge ubicat en un disc dur físic:

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:
- none

Això em dóna l'oportunitat d'experimentar amb contenidors al disc HDD, estalviant els recursos del disc SSD, que també està disponible al meu sistema 🙂 per al qual he creat un perfil de configuració independent ssdroot.

Un cop creat el contenidor, es troba a l'estat STOPPED, així que hem d'iniciar-lo executant-hi el sistema d'inici:

lxc start jupyterlab

Mostrem una llista de contenidors en LXD amb la clau -c que indica quin cvisualització de columnes:

lxc list -c ns4b
+------------+---------+-------------------+--------------+
|    NAME    |  STATE  |       IPV4        | STORAGE POOL |
+------------+---------+-------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.198 (eth0) | hddpool      |
+------------+---------+-------------------+--------------+

En crear el contenidor, l'adreça IP s'ha escollit aleatòriament, ja que hem utilitzat un perfil de configuració default que es va configurar prèviament a l'article Característiques bàsiques dels sistemes de contenidors LXD - Linux.

Canviarem aquesta adreça IP per una de més memorable mitjançant la creació d'una interfície de xarxa a nivell de contenidor, i no a nivell de perfil de configuració com ara es troba a la configuració actual. No cal que feu això, podeu saltar-lo.

Creació d'una interfície de xarxa eth0 que enllacem a l'interruptor (pont de xarxa) lxdbr0 en què vam habilitar NAT segons l'article anterior i el contenidor ara tindrà accés a Internet, i també assignem una adreça IP estàtica a la interfície: 10.0.5.5:

lxc config device add jupyterlab eth0 nic name=eth0 nictype=bridged parent=lxdbr0 ipv4.address=10.0.5.5

Després d'afegir un dispositiu, el contenidor s'ha de reiniciar:

lxc restart jupyterlab

Comprovació de l'estat del contenidor:

lxc list -c ns4b
+------------+---------+------------------+--------------+
|    NAME    |  STATE  |       IPV4       | STORAGE POOL |
+------------+---------+------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.5 (eth0)  | hddpool      |
+------------+---------+------------------+--------------+

Instal·lació de programari bàsic i configuració del sistema ^

Per administrar el nostre contenidor, cal instal·lar el programari següent:

paquet
Descripció

colpejar
El shell de GNU Bourne Again

finalització de bash
Finalització programable per a l'intèrpret d'ordres bash

suo
Doneu a determinats usuaris la possibilitat d'executar algunes ordres com a root

ombra
Suite d'eines de gestió de contrasenyes i comptes amb suport per a fitxers d'ombra i PAM

tzdata
Fonts de dades de zona horària i hora d'estiu

nano
Pico editor clon amb millores

A més, podeu instal·lar suport a les pàgines man del sistema instal·lant els paquets següents: − man man-pages mdocml-apropos less

lxc exec jupyterlab -- apk add bash bash-completion sudo shadow tzdata nano

Vegem les ordres i tecles que hem utilitzat:

  • lxc — Truqueu al client LXD
  • exec - Mètode client LXD que executa una ordre al contenidor
  • jupyterlab - ID del contenidor
  • -- - Una clau especial que especifica no interpretar més claus com a claus per a lxc i passar la resta de la corda tal com està al recipient
  • apk — Gestor de paquets de distribució Alpine Linux
  • add — Un mètode de gestor de paquets que instal·la els paquets especificats després de l'ordre

A continuació, establirem una zona horària al sistema Europe/Moscow:

lxc exec jupyterlab -- cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime

Després d'instal·lar la zona horària, el paquet tzdata ja no és necessari al sistema, ocuparà espai, així que suprimim-lo:

lxc exec jupyterlab -- apk del tzdata

Comprovació de la zona horària:

lxc exec jupyterlab -- date

Wed Apr 15 10:49:56 MSK 2020

Per no passar molt de temps configurant Bash per als nous usuaris al contenidor, en els passos següents copiarem els fitxers skel ja fets del sistema amfitrió. Això us permetrà embellir Bash en un contenidor de manera interactiva. El meu sistema amfitrió és Manjaro Linux i els fitxers que s'estan copiant /etc/skel/.bash_profile, /etc/skel/.bashrc, /etc/skel/.dir_colors en principi són adequats per a Alpine Linux i no causen problemes crítics, però és possible que tingueu una distribució diferent i haureu d'esbrinar de manera independent si hi ha un error en executar Bash al contenidor.

Copieu els fitxers skel al contenidor. clau --create-dirs crearà els directoris necessaris si no existeixen:

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_colors

Per a un usuari root existent, copieu els fitxers skel acabats de copiar al contenidor al directori d'inici:

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

Alpine Linux instal·la un shell del sistema per als usuaris /bin/sh, el substituirem per root usuari a Bash:

lxc exec jupyterlab -- usermod --shell=/bin/bash root

Que root l'usuari no tenia contrasenya, ha d'establir una contrasenya. La següent comanda generarà i establirà una nova contrasenya aleatòria per a ell, que veureu a la pantalla de la consola després de la seva execució:

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: sFiXEvBswuWA

A més, creem un nou usuari del sistema - jupyter per al qual configurarem més endavant JupyterLab:

lxc exec jupyterlab -- useradd --create-home --shell=/bin/bash jupyter

Generem i establim una contrasenya per a això:

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

A continuació, executarem dues ordres, la primera crearà un grup de sistema sudo, i el segon hi afegirà un usuari jupyter:

lxc exec jupyterlab -- groupadd --system sudo
lxc exec jupyterlab -- groupmems --group sudo --add jupyter

Vegem a quins grups pertany l'usuari jupyter:

lxc exec jupyterlab -- id -Gn jupyter

jupyter sudo

Tot està bé, seguim.

Permet a tots els usuaris que són membres del grup sudo utilitzar l'ordre sudo. Per fer-ho, executeu l'script següent, on sed descomenta la línia de paràmetres al fitxer de configuració /etc/sudoers:

lxc exec jupyterlab -- /bin/bash -c "sed --in-place -e '/^#[ t]*%sudo[ t]*ALL=(ALL)[ t]*ALL$/ s/^[# ]*//' /etc/sudoers"

Instal·lació i configuració de JupyterLab ^

JupyterLab és una aplicació de Python, així que primer hem d'instal·lar aquest intèrpret. També, JupyterLab instal·larem utilitzant el gestor de paquets Python pip, i no el del sistema, perquè pot estar obsolet al repositori del sistema i, per tant, n'hem de resoldre manualment les dependències instal·lant els paquets següents: python3 python3-dev gcc libc-dev zeromq-dev:

lxc exec jupyterlab -- apk add python3 python3-dev gcc libc-dev zeromq-dev

Actualitzem els mòduls de Python i el gestor de paquets pip a la versió actual:

lxc exec jupyterlab -- python3 -m pip install --upgrade pip setuptools wheel

Establir JupyterLab mitjançant el gestor de paquets pip:

lxc exec jupyterlab -- python3 -m pip install jupyterlab

Des de les ampliacions a JupyterLab són experimentals i no s'envien oficialment amb el paquet jupyterlab, de manera que l'hem d'instal·lar i configurar manualment.

Instal·lem NodeJS i el gestor de paquets per a això - NPM, des de llavors JupyterLab els utilitza per a les seves extensions:

lxc exec jupyterlab -- apk add nodejs npm

A extensions per JupyterLab que instal·larem funcionaven, s'han d'instal·lar al directori d'usuaris ja que l'aplicació s'iniciarà des de l'usuari jupyter. El problema és que no hi ha cap paràmetre a l'ordre de llançament que es pugui passar a un directori; l'aplicació només accepta una variable d'entorn i, per tant, l'hem de definir. Per fer-ho, escriurem l'ordre d'exportació variable JUPYTERLAB_DIR en l'entorn de l'usuari jupyter, arxivar .bashrcque s'executa cada vegada que l'usuari inicia sessió:

lxc exec jupyterlab -- su -l jupyter -c "echo -e "nexport JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab" >> .bashrc"

La següent comanda instal·larà una extensió especial: el gestor d'extensions JupyterLab:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager"

Ara tot està preparat per al primer llançament JupyterLab, però encara podem instal·lar algunes extensions útils:

  • toc — Taula de continguts, genera una llista d'encapçalaments en un article/quadern
  • jupyterlab-horizon-theme - Tema de la IU
  • jupyterlab_neon_theme - Tema de la IU
  • jupyterlab-ubu-theme - Un altre tema de l'autor aquest article :) Però en aquest cas, es mostrarà la instal·lació des del dipòsit de GitHub

Per tant, executeu les ordres següents seqüencialment per instal·lar aquestes extensions:

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"

Després d'instal·lar les extensions, les hem de compilar, ja que prèviament, durant la instal·lació, vam especificar la clau --no-build per estalviar temps. Ara accelerarem significativament recopilant-los d'una vegada:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter lab build"

Ara executeu les dues ordres següents per executar-lo per primera vegada JupyterLab. Seria possible llançar-lo amb una ordre, però en aquest cas, l'ordre de llançament, que és difícil de recordar a la vostra ment, es recordarà per bash al contenidor i no a l'amfitrió, on ja hi ha prou ordres. per gravar-los a la història :)

Inicieu sessió al contenidor com a usuari jupyter:

lxc exec jupyterlab -- su -l jupyter

A continuació, córrer JupyterLab amb les tecles i els paràmetres indicats:

[jupyter@jupyterlab ~]$ jupyter lab --ip=0.0.0.0 --no-browser

Aneu a l'adreça del vostre navegador web http://10.0.5.5:8888 i a la pàgina que s'obre entrar fitxa accés que veureu a la consola. Copieu-lo i enganxeu-lo a la pàgina i, a continuació, feu clic Login. Després d'iniciar sessió, aneu al menú d'extensions de l'esquerra, tal com es mostra a la figura següent, on se us demanarà, en activar el gestor d'extensions, que preneu riscos de seguretat instal·lant extensions de tercers per a les quals l'ordre. Desenvolupament de JupyterLab no és responsable:

Llançament de Jupyter a LXD Orbit

Tanmateix, estem aïllant-ho tot JupyterLab i col·loqueu-lo en un contenidor perquè les extensions de tercers que requereixin i utilitzin NodeJS no puguin almenys robar dades del disc diferents de les que obrim dins del contenidor. Accediu als vostres documents privats a l'amfitrió /home És poc probable que els processos del contenidor tinguin èxit, i si ho fan, haureu de tenir privilegis sobre els fitxers del sistema amfitrió, ja que executem el contenidor en manera sense privilegis. A partir d'aquesta informació, podeu avaluar el risc d'incloure extensions JupyterLab.

S'han creat quaderns IPython (pàgines a JupyterLab) ara es crearà al directori inicial de l'usuari - /home/jupyter, però els nostres plans són dividir les dades (compartir) entre l'amfitrió i el contenidor, així que torneu a la consola i atureu-vos JupyterLab executant la tecla d'accés ràpid - CTRL+C i contestant y a petició. A continuació, finalitzeu la sessió interactiva de l'usuari jupyter completant una tecla d'accés ràpid CTRL+D.

Compartint dades amb l'amfitrió ^

Per compartir dades amb l'amfitrió, heu de crear un dispositiu al contenidor que us permeti fer-ho i, per fer-ho, executeu l'ordre següent on especifiquem les claus següents:

  • lxc config device add — L'ordre afegeix la configuració del dispositiu
  • jupyter — ID del contenidor al qual s'afegeix la configuració
  • hostfs - ID del dispositiu. Podeu posar qualsevol nom.
  • disk — S'indica el tipus d'aparell
  • path — Especifica la ruta del contenidor on LXD muntarà aquest dispositiu
  • source — Especifiqueu la font, el camí al directori de l'amfitrió que voleu compartir amb el contenidor. Especifiqueu el camí segons les vostres preferències
lxc config device add jupyterlab hostfs disk path=/mnt/hostfs source=/home/dv/projects/ipython-notebooks

Per al catàleg /home/dv/projects/ipython-notebooks el permís s'ha d'establir per a l'usuari del contenidor que actualment té un UID igual a SubUID + UID, vegeu el capítol Seguretat. Privilegis del contenidor a l'article Característiques bàsiques dels sistemes de contenidors LXD - Linux.

Establiu el permís a l'amfitrió, on el propietari serà l'usuari del contenidor jupyter, i la variable $USER especificarà el vostre usuari amfitrió com a grup:

sudo chown 1001000:$USER /home/dv/projects/ipython-notebooks

Hola món! ^

Si encara teniu una sessió de consola oberta al contenidor amb JupyterLabi reinicieu-lo amb una clau nova --notebook-dir fixant el valor /mnt/hostfs com el camí a l'arrel dels ordinadors portàtils al contenidor del dispositiu que vam crear al pas anterior:

jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs

A continuació, aneu a la pàgina http://10.0.5.5:8888 i creeu el vostre primer ordinador portàtil fent clic al botó de la pàgina tal com es mostra a la imatge següent:

Llançament de Jupyter a LXD Orbit

A continuació, al camp de la pàgina, introduïu el codi Python que mostrarà el clàssic Hello World!. Quan hàgiu acabat d'introduir, premeu CTRL+ENTER o el botó "reproduir" a la barra d'eines a la part superior perquè JupyterLab faci això:

Llançament de Jupyter a LXD Orbit

En aquest punt, gairebé tot està llest per al seu ús, però serà poc interessant si no instal·lem mòduls addicionals de Python (aplicacions completes) que puguin ampliar significativament les capacitats estàndard de Python en JupyterLabPer tant, seguim endavant :)

PS L'interessant és que l'antiga implementació Júpiter sota el nom de codi Jupyter Llibreta no ha desaparegut i existeix en paral·lel JupyterLab. Per canviar a la versió antiga, seguiu l'enllaç afegint el sufix a l'adreça/tree, i la transició a la nova versió es realitza amb el sufix /lab, però no cal especificar-ho:

Ampliació de les capacitats de Python ^

En aquesta secció, instal·larem mòduls de llenguatge Python tan potents com numpy, pandes, matplotlib, IPyWidgets els resultats dels quals s'integren en ordinadors portàtils JupyterLab.

Abans d'instal·lar els mòduls Python llistats mitjançant el gestor de paquets pip primer hem de resoldre les dependències del sistema a Alpine Linux:

  • g++ — Necessari per compilar mòduls, ja que alguns d'ells estan implementats en el llenguatge C + + i connecteu-vos a Python en temps d'execució com a mòduls binaris
  • freetype-dev - dependència del mòdul Python matplotlib

Instal·lació de dependències:

lxc exec jupyterlab -- apk add g++ freetype-dev

Hi ha un problema: en l'estat actual de la distribució Alpine Linux, no serà possible compilar la nova versió de NumPy; apareixerà un error de compilació que no he pogut resoldre:

ERROR: No s'han pogut construir rodes per a numpy que utilitzen PEP 517 i no es poden instal·lar directament

Per tant, instal·larem aquest mòdul com un paquet del sistema que distribueix una versió ja compilada, però una mica més antiga que la que hi ha actualment disponible al lloc:

lxc exec jupyterlab -- apk add py3-numpy py3-numpy-dev

A continuació, instal·leu els mòduls de Python mitjançant el gestor de paquets pip. Si us plau, tingueu paciència, ja que alguns mòduls es compilaran i poden trigar uns minuts. A la meva màquina, la compilació va trigar uns 15 minuts:

lxc exec jupyterlab -- python3 -m pip install pandas ipywidgets matplotlib

Esborrar memòria cau d'instal·lació:

lxc exec jupyterlab -- rm -rf /home/*/.cache/pip/*
lxc exec jupyterlab -- rm -rf /root/.cache/pip/*

Prova de mòduls a JupyterLab ^

Si estàs corrent JupyterLab, reinicieu-lo perquè s'activin els mòduls recentment instal·lats. Per fer-ho, en una sessió de consola, feu clic a CTRL+C on el tens en marxa i entra y per aturar la sol·licitud i tornar a començar JupyterLab prement la fletxa amunt del teclat per no tornar a introduir l'ordre i després Enter per començar-lo:

jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs

Aneu a la pàgina http://10.0.5.5:8888/lab o actualitzeu la pàgina al vostre navegador i, a continuació, introduïu el codi següent en una nova cel·la del bloc de notes:

%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_plot

Hauríeu d'obtenir un resultat com a la imatge següent, on IPyWidgets genera un element d'IU a la pàgina que interactua de manera interactiva amb el codi font, i també matplotlib mostra el resultat del codi en forma d'imatge com a gràfic de funcions:

Llançament de Jupyter a LXD Orbit

Molts exemples IPyWidgets el podeu trobar als tutorials aquí

Què més? ^

Molt bé si us heu quedat i heu arribat al final de l'article. Deliberadament, no vaig publicar cap script ja fet al final de l'article que s'instal·laria JupyterLab en "un clic" per animar els treballadors :) Però podeu fer-ho vosaltres mateixos, ja que ja sabeu com, havent recopilat les ordres en un únic script Bash :)

Tu pots també:

  • Establiu un nom de xarxa per al contenidor en lloc d'una adreça IP escrivint-lo d'una manera senzilla /etc/hosts i escriviu l'adreça al navegador http://jupyter.local:8888
  • Juga amb el límit de recursos per al contenidor, per això llegiu el capítol a capacitats bàsiques de LXD o obteniu més informació al lloc per a desenvolupadors de LXD.
  • Canvia el tema:

Llançament de Jupyter a LXD Orbit

I molt més pots fer! Això és tot. Et desitjo èxit!

ACTUALITZACIÓ: 15.04.2020/18/30 XNUMX:XNUMX - S'han corregit errors al capítol "Hola, món!"
ACTUALITZACIÓ: 16.04.2020/10/00 XNUMX:XNUMX — Text corregit i afegit a la descripció de l'activació del gestor d'extensions JupyterLab
ACTUALITZACIÓ: 16.04.2020/10/40 XNUMX:XNUMX — S'han corregit errors trobats al text i s'ha modificat lleugerament per a millor el capítol "Instal·lació de programari bàsic i configuració del sistema"

Font: www.habr.com

Afegeix comentari