Автоматизация развертывания стенда Kubernetes

Меня заинтересовала тема Kubernetes, и я решил освоить его. На начальном этапе все шло хорошо, пока я изучал теорию.

Однако как только дело дошло до практики внезапно выяснилось что по каким то причинам самое быстрое и распространённое решение minicube просто отказывается разворачиваться на моей Fedora. Разворачивание просто зависало на одном из этапов. Причина подозреваю была в не отключенном по умолчанию swap разделе, но на тот момент я не додумал.

Попробовав несколько вариантов с разными виртуальными машинами, я решил что раз не работает minicube, значит надо развернуть более комплексное решение. Подумал и полез в интернет. После прочтения нескольких статей на нашем ресурсе я решил остановиться на этой:

@imbasoft «Гайд для новичков по установке Kubernetes» 

В ней было всё разложено по полочкам, все шаги выполнялись легко и непринуждённо. В конце получался замечательный стенд для тренировок. Причем стенд вариативный, с разными движками контейнеризации, и вариантами запуска как аналог миникуба, так и пяти хостовый вариант с управляющими и рабочими нодами.

Но смущал меня один момент, в статье было описано развёртывание со многими снапшотами, чтобы можно было вернуться и переиграть по-новому.
Каждый раз откатываться по нескольким машинам мне не хотелось. А развернуть хотелось.
Решение пришло быстро. Ansible. Можно раскатываться и перераскатываться относительно быстро. В любой момент можно удалить стенд и начать заново.

До этого не работал с ansible, поэтому это так же показалось мне вполне неплохой практикой.

Возможно решение не самое правильное, надеюсь люди в комментариях меня поправят или подскажут более удачные варианты решений. Но пока представляю на суд то что получилось в итоге.
Я знаю что существуют готовые варианты типа kubespray, но тут больше хотелось поработать именно с ansible, так что я решил не упускать возможности. В любом случае не ошибается тот, кто ничего не делает, так что лучше я допущу десяток ошибок новичка, на которые мне может даже укажут, чем вообще не попробую.

Итак, с чего начать?

Во-первых надо было выбрать виртуализацию. KVM показался мне нормальным решением, он можно сказать родной для linux, есть возможность рулить из командной строки.

Я не буду описывать как настраивать KVM на машине и устанавливать ansible, статья не об этом. Предположим что у вас уже всё установлено.

Как я уже написал, опыта с ansible у меня не много, но даже с ним я понимаю что писать одну большую простыню кода не особо удобно, а отлаживать и того хуже. Было решено разбить её на несколько простыней поменьше посредством ролей.

В целом если прочитать оригинальную статью то можно выделить 3 этапа:
1) Подготовка виртуальных машин
2) Установка движка контейнеризации
3) Установка all_in_one/ha_cluster

Исходя из этого будем готовить 4 роли со своими тасками.
— vm_provision
— driver_provision
— k8s_all_in_one
— k8s_ha_cluster

Создаём каталог, у меня он называется kvmlab, и в нем файл setup_k8s.yaml

Это будет главный playbook, из него будут подтягиваться остальные по мере необходимости. Tак же нам понадобится inventory и файл с переменными которыми мы будем управлять развёртыванием. Ну и конечно же роли.

В каталоге выполним, для создания ролей.

ansible-galaxy role init vm_provision ansible-galaxy role init driver_provision ansible-galaxy role init k8s_ha_cluster ansible-galaxy role init k8s_all_in_one

Файл inventory описывает наши ansible_host для подключения:

all:   children:     management:       hosts:         node1:           ansible_host: 172.30.0.201         node2:           ansible_host: 172.30.0.202         node3:           ansible_host: 172.30.0.203     workers:       hosts:         node4:           ansible_host: 172.30.0.204         node5:           ansible_host: 172.30.0.205

my_vars.yml как видно из названия описывает переменные, параметры развертывания, параметры виртуальных машин, каталоги хранения iso и дисков VM:

variant: all-in-one #[all-in-one, ha-cluster] engine: cri-o #[container-d, cri-o, docker] libvirt_pool_dir: "/home/alex/myStorage/storage_for_VMss" libvirt_pool_images: "/home/alex/myStorage/iso_imagess" vm_net: k8s_net ssh_key: "/home/alex/.ssh/id_rsa.pub" ansible_ssh_common_args: "-o StrictHostKeyChecking=no" version: "1.26" os: "Debian_11" vm_info:   vm_names:     - name: node1       memory: 2048       cpu: 2       ipaddr: 172.30.0.201     - name: node2       memory: 2048       cpu: 2       ipaddr: 172.30.0.202     - name: node3       memory: 2048       cpu: 2       ipaddr: 172.30.0.203     - name: node4       memory: 3072       cpu: 4       ipaddr: 172.30.0.204     - name: node5       memory: 3072       cpu: 4       ipaddr: 172.30.0.205

Рассмотрим переменные чуть подробнее:

variant — это наш вариант установки будем ли мы устанавливать кластер или ограничимся одной машиной и сделаем аналог minikube.

engine — собственно движок контейнеризации

libvirt_pool_dir и libvirt_pool_images каталоги хранения дисков виртуальных машин и скачанных образов соответственно.

vm_net — имя создаваемой сети для ваших машин.

ssh_key — ваш публичный ключ, подкидывается на ВМ в процессе подготовки и дальнейшие действия выполняются вашим логином из под root.

ansible_ssh_common_args — отключение проверки хеша ключа.

Теперь вернемся к setup_k8s.yaml:
Первый play выполняется на localhost, требует повышенных прав и состоит из 6 task:

  1. Подготовка окружения — на этом этапе мы устанавливаем необходимые пакеты для управления libvirt

  2. Настройка сети — машины будут использовать свою сеть, но её надо предварительно создать.

  3. Подготовка шаблона для ВМ — все машины будут с одинаковой OS, в моём случае с debian 11, у них будет одинаковый набор начальных пакетов. Каждый раз разворачивать с нуля долго, поэтому надо подготовить шаблон VM и переиспользовать его при необходимости.

  4. Создание ВМ нод из шаблонного образа. Создание нужного количества VM для развертывания.

  5. Перезагрузка созданных машин

  6. Создание снапшота. Эта таска опциональна, при дальнейшем развёртывании часто случались ошибки и надо было начинать сначала, снапшот решал эту проблему. в целом сейчас он уже не нужен, но я оставил. Для подготовки будем использовать роль vm_provision о ней чуть позже, а сейчас посмотрим на то что получилось:

kvmlab/setup_k8s.yaml:

--- - name: Подготовка ВМ к развёртыванию k8s   hosts: localhost   gather_facts: yes   become: yes   tasks:     - name: Подготовка окружения       package:         name:           - libguestfs-tools           - python3-libvirt         state: present      - name: Настройка сети       include_role:         name: vm_provision         tasks_from: create_network.yml      - name: Подготовка шаблона для ВМ       include_role:         name: vm_provision         tasks_from: prepare_images_for_cluster.yml      - name: Создание ВМ нод из шаблонного образа.       include_role:         name: vm_provision         tasks_from: create_nodes.yml       vars:         vm_name: "{{ item.name }}"         vm_vcpus: "{{ item.cpu }}"         vm_ram_mb: "{{ item.memory }}"         ipaddr: "{{ item.ipaddr }}"       with_items: "{{ vm_info.vm_names }}"       when: variant == 'ha-cluster' or (variant == 'all-in-one' and item.name == 'node1')      - name: Ожидание загрузки всех ВМ из списка       wait_for:         host: "{{ hostvars[item].ansible_host }}"         port: 22         timeout: 300         state: started       when: variant == 'ha-cluster' or item == 'node1'       with_items: "{{ groups['all'] }}"      - name: Создаем снимок host_provision       include_role:         name: vm_provision         tasks_from: create_snapshot.yml       vars:         vm_name: "{{ item.name }}"         snapshot_name: "host_provision"         snapshot_description: "Нода подготовлена к установке движка"       when: variant == 'ha-cluster' or item.name == 'node1'       with_items: "{{ vm_info.vm_names }}"

Визуально не много, давай разберём что скрывается под include_role.

А под ролью у нас скрывается:

Дефолтные настройки на случай если какие то переменные не заполнены, по иерархии если не ошибаюсь стоят в самом низу, т.е. если эти переменные прилетят откуда от еще, их приоритет будет выше:

kvmlab/roles/vm_provision/defaults/main.yml:

--- # defaults file for vm_provision base_image_name: debian-11-generic-amd64-20230124-1270.qcow2 base_image_url: https://cdimage.debian.org/cdimage/cloud/bullseye/20230124-1270/{{ base_image_name }} base_image_sha: 8db9abe8e68349081cc1942a4961e12fb7f94f460ff170c4bdd590a9203fbf83 libvirt_pool_dir: "/var/lib/libvirt/images" libvirt_pool_images: "/var/lib/libvirt/images" vm_vcpus: 2 vm_ram_mb: 2048 vm_net: vmnet vm_root_pass: test123 ssh_key: /root/.ssh/id_rsa.pub

2 шаблона:

roles/vm_provision/templates/
vm-template.xml.j2 — Шаблон по которому создается виртуальная машина в xml формате. при создании параметры заполняются из заданных переменных.
vm_network.xml.j2 — Шаблон для создания сети которую будут использовать VM.

Я не буду их приводить, вы сможете забрать их в репозитории.

Ну и наконец roles/vm_provision/tasks/

create_network.yml — набор задач для создания сети

create_nodes.yml — набор задач для создания нод

create_snapshot.yml — создание снапшотов

prepare_images_for_cluster.yml — подготовка шаблона

Начнем с подготовки шаблона:
состоит из 4 задач:

  1. Создание каталога для хранения исходного образа(если конечно он не существует)

  2. Скачивание и проверка базового образа. Каждый раз качать его нет смысла, поэтому скачивается один раз, при повторном запуске, если файл уже лежит на месте эта часть скипается.

  3. Базовый образ уже можно подключить к ВМ и работать с ним, однако тогда он перестанет быть базовым, а уже будет кастомизированным. Оставим его как есть, но скопируем его как шаблон для ВМ

  4. первичная настройка шаблона. Часть библиотек и ПО для любого варианта развертывания будет одна и та же. Поэтому проще накатить их сразу в шаблон. Так же заполним hosts, по-хорошему его бы заполнять динамически в зависимости от количества нод, но я прописал 5 штук сразу. Сильно не мешает.

kvmlab/roles/vm_provision/tasks/prepare_images_for_cluster.yml:

--- # tasks file vm_provision, создание  шаблона ВМ - name: Создание каталога {{ libvirt_pool_images }} если не существует.   file:     path: "{{ libvirt_pool_images }}"     state: directory     mode: 0755  - name: Скачивание базового образа если его нет в хранилище   get_url:     url: "{{ base_image_url }}"     dest: "{{ libvirt_pool_images }}/{{ base_image_name }}"     checksum: "sha256:{{ base_image_sha }}"    - name: Создание копии базового образа, для шаблона   copy:     dest: "{{ libvirt_pool_images }}/template_with_common_settings.qcow2"     src: "{{ libvirt_pool_images }}/{{ base_image_name }}"     force: no     remote_src: yes      mode: 0660   register: copy_results  - name: Первичная настройка шаблона.   command: |     virt-customize -a {{ libvirt_pool_images }}/template_with_common_settings.qcow2 \     --root-password password:{{ vm_root_pass }} \     --ssh-inject 'root:file:{{ ssh_key }}' \     --uninstall cloud-init \     --run-command 'apt update && apt install -y ntpdate gnupg gnupg2 curl software-properties-common wget keepalived haproxy' \     --append-line '/etc/hosts:172.30.0.201 node1.internal node1\n172.30.0.202 node2.internal node2\n172.30.0.203 node3.internal node3\n172.30.0.204 node4.internal node4\n172.30.0.205 node5.internal node5' \     --run-command 'curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmour -o /etc/apt/trusted.gpg.d/cgoogle.gpg' \     --run-command 'apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"' \     --run-command 'apt update && apt install -y kubeadm kubectl' \     --run-command 'echo 'overlay' > /etc/modules-load.d/k8s.conf && echo 'br_netfilter' >> /etc/modules-load.d/k8s.conf' \     --run-command 'echo -e "net.bridge.bridge-nf-call-ip6tables = 1\nnet.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1" > /etc/sysctl.d/10-k8s.conf'    when: copy_results is changed

Отлично, шаблон готов. далее на очереди создание сети, ибо сеть используется в шаблоне создания ВМ, и если её не будет, то чуда не случится.

Тут все просто, используя virsh мы проверяем создавалась ли сеть ранее. если да, то скипаем, если же нет, то используя шаблон в который будет подставлено имя сети из переменных средствами всё той же virsh будет создана, запущена и выставлена в автозапуск сеть.

kvmlab/roles/vm_provision/tasks/create_network.yml:

--- # tasks file for vm_provision, пересоздание сети  - name: Получение списка сетей KVM   command: virsh net-list --all   register: net_list_output  - name: Проверка наличия сети {{ vm_net }}   shell: echo "{{ net_list_output.stdout }}" | grep -w "{{ vm_net }}"   register: network_check   ignore_errors: true  - name: Создание и настройка сети {{ vm_net }}   block:     - name: Копирование шаблона сети       template:         src: vm_network.xml.j2         dest: /tmp/vm_network.xml      - name: Создание сети {{ vm_net }}       command: virsh net-define /tmp/vm_network.xml      - name: Запуск сети {{ vm_net }}       command: virsh net-start {{ vm_net }}      - name: Автостарт сети {{ vm_net }}       command: virsh net-autostart {{ vm_net }}   when: network_check.rc != 0

Так. Шаблон ВМ есть, сеть есть. Ничего не мешает нам создать ноду или ноды:
Создание нод запускается циклом по переменным. (vm_info.vm_names)
ноды создаются по одной и проходят следующие этапы:

  1. Опять же создается каталог для хранения дисков виртуальных машин, если он не существует.

  2. Каждая машина перед созданием проверяется на наличие её в уже существующих, если она есть, то создание пропускается, так что если у вас осталась машина с прошлого стенда то лучше её пересоздать.

  3. Копируется шаблонный образ диска и переименовывается в соответствии с именем ВМ

  4. Изменяется размер диска, расширяется до 10 GB, этого объема мне хватило для установки всех вариантов. Значение захардкожено, но при желании его можно так же параметризовать.

  5. Начальное конфигурирование ноды. Тут у нод появляется индивидуальность, имя, ip и свой ssh ключ

  6. Когда все составные части готовы, создается машина из шаблона xml

  7. Запуск ВМ

kvmlab/roles/vm_provision/tasks/create_nodes.yml:

--- # tasks file for vm_provision, создание нод - name: Создание каталога {{ libvirt_pool_dir }} если не существует.   file:     path: "{{ libvirt_pool_dir }}"     state: directory     mode: 0755  - name: Получаем список существующих ВМ   community.libvirt.virt:     command: list_vms   register: existing_vms   changed_when: no  - name: Создание ВМ если её имени нет в списке   block:   - name: Копирование шаблонного образа в хранилище     copy:       dest: "{{ libvirt_pool_dir }}/{{ vm_name }}.qcow2"       src: "{{ libvirt_pool_images }}/template_with_common_settings.qcow2"       force: no       remote_src: yes        mode: 0660     register: copy_results      - name: Изменение размера виртуального диска     shell: "qemu-img resize {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 10G"     - name: Начальное конфигурирование hostname:{{ vm_name }}, ip:{{ ipaddr }}     command: |       virt-customize -a {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 \       --hostname {{ vm_name }}.internal \       --run-command 'echo "source /etc/network/interfaces.d/*\nauto lo\niface lo inet loopback\nauto enp1s0\niface enp1s0 inet static\naddress {{ ipaddr }}\nnetmask 255.255.255.0\ngateway 172.30.0.1\ndns-nameservers 172.30.0.1" > /etc/network/interfaces'       --run-command 'ssh-keygen -A'      when: copy_results is changed    - name: Создание ВМ из шаблона     community.libvirt.virt:       command: define       xml: "{{ lookup('template', 'vm-template.xml.j2') }}"          when: "vm_name not in existing_vms.list_vms"  - name: Включение ВМ   community.libvirt.virt:     name: "{{ vm_name }}"     state: running   register: vm_start_results   until: "vm_start_results is success"   retries: 15   delay: 2

Отлично, машины созданы. теперь перезагрузим их, дождемся пока все загрузятся и сделаем снапшоты.

kvmlab/roles/vm_provision/tasks/create_snapshot.yml:

--- # tasks file for vm_provision, создание снапшотов - name: Создание снапшота {{ snapshot_name }}   shell: "virsh snapshot-create-as --domain {{ vm_name }} --name {{ snapshot_name }} --description '{{ snapshot_description }}'"   register: snapshot_create_status   ignore_errors: true

Если ничего не забыл, то первый этап выполнен.

У вас есть одна или пять нод, все готовы к дальнейшей работе.

Причем если удалить все ВМ и запустить создание повторно, то из за наличия готового шаблона процесс пройдёт гораздо быстрее.

Отлично. переходим к установке движка:

вернемся в setup_k8s.yaml и добавим следующий play:

 - name: Установка движка контейнеризации [cri-o, container-d, docker]   hosts: all   gather_facts: true   become: true   remote_user: root   tasks:     - name: Синхронизация даты/времени с NTP сервером       shell: ntpdate 0.europe.pool.ntp.org      - name: Установка cri-o       include_role:         name: driver_provision         tasks_from: install_crio.yml       when: engine == "cri-o"      - name: Установка container-d       include_role:         name: driver_provision         tasks_from: install_container_d.yml       when: engine == "container-d"      - name: Установка docker cri       include_role:         name: driver_provision         tasks_from: install_docker_cri.yml       when: engine == "docker"

В целом всё просто, используем роль driver_provision, но в зависимости от установленных параметров запускаем одну из трех последовательностей.

Вся последовательность действий для каждого из движков была взята из статьи указанной вначале.

Я не буду подробно комментировать таски, в целом их имена отражают суть всех действий.
приведём все три варианта:

kvmlab/roles/driver_provision/tasks/install_container_d.yml:

--- # tasks file for driver_provision, установка container-d - name: Скачиваем containerd   get_url:     url: "https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz"     dest: "/tmp/containerd-1.7.0-linux-amd64.tar.gz"  - name: Распаковываем архив   unarchive:     src: /tmp/containerd-1.7.0-linux-amd64.tar.gz     dest: /usr/local     copy: no  - name: Удаляем скачаный архив за ненадобностю   file:     path: "/tmp/containerd-1.7.0-linux-amd64.tar.gz"     state: absent  - name: Создание директории для конфигурации containerd   file:     path: /etc/containerd/     state: directory  - name: Проверяем создан ли каталог   stat:     path: /etc/containerd   register: containerd_dir  - name: Создание конфиг файла containerd   become: true   command: "sh -c 'containerd config default > /etc/containerd/config.toml'"   when: containerd_dir.stat.exists  - name: конфигурирование cgroup driver   replace:     path: "/etc/containerd/config.toml"     regexp: "SystemdCgroup = false"     replace: "SystemdCgroup = true"  - name: Скачиваем containerd systemd service file   get_url:     url: "https://raw.githubusercontent.com/containerd/containerd/main/containerd.service"     dest: "/etc/systemd/system/containerd.service"  - name: Скачиваем и устанавливаем runc   get_url:     url: "https://github.com/opencontainers/runc/releases/download/v1.1.4/runc.amd64"     dest: "/usr/local/sbin/runc"     mode: "u+x"  - name: Скачиваем CNI plugins   get_url:     url: "https://github.com/containernetworking/plugins/releases/download/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz"     dest: "/tmp/cni-plugins-linux-amd64-v1.2.0.tgz"  - name: Распаковываем CNI plugins archive   unarchive:     src: "/tmp/cni-plugins-linux-amd64-v1.2.0.tgz"     dest: "/opt/cni/bin"     copy: no  - name: Удаляем CNI plugins archive   file:     path: "/tmp/cni-plugins-linux-amd64-v1.2.0.tgz"     state: absent  - name: Перезагрузка systemd   systemd:     daemon_reload: yes  - name: Запуск и активация containerd service   systemd:     name: containerd     state: started     enabled: yes

kvmlab/roles/driver_provision/tasks/install_crio.yml:

--- # tasks file for driver_provision, установка cri-o - name: Установка ключа репозитория cri-o   apt_key:     url: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/{{ version }}/{{ os }}/Release.key     state: present  - name: Установка репозитория cri-o   apt_repository:     repo: 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/{{ os }}/ /'     filename: devel:kubic:libcontainers:stable.list  - name: Установка репозитория cri-ostable/cri-o   apt_repository:     repo: 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/{{ version }}/{{ os }}/ /'     filename: 'devel:kubic:libcontainers:stable:cri-o:{{ version }}.list'  - name: Установка cri-o   apt:     name: ['cri-o', 'cri-o-runc']     state: latest  - name: Создание каталога /var/lib/crio   file:     path: /var/lib/crio     state: directory      - name: Перезагрузка systemd   systemd:     daemon_reload: yes  - name: запуск служб crio   systemd:     name: crio     enabled: yes     state: started

kvmlab/roles/driver_provision/tasks/install_docker_cri.yml:

--- # tasks file for driver_provision, установка docker + cri - name: Create directory /etc/apt/keyrings   file:     path: /etc/apt/keyrings     state: directory     mode: '0755'  - name: Add GPG key Docker   ansible.builtin.shell:  curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/docker.gpg --yes  - name: Get dpkg architecture   shell: "dpkg --print-architecture"   register: architecture  - name: Get lsb release   shell: "lsb_release -cs"   register: release_output  - name: Add Docker repository   apt_repository:     repo: "deb [arch={{ architecture.stdout_lines | join }} signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://download.docker.com/linux/debian {{ release_output.stdout_lines | join }} stable"     state: present   register: docker_repo  - name: Apt Update   ansible.builtin.apt:     update_cache: yes  - name: Install Docker   apt:     name:       - docker-ce       - docker-ce-cli       - containerd.io       - docker-compose-plugin     state: present  - name: Download plugin cri-dockerd   get_url:     url: "https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.1/cri-dockerd-0.3.1.amd64.tgz"     dest: "/tmp/cri-dockerd.tgz"  - name: Unpack cri-dockerd   unarchive:     src: "/tmp/cri-dockerd.tgz"     dest: "/tmp/"     copy: no  - name: Copy unpacked bin cri-dockerd   copy:     dest: "/usr/local/bin/"     src: "/tmp/cri-dockerd/cri-dockerd"     force: no     remote_src: yes      mode: 0660   register: copy_results  - name: change alc on cri-dockerd   file:     path: "/usr/local/bin/cri-dockerd"     mode: "0755"  - name: Download config file on cri-dockerd.service   get_url:     url: "https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service"     dest: "/etc/systemd/system/cri-docker.service"  - name: Download config file on cri-dockerd.socket   get_url:     url: "https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket"     dest: "/etc/systemd/system/cri-docker.socket"  - name: Update cri-docker.service   ansible.builtin.shell:  "sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service"  - name: daemon reload   systemd:     daemon_reload: yes  - name: enable cri-docker.service   systemd:     name: cri-docker.service     enabled: yes     state: started  - name: enable cri-dockerd.socket   systemd:     name: cri-docker.socket     enabled: yes     state: started

Так, готово. после этапа установки движка идёт еще один play для localhost для создания снапшота.

- name: Создаем снапшот driver_provision   hosts: localhost   become: yes   tasks:     - name: Создаем снимки       include_role:         name: vm_provision         tasks_from: create_snapshot.yml       vars:         vm_name: "{{ item.name }}"         snapshot_name: "driver_provision"         snapshot_description: "Движок установлен, нода подготовлена к инициализации k8s"       when: variant == 'ha-cluster' or item.name == 'node1'       with_items: "{{ vm_info.vm_names }}"

В целом так же опциональный, можно удалить.

Bтак, осталось самое важное, ради чего всё это начиналось.

Инициализация кубера!

возвращаемся в setup_k8s.yaml и дописываем следующий play.

- name: Настройка kubernetes [all-in-one либо ha-cluster]   hosts: all   gather_facts: true   become: true   remote_user: root   tasks:     - name: Установка all-in-one       include_role:         name: k8s_all_in_one         tasks_from: all_in_one.yml       when: variant == "all-in-one" and inventory_hostname == 'node1'      - name: Подготовка нод для ha-cluster       include_role:         name: k8s_ha_cluster         tasks_from: ha_cluster_prepare_managers.yml       when: variant == "ha-cluster"      - name: Установка первой ноды       include_role:         name: k8s_ha_cluster         tasks_from: ha_cluster_first_node.yml       when: variant == "ha-cluster" and inventory_hostname == 'node1' and inventory_hostname in groups['management']       register: first_node_result       - name: Передача команд на остальные ноды       set_fact:         control_plane_join_command: "{{ hostvars['node1']['control_plane_join_command'] }}"         worker_join_command: "{{ hostvars['node1']['worker_join_command'] }}"       when: variant == "ha-cluster" and inventory_hostname != 'node1'      - name: вывод команд подключения       debug:         msg: |           control_plane_join_command: {{ control_plane_join_command }}           worker_join_command: {{ worker_join_command }}       when: variant == "ha-cluster" and inventory_hostname == 'node1'      - name: Использование команды control_plane_join_command       block:         - name: Подключение управляющих нод для ['container-d', 'cri-o']           ansible.builtin.shell:             cmd: "{{ control_plane_join_command }}"           until: result.rc == 0           register: result           retries: 5           delay: 30           when: engine in ['container-d', 'cri-o']          - name: Подключение управляющих нод для docker           ansible.builtin.shell:             cmd: "{{ control_plane_join_command }} --cri-socket unix:///var/run/cri-dockerd.sock"           until: result.rc == 0           register: result           retries: 5           delay: 30           when: engine == 'docker'       when: variant == "ha-cluster" and inventory_hostname != 'node1' and inventory_hostname in groups['management']       - name: Использование команды worker_join_command       block:         - name: Подключение рабочих нод для ['container-d', 'cri-o']           ansible.builtin.shell:             cmd: "{{ worker_join_command }}"           until: result.rc == 0           register: result           retries: 5           delay: 30           when: engine in ['container-d', 'cri-o']          - name: Подключение рабочих нод для docker           ansible.builtin.shell:             cmd: "{{ worker_join_command }} --cri-socket unix:///var/run/cri-dockerd.sock"           until: result.rc == 0           register: result           retries: 5           delay: 30           when: engine == 'docker'       when: variant == "ha-cluster" and inventory_hostname != 'node1' and inventory_hostname in groups['workers']      - name: Скачивание конфига с первой ноды (подходит для обоих вариантов all-in-one и ha-cluster)       ansible.builtin.fetch:         src: /etc/kubernetes/admin.conf         dest: /tmp/         flat: yes         force: yes       when: inventory_hostname == 'node1'      - name: Перезагрузка всех машин       ansible.builtin.reboot:         reboot_timeout: 300

Тут для установки используются две роли (можно было и одной обойтись но так нагляднее).

Начнем пожалуй с all-in-one варианта установки, он самый простой:

roles/k8s_all_in_one/tasks/all_in_one.yml:

--- - name: Проверка наличия файла конфига   stat:     path: /etc/kubernetes/admin.conf   register: file_info  - name: Инициализация кластера если конфиг не обнаружен.   block:     - name: Инициализация кластера для движков ['container-d', 'cri-o']       shell: kubeadm init --pod-network-cidr=10.244.0.0/16       when: engine in ['container-d', 'cri-o']       register: kubeadm_output      - name: Инициализация кластера для движка docker       shell: |         kubeadm init \                --pod-network-cidr=10.244.0.0/16 \                --cri-socket unix:///var/run/cri-dockerd.sock       when: engine == 'docker'       register: kubeadm_output      - name: Установка KUBECONFIG в enviroment       become: true       lineinfile:         dest: /etc/environment         line: 'export KUBECONFIG=/etc/kubernetes/admin.conf'      - name: Установка KUBECONFIG в bashrc       become: true       lineinfile:         dest: '~/.bashrc'         line: 'export KUBECONFIG=/etc/kubernetes/admin.conf'      - name: Подождем пока всё запустится       wait_for:         host: localhost         port: 6443         timeout: 300      - name: Установка сетевого плагина Flannel       shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml      - name: Снятие ограничения на запуск рабочих нагрузок c {{ ansible_hostname }}       shell: "kubectl taint nodes --all node-role.kubernetes.io/control-plane-"       become:roles/k8s_all_in_one/tasks/all_in_one.yml: true       register: taint_result       failed_when:         - "'error: taint \"node-role.kubernetes.io/control-plane\" not found' not in taint_result.stderr"         - "'node/' + ansible_hostname + '.internal untainted' not in taint_result.stdout"   when: not file_info.stat.exists  - name: Проверка инициализации   shell: "export KUBECONFIG=/etc/kubernetes/admin.conf && kubectl get nodes"   register: kubectl_output   ignore_errors: true  - name: Инициализация завершена.   debug:     msg: 'Инициализация завершена! выполните комманду export KUBECONFIG=/etc/kubernetes/admin.conf, проверьте вывод команды kubectl get nodes'   when: kubectl_output.rc == 0

Что в нем происходит.

Проверяем есть ли файл конфига. логика проста, если файл есть то с большой долей вероятности инициализация уже была и отчасти успешна. в этом случае если не работает, то лучше убить ноду и собрать заново.

Если же файла нет, то в зависимости от движка идёт команда инициализации (для докера она идёт с доп параметрами).

Устанавливается сетевой плагин, снимаются ограничения и проверяется установка.

всё, стенд готов.

Теперь давай пробежимся по ha_cluster.

Тут всё немного сложнее.
первое что надо сделать это подготовить ноды, а именно настроить keepalived и haproxy, для обеспечения отказоустойчивости и балансировки нагрузки.

roles/k8s_ha_cluster/tasks/ha_cluster_prepare_managers.yml

- name: Синхронизация даты/времени с NTP сервером   shell: ntpdate 0.europe.pool.ntp.org  - name: Копируем настройку демона keepalived   template:     src: templates/keepalived.conf.j2     dest: /etc/keepalived/keepalived.conf     mode: '0644'  - name: Копируем скрипт check_apiserver.sh, предназначенный для проверки доступности серверов.   template:     src: templates/check_apiserver.sh.j2     dest: /etc/keepalived/check_apiserver.sh     mode: '0755'  - name: запуск службы keepalived   systemd:     name: keepalived     enabled: yes     state: restarted  - name: Копируем настройку демона haproxy   template:     src: templates/haproxy.cfg.j2     dest: /etc/haproxy/haproxy.cfg     mode: '0644'  - name: запуск службы haproxy   systemd:     name: haproxy     enabled: yes     state: restarted

Вторым шагом удет установка первой ноды. важный процесс, ибо после инициализации кубера он выдает команды для добавления новых хостов, которые нам надо будет передать на выполнение следующим нодам.

В целом суть та же, проверяем конфиг, если его нет, то делаем инициализацию. для docker команда чуть побольше.

После инициализации фильтруем вывод регуляркой и сохраняем для передачи остальным нодам. экспортируем конфиг, устанавливаем сетевой плагин и идём дальше.

ha_cluster_first_node.yml: - name: Проверка наличия файла конфига   stat:     path: /etc/kubernetes/admin.conf   register: file_info  - name: Инициализация кластера если конфиг не обнаружен.   block:     - name: Инициализация кластера для движков ['container-d', 'cri-o']       shell: |         kubeadm init \                 --pod-network-cidr=10.244.0.0/16 \                 --control-plane-endpoint "172.30.0.210:8888" \                 --upload-certs       register: init_output_containerd_crio       when: engine in ['container-d', 'cri-o']       - name: Инициализация кластера для движка ['docker']       shell: |         kubeadm init \                 --cri-socket unix:///var/run/cri-dockerd.sock \                 --pod-network-cidr=10.244.0.0/16 \                 --control-plane-endpoint "172.30.0.210:8888" \                 --upload-certs       register: init_output_docker       when: engine == 'docker'      - name: Сохранение значения init_output для дальнейшего использования       set_fact:         init_output: "{{ init_output_containerd_crio if init_output_containerd_crio is defined and init_output_containerd_crio.stdout is defined else init_output_docker }}"      - name: Фильтрация вывода kubeadm init       set_fact:         filtered_output: "{{ init_output.stdout | regex_replace('(\\n|\\t|\\\\n|\\\\)', ' ') }}"      - name: Фильтр комманд для добавления управляющих и рабочих нод       set_fact:         control_plane_join_command: "{{ filtered_output | regex_search('kubeadm join(.*?--discovery-token-ca-cert-hash\\s+sha256:[\\w:]+.*?--control-plane.*?--certificate-key.*?[\\w:]+)')}}"         worker_join_command: "{{ filtered_output | regex_search('kubeadm join(.*?--discovery-token-ca-cert-hash\\s+sha256:[\\w:]+)')}}"      - name: Установка KUBECONFIG в enviroment       lineinfile:         dest: /etc/environment         line: 'export KUBECONFIG=/etc/kubernetes/admin.conf'      - name: Установка KUBECONFIG в bashrc       lineinfile:         dest: '~/.bashrc'         line: 'export KUBECONFIG=/etc/kubernetes/admin.conf'      - name: Подождем пока всё запустится       wait_for:         host: localhost         port: 6443         timeout: 300      - name: Установка сетевого плагина Flannel       shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml    when: not file_info.stat.exists

После успешной инициализации кластера и получения команд для добавления нод, мы используем эти команды чтобы добавить управляющие и рабочие ноды.

И снова ветвление ибо у докера есть доп параметры при установке.

Есть нюанс, управляющие ноды иногда по неизвестной мне причине не добавлялись. при этом при повторном запуске команды всё проходило нормально. Поэтому я добавил 5 попыток подключения. обычно хватает двух.

С воркерами такого не наблюдалось, однако я всё равно добавил те же 5 попыток.

Воркер или управляющая нода определяется из группы в inventory.

Готово, перезагружаем все машины и ждем загрузки.

Последний play скопирует конфиг с первой ноды на вашу локальную машину. чтобы можно было управлять кластером непосредственно с хоста. он так же опционален, можно просто зайти на первую ноду и запускать деплои оттуда.

- name: Настройка хостовой машины, чтобы не лазить постоянно на виртуальные.   hosts: localhost   gather_facts: false   tasks:     - name: Переместить файл       ansible.builtin.file:         src: /tmp/admin.conf         dest: /etc/kubernetes/admin.conf         state: link         force: yes       become: true      - name: Установка KUBECONFIG в enviroment       lineinfile:         dest: /etc/environment         line: 'export KUBECONFIG=/etc/kubernetes/admin.conf'      - name: Установка KUBECONFIG в bashrc       lineinfile:         dest: '~/.bashrc'         line: 'export KUBECONFIG=/etc/kubernetes/admin.conf'

Бонусом идёт удаление стенда. раз он быстро создаётся то должен быстро и исчезать.

Удаляются только ВМ их диски и снапшоты, шаблоны и образы остаются в каталогах хранения
remove_stand.yml:

---   - name: Удаление стенда kubernetes     hosts: localhost     become: trueс     vars_files:       - my_vars.yml     tasks:       - name: Получаем список существующих ВМ         community.libvirt.virt:           command: list_vms         register: existing_vms         changed_when: no        - name: Удаление машин         block:           - name: Полностью останавливаем ВМ             community.libvirt.virt:               command: destroy               name: "{{ item.name }}"             loop: "{{ vm_info.vm_names }}"             when: "item.name in existing_vms.list_vms"             ignore_errors: true            - name: Удаляем снапшоты             shell: |               virsh snapshot-delete --domain {{ item.name }} --snapshotname host_provision               virsh snapshot-delete --domain {{ item.name }} --snapshotname driver_provision             ignore_errors: true             loop: "{{ vm_info.vm_names }}"             when: "item.name in existing_vms.list_vms"            - name: Отменяем регистрацию ВМ             community.libvirt.virt:               command: undefine               name: "{{ item.name }}"             loop: "{{ vm_info.vm_names }}"             when: "item.name in existing_vms.list_vms"            - name: Удаление диска виртуальной машины             ansible.builtin.file:               path: "{{libvirt_pool_dir}}/{{ item.name }}.qcow2"               state: absent             loop: "{{ vm_info.vm_names }}"             when: "item.name in existing_vms.list_vms"

В целом всё готово. можно запускать.

Установка стенда:

ansible-playbook -K ./setup_k8s.yaml -i ./inventory --extra-vars "@my_vars.yml"

Удаление стенда:

ansible-playbook -K ./remove_stand.yml

В общем и целом я просто перенёс готовый гайд на рельсы автоматизации, отсебятины я добавил по минимуму. где то иначе добавляются репозитории и ключи, где то запускаю синхронизацию времени, из за того что при восстановлении со снапшота у меня начинались проблемы с ключами из за неверной текущей даты.

Добавляю ссылку на репозиторий со всем этим добром.

В решении я попробовал много различных элементов управления ansible, создание каталогов, циклы, ветвления, установки и прочие кирпичи из которых вырастает система.

Буду рад если вы подскажете какие решения были удачными, а какие не очень. эта информация будет очень полезна для меня.

Спасибо что осилили и прочли до конца!)

Отдельное спасибо @imbasoft за отличную и понятную статью.


ссылка на оригинал статьи https://habr.com/ru/articles/751582/

5 примечательных IT-компаний из Вьетнама

В последние годы экономика Вьетнама бурно растет, и немалую лепту в общий прогресс страны внесла IT-индустрия. Низкая стоимость рабочей силы (к тому же высококвалифицированной) — основа быстрого расширения сектора информационных технологий. Сегодня Вьетнам является восьмой по величине страной в мире, предлагающей IT-услуги. Даже сельское хозяйство и торговля ушли на задний план.

Соревнуясь с Сингапуром в области разработки программного обеспечения и экспорта за границу, Вьетнам гордится тем, что взрастил в стране крупные проекты, поставляя высококачественную продукцию международным клиентам. О пяти таких компаниях и пойдет речь в нашем материале. 

FPT Software

FPT Software со штаб-квартирой в Ханое — одно из старейших технологических предприятий. Она обслуживает более 1 000 клиентов по всему миру, 85 из которых входят в список Fortune Global 500 в аэрокосмической, авиационной, автомобильной, банковской, финансовой, страховой и других отраслях. В штате фирмы более 27 000 сотрудников, а ее филиалы можно найти в Японии, Америке и Европе. Компания уже вложила значительные средства в свою инфраструктуру в Дананге, где в том числе строится комплекс FPT, в котором будут работать до 2 500 специалистов. Корпорация планирует открыть собственный университет FPT University для обучения айтишников. 

CMC Corporation

CMC — эта та фирма, которую мы не можем упустить из виду, если речь идет о лучших IT-компаниях Вьетнама. Она зарекомендовала себя на внутреннем и международном рынках. При этом свою работу она начала еще 30 лет. Сейчас компания задействована в следующих областях: технологии и решения, глобальный бизнес и телекоммуникации. 

CMC создала и развила профессиональную рабочую среду для более чем 3 000 сотрудников и добилась значительных успехов в производственной и коммерческой деятельности. В течение многих лет подряд ей удается поддерживать высокие темпы роста. 

Newwave Solutions

Компания Newwave Solutions является признанным поставщиком программных решений с поддержкой прорывных технологий, используемых в мобильных устройствах, интернете, облачных сервисах, блокчейне, искусственном интеллекте и больших данных. Вот уже 11 лет она предоставляет высококачественные аутсорсинговые услуги для предприятий и стартапов по доступной цене. 

Недвижимость, здравоохранение, финансы, маркетинг и реклама — вот некоторые из областей, в которых работает Newwave Solutions. Цель фирмы — предложить клиентам качественные аутсорсинговые услуги по новым технологиям, повышающим их производительность. 

Rikkeisoft

Rikkeisoft была основана в 2012 году. Штаб–квартира у нее, как и многих других IT-компаний коммунистического государства находится в Ханое. Особое внимание фирма уделяет исследованиям и внедрениям технологий-4.0, включая облачные сервисы, блокчейн, искусственный интеллект и большие данные. Rikkeisoft получила награду IT World Award в 2022 году в области технологических услуг и решений. Компания в последние годы активно расширяет штат в том числе за счет экспатов. 

Saigon Technology

Saigon Technology — ведущая вьетнамская компания по разработке программного обеспечения в Хошимине. Она была основана также в 2012 году, но уже имеет офисы по всему миру: в Сингапуре, Австралии, Швейцарии и США. Saigon Technology сосредоточилась на услугах в следующих сферах: .NET Core / MVC, Java, PHP, NodeJS, Angular, ReactJS, iOS и разработке приложений для Android. 

На протяжении последних летSaigon Technology удостаивалась различных международных наград, среди которых, например, Excellence Award за услуги аутсорсинга программного обеспечения. В 2020 году VINASA (Вьетнамская ассоциация программного обеспечения) включила Saigon Technology в список 10 лучших ведущих аутсорсинговых компаний в области ПО. Фирма также партнер Microsoft в области разработки приложений. 

О сервисе Онлайн Патент

Онлайн Патент — цифровая система № 1 в рейтинге Роспатента. С 2013 года мы создаем уникальные LegalTech‑решения для защиты и управления интеллектуальной собственностью. Зарегистрируйтесь в сервисе Онлайн‑Патент и получите доступ к следующим услугам:

  • Онлайн‑регистрация программ, патентов на изобретение, товарных знаков, промышленного дизайна;

  • Подача заявки на внесение в реестр отечественного ПО;

  • Опции ускоренного оформления услуг;

  • Бесплатный поиск по базам патентов, программ, товарных знаков;

  • Мониторинги новых заявок по критериям;

  • Онлайн‑поддержку специалистов.

Больше статей, аналитики от экспертов и полезной информации о интеллектуальной собственности в России и мире ищите в нашем Телеграм‑канале.

Получите скидку в 1000 рублей на первый заказ. Подробнее в закрепленном посте


ссылка на оригинал статьи https://habr.com/ru/companies/onlinepatent/articles/751566/

Права в Django и django-rest-framework

Я Python Developer в компании Нетрика. В данной статье расскажу, как устроены права в Django и django-rest-framework и как мы используем их на одном из проектов.

  1. Django

В Django есть модели User, которая представляет пользователей вашей системы, и Group, которая представляет наборы пользователей. Также Django поставляется со встроенной системой прав. Она предоставляет возможность назначать права пользователям и группам пользователей.

Например, она используется в админке. Django админка использует разрешения следующим образом:

  • Доступ к просмотру объектов определённого типа есть у тех пользователей, у которых есть право на просмотр и изменение.

  • Доступ к добавлению объектов определённого типа есть у тех пользователей, у которых есть право на добавление.

  • Доступ к просмотру истории изменений и изменению объектов определённого типа есть у пользователей, у которых есть право на изменение.

  • Доступ к удалению объектов определённого типа есть у пользователей, у которых есть право на удаление.

К слову, права можно устанавливать не только по типу объекта, но так же и по конкретным объектам. Для этого можно переопределить методы has_view_permission, has_add_permission, has_change_permission и has_delete_permission класса ModelAdmin.

В моделе User есть many-to-many поля groups и user_permissions.

Когда django.contrib.auth представлен в настройке INSTALLED_APPS, то при вызове ./manage.py migrate создаются права для всех зарегистрированных поделей. Функциональность, которая создаёт разрешения, находится в сигнале post_migrate.

Предположим, что у вас есть приложение foo и модель Bar в нём, проверить наличие у пользователя прав для этой модели можно следующим образом:

  • add: user.has_perm(«foo.add_bar»)

  • change: user.has_perm(foo.change_bar»)

  • delete: user.hes_perm(«foo.delete_bar»)

  • view: user.has_perm(«foo.view_bar»)

Модель Group предназначена для категоризации пользователей, так вы можете предоставлять права или что-нибудь ещё какой-то группе пользователей. Пользователь может принадлежать произвольному количеству групп. Пользователь получает все права, которые назначены его группе.

Так же через группы можно дать набору пользователей какой-нибудь ярлык или расширенную функциональность. Например, можно отправлять email только пользователям из какой-то группы.

  1. django-rest-framework

Права в django-rest-framework определяют, следует ли удовлетворить или отклонить запрос к различным частям вашего API. Например, права могут проверять, что пользователь аутентифицирован (IsAuthenticated). Они всегда запускаются перед любым кодом вашего представления.

Где определяются права

Права в django-rest-framework всегда определяются как список классов прав. Каждое право из списка проверяется перед запуском тела вашего представления. Если хотя бы одно из них не проходит, то одно из исключений (exceptions.PermissionDenied или exceptions.NotAuthenticated) выбрасывается и код вашего представления не запускатеся.

Возвращается 403 или 401 соответственно статус ответа согласно следующим правилам:

  • Запрос был аутентифицирован, но право не предоставлено — 403 Forbidden.

  • Запрос не был аутентифицирован и класс для аутентификации с наивысшим приоритетом не использует WWW-Authenticate заголовок — 403 Forbidden.

  • Запрос не был аутентифицирован и заголовок используется — 401 Unauthorized.

Права можно задать глобально, используя настройку DEFAULT_PERMISSION_CLASSES:

REST_FRAMEWORK = {   "DEFAULT_PERMISSION_CLASSES": [     "rest_framework.permissions.IsAuthenticated",   ] }

Права можно задать для вашего представления используя permission_classes атрибут или get_permissions метод:

from rest_framework.dПрава можно задать глобально, используя настройку DEFAULT_PERMISSION_CLASSES:  ecorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response   @api_view(["GET"]) @permission_classes([IsAuthenticated]) def example_view(request, format=None):   return Response()

Когда права определяются на уровне представления, то права в settings.py игнорируются.

Все права наследуются от BasePermission и могут быть объеденены в цепочки, используя побитовые операции (| (or), & (and), ~(not)).

Встроенные права

AllowAny

Это право предоставляет неограниченный доступ, не зависимо от того является ли запрос аутентифицированным или нет.

IsAuthenticated

Это право предоставляет доступ только аутентифицированным пользователя, иначе отклоняет запрос.

IsAdminUser

Это право предоставляет доступ только администраторам, т.е. пользователям, у которых поле is_staff равно True.

IsAuthenticatedOrReadOnly

Это право позволяет аутентифицированным пользователям выполнять любой тип запроса. Иначе запрос выполнится только в том случае, если его метод «безопасный» (GET, HEAD или OPTIONS).

DjangoModelPermissions

Это право связано со стандартными разрешениями django, расположенными в django.contrib.auth. Для его использования в вашем представлении должно быть объявлено либо .queryset свойство, либо .get_queryset() метод. Запрос будет разрешён только в том случае, если пользователь аутентифицирован и имеет соответствующее джанговское право для модели. Модель определяется по .get_queryset().model или .queryset.model.

  • POST запросы требуют, чтобы у пользователя было add право на модель.

  • PUT и PATCH запросы требуют, чтобы у пользователя было change право на модель.

  • DELETE запросы требуют, чтобы у пользователя было delete право на модель.

Также можно добавить свои джанговские права, переопределив право DjangoModelPermissions. Например, можно добавить право на просмотр модели (view) для GET-запросов. Для этого нужно переопределить .perms_map свойство.

DjangoModelPermissionsOrAnonReadOnly

Схоже с предыдущим, только неаутентфицированные пользователи получают доступ на чтение.

DjangoObjectPermissions

Как и DjangoModelPermissions это право связано со стандартными джанговскими правами. Оно позволяет работать с правами на уровне объектов. Но, чтобы его использовать, нужно добавить бэкенд, которых поддерживает права на уровне объектов (например, django-guardian).

Для view права можно использовать DjangoObjectPermissionsFilter класс из django-guardian, который возвращает только объекты, для которых у пользователя есть соответствующее право.

Пользовательские разрешения

Для того, чтобы реализовать своё право, нужно переопределить BasePermission и реализовать все (или один) из методов:

  • .has_permission(self, request, view)

  • .has_object_permission(self, request, view, obj)

Эти методы должны возвращать True, если запрос разрешён, иначе — False.

Если в вашем праве, нужно проверять является ли запрос «безопасным», можно использовать константу SAFE_METHODS, которая является кортежем, содержащим ‘GET’, ‘OPTIONS’ и ‘HEAD’. Например:

if request.method in permissions.SAFE_METHODS:   # read-only запрос else:   # запрос на запись

Метод has_object_permission вызывается, если метод has_permission пройдёт успешно.

Отметим, что generic представления проверяют права для объекта (has_object_permission) только тогда, когда запрашивается один объект. Если необходимо фильтровать список объектов, нужно фильтровать queryset отдельно в методе .get_queryset() вашего представления.

  1. Опыт использования прав на одном из проектов

Ниже расскажу про использования прав на одном из проектов. Но немного про сам проект.

Мы разрабатываем комплексное решение для автоматизации проектной деятельности, сам продукт — это по сути отечественный аналог MS Project с адаптацией под потребности российских компаний.

Особенность в том, что в продукте работают все участники проектной деятельности от топ-менеджмента до непосредственных исполнителей + смежные подразделения компаний + администратор системы. Нужно было создать достаточно сложную модель прав с учетом всей функциональности продукта и предусмотреть отдельные права для каждой роли пользователя в разных сценариях:

  • ведение проектов, программ и портфелей проектов, работа с инициативами для всех участников

  • отдельный кабинет для топ-менеджмента с контролем реализации проектов и верхнеуровневой аналитикой

  • работа с запросами на изменения и многоступенчатыми согласованиями с участием пользователей из смежных подразделений

Модели User и UserGroup

Мы не используем стандартные джанговские права. Вместо этого права представлены следующим образом:

USER_PERMISSION = Choices(   ...   ('project_view', 'Право на просмотр проектов'),   ('project_add', 'Право на создание проектов'),   ('project_change', 'Право на редактирование проектов'),   ... )

И храняться в модели как кастомное поле ChoiceArrayField:

class User(PermissionModelMixin, AbstractUser):   ...   groups = models.ManyToManyField(     UserGroup,     verbose_name='Группы',     related_name='user_set',     related_query_name='user',     ...   )   permissions = ChoiceArrayField(     models.CharField(max_length=32, blank=True, choices=USER_PERMISSION),     blank=True,     default=list,     verbose_name='Права',   )   ...

Также мы не используем стандартную модель для групп, вместо этого у нас своя — UserGroup. Группы в моделе User хранятся как поле groups — many-to-many поле на модель UserGroup. Права и группы можно выбрать в админке на странице редактирования пользователя.

Чтобы была возможность проверять права у пользователя через метод .has_perm() (user.has_perm(USER_PERMISSION.project_add)), был добавлен свой бэкенд:

class ISUPModelBackend(ModelBackend):   ...   def _get_user_permissions(self, user_obj):       return set(user_obj.permissions)    def _get_group_permissions(self, user_obj):       perms = sum(user_obj.groups.values_list('permissions', flat=True), [])       perms = set(perms)        return perms   ...

Представления

class ProjectViewSet(...):   ...   permission_classes = [IsAuthenticated, ProjectPermission]   ...

Модели и миксин PermissionModelMixin

Миксин для работы требует наличие атрибута authorization_class и определяет метод .can():

class PermissionModelMixin:   authorization_class: Type[ModelAuthorization]    def can(self, user: User, action: str) -> bool:     authorization = self.authorization_class(self, user)     can = authorization.can(action)     return can ...

Модели наследуются от PermissionModelMixin и определяют атрибут authorization_class — класс, который наследуется от класса ModelAuthorization. Эти классы так же определяют метод .can():

class Project(..., PermissionModelMixin, ...):   ...   authorization_class = ProjectAuthorization   ...

Права

Все наши права наследуются от базового класса ModelAuthorizationPermission, который в свою очередь наследуется от BasePermission, определённого в django-rest-framework.

class ModelAuthorizationPermission(BasePermission):   authorization_action_mapping: Dict[str, str] = {}   ...    def has_permission(self, request, view):     ...     action = self.authorization_action_mapping.get(view.action)      ...      parent = ...      if action:         return parent.can(request.user, action)      return False    def has_object_permission(self, request, view, obj):       action = self.authorization_action_mapping.get(view.action)        ...        if action:           return obj.can(request.user, action)        return False

Метод .can() определён в миксине PermissionModelMixin, от которого наследуются наши модели.

Каждое наше право определяет аттрибут-словарь authorization_action_mapping, который представляет собой отображение view action на права в системе.

class ProjectPermission(ModelAuthorizationPermission):   authorization_action_mapping = {     'create': USER_ACTIONS.project_add,     'create_with_offer': USER_ACTIONS.project_add,     'create_with_template': USER_ACTIONS.project_add,     'create_with_federal_project': USER_ACTIONS.project_add,     'update': PROJECT_ACTIONS.change,     'destroy': PROJECT_ACTIONS.delete,     'change_protocol': PROJECT_ACTIONS.change_protocol,     'change_visibilities': PROJECT_ACTIONS.change_visibilities,     'status_info': PROJECT_ACTIONS.view_status_info,     'save_basic_plan': PROJECT_ACTIONS.save_basic_plan,     'start': PROJECT_ACTIONS.change,   }

Класс ModelAuthorization и наследники

Эти классы и занимаются проверкой прав.

Класс ModelAuthorization определяет метод .can():

class ModelAuthorization(metaclass=abc.ABCMeta):   actions = abc.abstractproperty    def __init__(self, instance: Model, user: User) -> None:     self.instance = instance     self.user = user    def can(self, action: str) -> bool:     if not self._is_ACTION_allowed(action):         return False      return self._has_ACTION_access(action)    def _is_ACTION_allowed(self, action: str) -> bool:     if action not in self.actions:         return False      checker_name = f'is_{action}_allowed'     checker = getattr(self, checker_name)      return checker()    def _has_ACTION_access(self, action: str) -> bool:     checker_name = f'has_{action}_access'     checker = getattr(self, checker_name)      return checker()

А его наследники — допустимые права и по два метода для каждого из них:

class ProjectAuthorization(ExternalModelAuthorizationMixin, ModelAuthorization):   instance: Project   actions = PROJECT_ACTIONS    ...    def is_change_allowed(self) -> bool:     return self.instance.status in {       PROJECT_STATUS_CHOICES.draft,       PROJECT_STATUS_CHOICES.reopened,     }    def has_change_access(self) -> bool:     is_developer = self.instance.is_developer(self.user)     has_perm = self.user.has_perm(USER_PERMISSION.project_change)      return is_developer or has_perm    ...

Всё вместе

При определении модели наследуем её от PermissionModelMixin. Определяем для неё класс, наследуемый от класса ModelAuthorization. В нём для каждого права определяем два метода is_custom_permission_allowed и has_custom_permission_access. В этих методах определяем логику проверки у пользователя прав на совершение данного действия.

Определяем класс, наследуемый от класса ModelAuthorizationPermission — наше право. В нём задаём словарь — отображение action из ViewSet — на право в системе. Добавляем этот класс в соответствующий ViewSet.

Какие плюсы у данной реализации

Стандартные джанговские права позволяют проверять права на CRUD операции. Наша же реализация позволяет в ModelAuthorization проверять права отличные от CRUD.


ссылка на оригинал статьи https://habr.com/ru/articles/751586/

Ноутбуче, исцелися сам! Как я материнку Lenovo Ideapad ремонтировал и экран ему менял

2023-07-31-12-53-IMG-7407

Привет, Хабр! Сегодня снова поговорим о ремонте ноутбуков. Вернее, об одном из них, модель которого — Lenovo Ideapad 330-15igm. Это рабочая лошадка со средними характеристиками, но для офисной работы или там фильмов — отлично подходит. Купил я этот ноутбук евро за 25, на испанской онлайн-барахолке. У него нет одной кнопки, разбит экран, и вообще компьютер не включается и не заряжается. Его поломка — одна из типовых проблем многих ноутбуков, так что, думаю, все это будет интересно широкому кругу читателей.

Что за ноутбук?


2023-07-31-12-51-IMG-7402

Давайте чуть подробнее о самой модели. Они, как и упоминалось выше, средние, но не ужасные:

  • Процессор: Intel Pentium Silver N5000 1.1 — 2.7 ГГц
  • Оперативная память: 4096 Мб DDR4.
  • Жесткий диск HDD: 500 Гб
  • Дисплей: 15.6″ Full HD 1920×1080.
  • Видеокарта: Intel UHD 605 Использует системную
  • ОС: DOS.
  • Цвет: Платина
  • Вес: 2.2 кг

Единственное отличие — в моем ноутбуке был установлен SSD объемом в 128 ГБ, а не HDD объемом в 500 ГБ. Все остальное — примерно такое же. Ну и попал он ко мне с предустановленной Windows 10, что выяснилось уже позже.

А что с ним стряслось?


Проблема типичная — не включается и не заряжается, никаких проявлений жизни. Светодиоды не светятся, вентиляторы не крутятся, мертвый ноутбук. Причин, кстати, может быть множество — от короткого замыкания после первого мосфета по линии 19В или других линий до проблем с коннектором порта зарядки.

С такими симптомами я перечинил несколько десятков ноутбуков. Некоторые из них преподносили чудные сюрпризы. Например, ожидаешь проблем с коротким замыканием, а там чуть не четверть платы выгорела, т.е. ремонту такое не подлежит. Или думаешь, что проблема с входным мосфетом, а на самом деле, вышел из строя процессор.

Но бывают и другие сюрпризы. Этот ноутбук стал одним из них. При этом сюрприз приятный, а поломка, насколько я могу судить, типовая, которую может попробовать в случае схожих симптомов любой пользователь, который умеет держать в руках отвертку.

Подробности, которых не хватает


Я выяснил, что ноутбук не работает, благодаря лабораторному блоку питания. При подключении его к ноутбуку он не показал потребления тока — никаких признаков. Тут ничего не поделать — нужно вскрывать ноутбук. Вот он уже открыт и притягивает взгляды любителей электроники.

2023-07-31-12-53-IMG-7406

Никаких особенных проблем я не заметил, поэтому решил перевернуть плату и посмотреть, что там с другой стороны. Оказалось, что в разъем зарядки каким-то образом когда-то давно проникла влага. Но тоже ничего особо критичного не было — я почистил ватной палочкой и изопропиловым спиртом, сняв окислы и очистив разводы. В целом, всего этого было не очень много — ниже видео, где есть вставка с микроскопа. Там хорошо виден мастштаб проблем.

После всех проделанных манипуляций я проверил подачу питания до первого мосфета (вот он, на фотке) и сразу после. Обычно если что-то не так с питаниями, то часто первый мосфет «не открывается», как принято говорить, т.е. не пропускает через себя питание.

11

И, точно, так и было — на входе в мосфет у нас 19В, а на выходе — 0,5В.

2023-07-31-01-04-IMG-7399

После того, как попытался подключить питание после первого мосфета — лабораторный источник питания показал замыкание. И с обратной стороны платы стал греться один из компонентов. Я посмотрел, что там может греться, увидел пару конденсаторов и еще один мосфет. Обычно, если SMD-конденсатор выходит из строя, он устраивает по всей цепи короткое замыкание. Удалишь такой компонент — проблема решена (не всегда, но часто).

Хорошо, так ноутбук отремонтирован?


Да, но здесь и начинается самое интересное. Перед тем, как идти в бой с «тяжелой артиллерией» вроде паяльника и паяльного фена, я решил отключить батарею BIOS-а (на фото она хорошо видна, такая батарейка-таблетка, подключенная к плате двумя проводками), замкнуть контакты батареи на плате и посмотреть, что получится и получится ли что-то.

Конечно, все это я делал при отключенном аккумуляторе — его нужно отключать в первую очередь, как только вы открыли ноутбук. Оставлять опасно. Можно что-то уронить на плату, капнуть водой или кофе, да мало ли что. Все это может обернуться дополнительными проблемами вроде сгоревших компонентов и короткого замыкания на плате.

В общем, после всех этих манипуляций я снова подключил снова источник питания — и, вуаля, светодиоды ноутбука замигали, а при подключении батареи — началась зарядка. Я решил проверить, все ли работает, и подключил экран и другие компоненты. Оказалось, что все функционирует, как и положено — ноутбук включился и без проблем запустил Windows.

Почему так случилось? Здесь у меня два варианта ответов. Первый — отключение батарейки BIOS (CMOS) и аккумулятора позволит вернуть фабричные настройки, плюс разряжаются конденсаторы и прочие компоненты на плате. Соответственно, если где-то «завис» какой-то контроллер, то после полной разрядки все возвращается на круги своя. Так бывает достаточно часто, и проблему действительно можно назвать типовой, поэтому я и решил о ней рассказать.

Что касается второго варианта — то, возможно, что-то не так было с теми компонентами, которые грелись. Я их на всякий пожарный протер ватной палочкой с изопропилом, хотя ничего подозрительного не увидел даже под микроскопом. Так что, скорее всего, актуален первый вариант.

И да, может быть еще и третий вариант — комбинация первых двух. Не стоит забывать еще, что был залит вход питания, и несколько мелких компонентов, которые были немного окислены. Так что их очистка вместе с другими действиями привела ноутбук в чувство.

2023-07-31-12-51-IMG-7400

Точно сказать не получится, поскольку вариантов может быть и больше, но узнать точно, что случилось, уже не получится. Да и в целом, это не первый такой случай. Иногда не работающий ноутбук сам по себе «чинится» — после снятия платы или вообще просто открытия крышки ноутбука. Иногда причина в «съехавшем» шлейфе, например, клавиатуры, что снаружи не увидеть, в глючном тачпаде или вообще непонятно чем. Я смотрел не раз и видео других мастеров, у которых случались «чудесные исцеления», по непонятной причине.

В общем, ноутбук тестирую — пока проблем не вижу.

И это еще не все


Кроме всего прочего, экран оказался поврежден, но и это не проблема. Когда часто ремонтируешь технику, в мастерской скапливается большое количество запасных частей. Они могут дорого стоить, если покупать, но мастеру достаются за копейки или вообще даром. Поэтому вопрос рентабельности ремонта не стоит — экран остался от другого ноутбука, который послужил донором.

Его я тоже поменял, для чего снова пришлось отключить батарею ноутбука. Повторюсь, при любых манипуляциях с материнской платой и экраном батарею нужно отключать. Если в случае замены экрана этого не сделать — то может сгореть и плата экрана, и материнская плата ноутбука. Отключение аккумулятора — один из самых важных шагов в ходе ремонта.

У меня оказался точно такой же экран, только не матовый, а глянцевый. Подключил его — и появилось чистое и красивое изображение. Вот ноутбук с новым экраном, который работает и старым, который уходит на покой — на снимке хорошо видно повреждение.

2023-07-31-13-21-IMG-7409

Ниже — видео ремонта (только без звука, камера отключила микрофон и все мои попытки смешно шутить и профессионально рассуждать о ремонте ноутбуков на камеру пошли прахом). Так что — только видео с ремонтом, и ничего более. Канал, кстати, буду вести регулярно, так что подписывайтесь, если есть интерес к подобным ремонтам.

Мне остается еще достать DVD-привод для этого ноутбука, а также купить отсутствующую на клавиатуре кнопку. Они продаются по отдельности на eBay, евро по 3-5, что в любом случае выгоднее покупки новой клавиатуры.

Ну а на этом все, не переключайтесь.

P.S. Ни паяльник, ни паяльный фен так ни разу и не включил, это был достаточно быстрый ремонт с отверткой и пинцетом в руках. Ну и еще лабораторным источником питания, да.

Возможно, эти тексты тоже вас заинтересуют:

10 шаблонов запросов для ChatGPT, которые выдадут качественные ответы в помощь продакт-менеджеру
OpenStack vs VMware: что лучше — open source или проприетарная платформа
Shawarma as a service: как создать бота для заказа шавермы и оставить голодными лишь 1,1% коллег


ссылка на оригинал статьи https://habr.com/ru/companies/selectel/articles/751592/

Опять про интервью, теперь со стороны нерелевантных вопросов

Утро доброе
Будучи не первый год на хабре, читал многие статьи на тему интервью: со стороны инженеров на входе, со стороны TA, со стороны успешного литкода. Хотелось бы добавить в эту, без сомнения, внушительную коллекцию мнение со стороны человека, чей подход к интервью многократно называли “завалил на тонкостях”.

Секция “о себе” будет в конце статьи, дабы не травить колодец. На всех интервью я задаю ровно три вопроса, которые отвечают мне на вопрос “хочу ли я этого человека в свою команду”. Все остальные вопросы скорее задаются либо для выяснения, не пригодится ли этот человек в соседние команды. Также, в рамках данного очерка, пройдусь только по инженерным вопросам “на завалить”.

Вопрос первый: Какие операционки есть у вас дома и почему?
Цель вопроса — не отсеять виндузятников, а услышать именно что мотивацию и склонность к ИТ вне тасок в джире. Как, наверно, понятно из описания, ответы в стиле “у меня есть комп, за которым я играю\смотрю ютуб и больше ничего” не приведет к успеху.

Вопрос второй: Какой инструмент вы бы выбрали для <стандартная задача в рамках роли> и почему?
Пример стандартных задач: CDC для дата инженера, оркестрация для MLOps, управление конфигурациями для админа и так далее.
Цель вопроса: понять знакомство кандидата с основной ролью, на которую он пришел и стандартным актуальным инструментарием в этой области. Если мне назовут динозавра или же always in beta продукт — я сильно насторожусь. С динозавров сложно переучиваться и скорость адаптации проекта у кандидата с динозавром не будет впечатлять. Смузи-стек может быть как хорошим признаком для совсем зеленого проекта, так и плохим признаком для команды с трехлетней историей.

Вопрос тертий, из-за которого и затевался этот очерк: назовите технологию, в которой вы чувствуете себя наиболее уверенно и которая вам больше всего по душе? После получения названия — вопрос “второй степени” в эту технологию.

“Вопрос второй степени” — это ситуация, которую с большой вероятностью нужно решать в проде, но на которую нет прямого ответа в документации. Пара примеров:

  • Технология: “elasticsearch”. Вопрос: индексы не помещаются в память. Что делать?

  • Технология: “linux”. Вопрос: у вас LA 9000/9000/9000, но система отвечает и к ней можно законнектится. Куда копать?

  • Технология: терадата\snowflake\hive. Вопрос (для дата инженера): перформанс одного и того же запроса падает быстрее, чем растет количество данных. В чем может быть проблема и что делать?

Цель вопроса достаточно проста: получить от кандидата технологию, в которую он умеет лучше всего (почти) безотносительно заявленного стека на проекте и проверить, то он в ней действительно разбирается. Если самая сильная технология раскопана на уровне первых трех ссылок гугла — то есть хорошая вероятность, что и с остальным будет сильно не лучше. Когда вам, в следующий раз, задают вопрос “на завалить” возможно ваш интервьюер преследует именно такую стратегию.

Хорошего дня, леди и джентльмены.

Немного о себе для контекста:

  • В основном, интервьюирую людей с бэкраундом около системной инженерии и анализа данных, в последние несколько лет добавился ML/DL и питон разработка в контексте интеграционных\системных сервисов

  • Команды от энтерпрайза разной степени кровавости, до стартапа с минус тремя месяцами существования

  • За плечами более 500 интервью на разные уровни: от джуна до архитектора и ПМа

  • Сам со стороны интервьюируемого был ровно 6 раз за все это время

  • Подход, описанный выше, дал сбой только дважды за весь мой опыт. Под дал сбой я подразумеваю «лучше бы я этого человека не брал в команду»


ссылка на оригинал статьи https://habr.com/ru/articles/751594/