Хамгийн бага амьдрах чадвартай Kubernetes

Нийтлэлийн орчуулгыг курс эхлэхийн өмнөх өдөр бэлтгэсэн "DevOps практик ба хэрэгслүүд".

Хамгийн бага амьдрах чадвартай Kubernetes

Хэрэв та үүнийг уншиж байгаа бол та Кубернетесийн талаар ямар нэг зүйл сонссон байх (мөн үгүй ​​бол та энд яаж ирсэн бэ?) Гэхдээ Кубернетес гэж яг юу вэ? Энэ “Үйлдвэрлэлийн зориулалттай савны зохион байгуулалт”? Эсвэл "Үүлэн-уугуул үйлдлийн систем"? Энэ нь бүр юу гэсэн үг вэ?

Үнэнийг хэлэхэд би 100% итгэлтэй биш байна. Гэхдээ миний бодлоор дотоод сэтгэлгээг ухаж, олон давхар хийсвэрлэлийн дор Кубернетес хотод юу болж байгааг харах нь сонирхолтой юм. Зүгээр л хөгжилтэй байхын тулд хамгийн бага "Кубернетес кластер" ямар харагддагийг харцгаая. (Энэ нь хамаагүй хялбар байх болно Кубернетес Хатуу зам.)

Таныг Кубернетес, Линукс, контейнерийн талаар анхан шатны мэдлэгтэй гэж бодож байна. Бидний энд ярьж байгаа бүх зүйл зөвхөн судалгаа/сургалтад зориулагдсан тул аль нэгийг нь үйлдвэрлэлд бүү оруулаарай!

тойм

Kubernetes нь олон бүрэлдэхүүн хэсгүүдийг агуулдаг. дагуу Википедиа, архитектур нь дараах байдалтай байна.

Хамгийн бага амьдрах чадвартай Kubernetes

Энд дор хаяж найман бүрэлдэхүүн хэсэг байгаа боловч бид тэдгээрийн ихэнхийг үл тоомсорлох болно. Кубернетес гэж нэрлэж болох хамгийн бага зүйл нь гурван үндсэн бүрэлдэхүүн хэсгээс бүрддэг гэдгийг би хэлмээр байна.

  • кубелет
  • kube-apiserver (энэ нь etcd - түүний мэдээллийн сангаас хамаарна)
  • контейнер ажиллах хугацаа (энэ тохиолдолд Docker)

Баримт бичигт тус бүрийн талаар юу хэлснийг харцгаая (орос., Англи.). Хамгийн эхэнд кубелет:

Кластер дахь зангилаа бүр дээр ажиллаж буй агент. Энэ нь саванд савнууд ажиллаж байгаа эсэхийг баталгаажуулдаг.

Хангалттай энгийн сонсогдож байна. Яах вэ контейнер ажиллах хугацаа (контейнер ажиллах хугацаа)?

Контейнер ажиллуулах хугацаа нь контейнер ажиллуулах зориулалттай програм юм.

Маш мэдээлэл сайтай. Гэхдээ хэрэв та Docker-ийг мэддэг бол энэ нь юу хийдэг талаар ерөнхий ойлголттой байх ёстой. (Сантерийн ажиллах хугацаа болон кубелетийн хоорондох үүрэг хариуцлагыг салгах нарийн ширийн зүйл нь үнэндээ маш нарийн бөгөөд би энд ярихгүй.)

И API сервер?

API сервер нь Kubernetes API-г ил гаргадаг Kubernetes хяналтын самбарын бүрэлдэхүүн хэсэг юм. API сервер нь Kubernetes хяналтын самбарын клиент тал юм

Kubernetes-тэй ямар нэгэн зүйл хийж байсан хэн бүхэн API-тай шууд эсвэл kubectl-ээр дамжуулан харилцах шаардлагатай болдог. Энэ бол бидний мэддэг, хайрладаг YAML-ийн уулсыг (?) ажлын дэд бүтэц болгон хувиргадаг тархи болох Кубернетес Кубернетесийг юу болгож байгаагийн гол зүрх юм. API нь бидний хамгийн бага тохиргоонд байх ёстой нь ойлгомжтой юм шиг байна.

Урьдчилсан нөхцөл

  • root хандалттай Линуксийн виртуал эсвэл физик машин (би виртуал машин дээр Ubuntu 18.04 ашиглаж байна).
  • Энэ бүгд!

Уйтгартай суурилуулалт

Бид ашиглах машин дээрээ Docker суулгах хэрэгтэй. (Би Docker болон контейнер хэрхэн ажилладаг талаар дэлгэрэнгүй ярихгүй; хэрэв та сонирхож байвал гайхалтай нийтлэлүүд). Үүнийг суулгаж өгье apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

Үүний дараа бид Kubernetes хоёртын файлыг авах хэрэгтэй. Үнэн хэрэгтээ манай "кластер" -ийг анх эхлүүлэхийн тулд бидэнд хэрэгтэй kubelet, учир нь бид бусад серверийн бүрэлдэхүүн хэсгүүдийг ажиллуулахын тулд ашиглаж болно kubelet. Кластер ажиллаж дууссаны дараа түүнтэй харилцахын тулд бид бас ашиглах болно kubectl.

$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5

Бид зүгээр л гүйгээд байвал яах вэ kubelet?

$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied

kubelet root хэлбэрээр ажиллах ёстой. Тэр бүхэл бүтэн зангилааг удирдах шаардлагатай тул нэлээд логик юм. Түүний параметрүүдийг харцгаая:

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

Хөөх, маш олон сонголт! Аз болоход бидэнд зөвхөн хоёр нь л хэрэгтэй. Бидний сонирхож буй параметрүүдийн нэг нь энд байна:

--pod-manifest-path string

Статик pods-д зориулсан файлуудыг агуулсан лавлах зам эсвэл статик подкуудыг дүрсэлсэн файлын зам. Цэгээр эхэлсэн файлуудыг үл тоомсорлодог. (ХУГАЦААГҮЙ: Энэ сонголтыг --config сонголтоор дамжуулан Kubelet-д дамжуулсан тохиргооны файлд тохируулах ёстой. Дэлгэрэнгүй мэдээллийг үзнэ үү. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Энэ сонголт нь биднийг ажиллуулах боломжийг олгодог статик хонхорцог — Kubernetes API-ээр удирдагддаггүй pods. Статик хонхорцог нь ховор хэрэглэгддэг боловч кластерийг хурдан өсгөхөд маш тохиромжтой бөгөөд энэ нь бидэнд яг хэрэгтэй зүйл юм. Бид энэ том сэрэмжлүүлгийг үл тоомсорлож (дахин хэлэхэд үүнийг үйлдвэрлэлд бүү ажиллуул!) Бид подволкыг ажиллуулж чадах эсэхийг шалгана.

Эхлээд бид статик pods-д зориулсан лавлах үүсгээд ажиллуулна kubelet:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

Дараа нь өөр терминал/tmux цонхонд/юунд ч бид pod манифест үүсгэх болно:

$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet зарим анхааруулга бичиж эхэлдэг бөгөөд юу ч болоогүй юм шиг санагддаг. Гэхдээ энэ нь үнэн биш юм! Докерыг харцгаая:

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconds ago      Exited (0) 36 seconds ago                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "/pause"                2 minutes ago       Up 2 minutes                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet Би pod manifest-ийг уншаад Docker-д манай техникийн дагуу хэд хэдэн контейнер ажиллуулах тушаал өгсөн. (Хэрэв та "түр зогсоох" савны талаар гайхаж байгаа бол энэ нь Kubernetes-ийн хакердсан хэрэг - үзнэ үү. энэ блог.) Кубелет манай савыг хөөргөх болно busybox заасан командыг ашиглан статик подыг устгах хүртэл тодорхойгүй хугацаагаар дахин эхлүүлэх болно.

Өөртөө баяр хүргэе. Терминал руу текст гаргах хамгийн будлиантай аргуудын нэгийг бид дөнгөж сая бодож оллоо!

Эхлүүлэх гэх мэт

Бидний эцсийн зорилго бол Kubernetes API-г ажиллуулах явдал боловч үүнийг хийхийн тулд эхлээд ажиллуулах хэрэгтэй гэх мэт. Minimum etcd кластерын тохиргоог pods директорт байрлуулж эхэлцгээе (жишээ нь, pods/etcd.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data

Хэрэв та хэзээ нэгэн цагт Kubernetes-тэй ажиллаж байсан бол эдгээр YAML файлууд танд танил байх ёстой. Энд зөвхөн хоёр зүйлийг анхаарч үзэх хэрэгтэй.

Бид хост фолдерыг суулгасан /var/lib/etcd Дахин ачаалсны дараа etcd өгөгдөл хадгалагдах тул pod-д суулгана (хэрэв үүнийг хийгээгүй бол подкликийг дахин эхлүүлэх бүрт кластерийн төлөв устах бөгөөд энэ нь Кубернетесийн хамгийн бага суулгалтад ч тохирохгүй).

Бид суулгасан hostNetwork: true. Энэ тохиргоо нь etcd-г подын дотоод сүлжээний оронд хост сүлжээг ашиглахаар тохируулдаг нь гайхмаар зүйл биш юм (энэ нь API серверт etcd кластерийг олоход хялбар болгоно).

Энгийн шалгалт нь etcd нь үнэхээр localhost дээр ажиллаж, өгөгдлийг дискэнд хадгалж байгааг харуулж байна:

$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
    ├── snap
    │   └── db
    └── wal
        ├── 0.tmp
        └── 0000000000000000-0000000000000000.wal

API серверийг эхлүүлж байна

Kubernetes API серверийг ажиллуулах нь бүр ч хялбар. Дамжуулах шаардлагатай цорын ганц параметр бол --etcd-servers, таны хүлээж буй зүйлийг хийнэ:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true

Энэ YAML файлыг директорт байрлуул pods, мөн API сервер эхлэх болно. -тай шалгаж байна curl Kubernetes API нь бүрэн нээлттэй хандалттай 8080 портыг сонсож байгааг харуулж байна - баталгаажуулалт шаардлагагүй!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(Дахин хэлэхэд, үүнийг үйлдвэрлэлд бүү ажиллуул! Би анхдагч тохиргоо нь тийм найдвартай биш байгаад бага зэрэг гайхсан. Гэхдээ энэ нь хөгжүүлэлт, туршилтыг хөнгөвчлөх зорилготой гэж таамаглаж байна.)

Мөн тааламжтай гэнэтийн бэлэг, kubectl ямар ч нэмэлт тохиргоогүйгээр хайрцагнаас гарч ажилладаг!

$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.

асуудал

Гэхдээ хэрэв та бага зэрэг гүн ухвал ямар нэг зүйл буруу болж байх шиг байна:

$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.

Бидний үүсгэсэн статик подкууд алга болсон! Үнэндээ бидний kubelet зангилаа огт нээгдээгүй байна:

$ ./kubectl get nodes
No resources found in default namespace.

Юу болсон бэ? Хэрэв та хэдэн догол мөрийн өмнө санаж байгаа бол бид kubelet-ийг командын мөрийн параметрүүдийн маш энгийн багцаар эхлүүлсэн тул kubelet API сервертэй хэрхэн холбогдож, төлөвийнх нь талаар мэдэгдэхээ мэдэхгүй байна. Баримт бичгийг судалсны дараа бид тохирох тугийг олно.

--kubeconfig string

Файлд хүрэх зам kubeconfig, энэ нь API сервертэй хэрхэн холбогдохыг зааж өгдөг. Бэлэн байдал --kubeconfig API серверийн горимыг идэвхжүүлдэг, үгүй --kubeconfig офлайн горимыг идэвхжүүлдэг.

Энэ бүх хугацаанд бид өөрийн мэдэлгүй kubelet-ийг "офлайн горимд" ажиллуулж байсан. (Хэрэв бид педантик байсан бол бие даасан кубелетийг "хамгийн бага амьдрах чадвартай Кубернетес" гэж төсөөлж болох ч энэ нь маш уйтгартай байх болно). "Бодит" тохиргоог ажиллуулахын тулд бид kubeconfig файлыг kubelet рүү дамжуулах хэрэгтэй бөгөөд ингэснээр API сервертэй хэрхэн ярихаа мэддэг. Аз болоход энэ нь маш энгийн (бидэнд баталгаажуулалт эсвэл гэрчилгээний асуудал байхгүй тул):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

Үүнийг дараах байдлаар хадгал kubeconfig.yaml, үйл явцыг устгана kubelet шаардлагатай параметрүүдээр дахин эхлүүлнэ үү:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(Дашрамд хэлэхэд, хэрэв та kubelet ажиллахгүй байхад curl-ээр API-д хандахыг оролдвол энэ нь одоо ч ажиллаж байгааг олж мэдэх болно! Kubelet нь Docker шиг подкуудын "эцэг эх" биш, харин "хяналт"-тай адил юм. дэмон." Кубелетийн удирддаг савнууд кубелет тэднийг зогсоох хүртэл ажиллана.)

Хэдхэн минутанд kubectl Бидний бодож байгаагаар хонхорцог болон зангилаануудыг харуулах ёстой:

$ ./kubectl get pods -A
NAMESPACE     NAME                    READY   STATUS             RESTARTS   AGE
default       hello-mink8s            0/1     CrashLoopBackOff   261        21h
kube-system   etcd-mink8s             1/1     Running            0          21h
kube-system   kube-apiserver-mink8s   1/1     Running            0          21h
$ ./kubectl get nodes -owide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
mink8s   Ready    <none>   21h   v1.18.5   10.70.10.228   <none>        Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

Энэ удаад үнэхээр баяр хүргэе (би аль хэдийн өөрсдөдөө баяр хүргэсэн гэдгээ мэдэж байна) - бидэнд бүрэн ажиллагаатай API бүхий хамгийн бага Kubernetes "кластер" байгаа!

Бид доор ажиллуулж байна

Одоо API ямар чадвартай болохыг харцгаая. Nginx pod-оос эхэлцгээе:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

Энд бид нэлээд сонирхолтой алдааг олж авах болно:

$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.

Эндээс бид Kubernetes-ийн орчин хэчнээн бүрэн бус байгааг харж байна - бидэнд үйлчилгээний данс байхгүй. Үйлчилгээний бүртгэлийг гараар үүсгэн дахин оролдоод юу болохыг харцгаая:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account

Үйлчилгээний бүртгэлийг гараар үүсгэсэн ч баталгаажуулалтын токен үүсдэггүй. Бид өөрсдийн минималист "кластер"-аа үргэлжлүүлэн туршиж үзэхэд ихэвчлэн автоматаар тохиолддог ихэнх ашигтай зүйлс алга болно гэдгийг олж мэдэх болно. Kubernetes API сервер нь нэлээд минималист бөгөөд хүнд даацын болон автомат тохиргооны ихэнх нь янз бүрийн хянагч болон хараахан ажиллаж амжаагүй суурь ажлуудад хийгддэг.

Бид сонголтыг тохируулснаар энэ асуудлыг шийдэж чадна automountServiceAccountToken үйлчилгээний дансны хувьд (бид үүнийг ямар ч байсан ашиглах шаардлагагүй болно):

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

Эцэст нь хонхорхой гарч ирэв! Гэвч үнэндээ манайд байхгүй учраас эхлэхгүй төлөвлөгч (Хуваарьлагч) нь Kubernetes-ийн өөр нэг чухал бүрэлдэхүүн хэсэг юм. Дахин хэлэхэд, Kubernetes API нь гайхалтай "дүлий" гэдгийг бид харж байна - таныг API дээр Pod үүсгэх үед тэр үүнийг бүртгэдэг боловч ямар зангилаа дээр ажиллуулахаа олохыг оролддоггүй.

Үнэн хэрэгтээ pod ажиллуулахын тулд төлөвлөгч хэрэггүй. Та параметрийн манифест руу гараар зангилаа нэмж болно nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(Солих mink8s Зангилааны нэр.) Устгаж хэрэглэсний дараа бид nginx ажиллаж, дотоод IP хаягийг сонсож байгааг харж байна:

$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Дотор хоорондын сүлжээ зөв ажиллаж байгаа эсэхийг шалгахын тулд бид өөр нэг подноос curl ажиллуулж болно:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: curlimages/curl
    name: curl
    command: ["curl", "172.17.0.2"]
  nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Энэ орчныг ухаж, юу нь болж, юу нь болохгүй байгааг харах нь нэлээд сонирхолтой юм. ConfigMap болон Secret нь хүлээгдэж байгаа шиг ажиллаж байгааг би олж мэдсэн боловч Үйлчилгээ болон Байршлуулалт нь тэгдэггүй.

Амжилт!

Энэ нийтлэл удаан болж байгаа тул би ялалтаа зарлаж, энэ бол "Кубернетес" гэж нэрлэгдэх боломжтой тохиргоо юм гэж хэлэх болно. Дүгнэж хэлэхэд: дөрвөн хоёртын файл, таван тушаалын мөрийн параметр ба "зөвхөн" 45 мөр YAML (биш Kubernetes стандартын дагуу) мөн бидэнд маш олон зүйл ажиллаж байна:

  • Pod-уудыг ердийн Kubernetes API ашиглан удирддаг (хэд хэдэн хакердсан)
  • Та нийтийн контейнерийн зургийг байршуулж, удирдах боломжтой
  • Подууд амьд хэвээр байх бөгөөд автоматаар дахин асаана
  • Нэг зангилаа доторх pods хоорондын сүлжээ нь маш сайн ажилладаг
  • ConfigMap, Нууц, хадгалах санг суурилуулах нь хүлээгдэж буй шигээ ажилладаг

Гэхдээ Кубернетесийг үнэхээр хэрэгтэй болгодог зүйлсийн ихэнх нь дутуу хэвээр байна, тухайлбал:

  • Под хуваарьлагч
  • Баталгаажуулалт/зөвшөөрөл
  • Олон зангилаа
  • Үйлчилгээний сүлжээ
  • Кластерт дотоод DNS
  • Үйлчилгээний дансны хянагч, байршуулалт, үүлэн үйлчилгээ үзүүлэгчтэй нэгтгэх болон Kubernetes-ийн авчирдаг бусад сайн зүйлс.

Тэгэхээр бид үнэндээ юу авсан бэ? Kubernetes API нь дангаараа ажилладаг бөгөөд энэ нь ердөө л платформ юм савны автоматжуулалт. Энэ нь тийм ч их зүйл хийдэггүй - энэ нь API ашигладаг янз бүрийн хянагч, операторуудад зориулсан ажил юм - гэхдээ энэ нь автоматжуулалтын тогтвортой орчинг бүрдүүлдэг.

Сургалтын талаар нэмэлт мэдээллийг үнэгүй вэбинараас аваарай.

Цааш унших:

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх