Обо мне

Инженер-программист: г. Иркутск. Я специалист в области информационных технологий. У меня более 5 лет опыта в администрировании серверов различных ОС и в разработки скриптов, ПО и БД. Для саморазвития я в свободное время читаю и перевожу техническую документацию на английском языке, администрирую свой веб-сервер и дорабатываю свои проекты https://github.com/it38dato. Мне интересно работать в команде, активно развиваться в программировании и изучать новые технологии.
Навыки: Html, Css, Windows, Виртуализация, Linux, Sql, Bash, Clouds, Python, English, Etl-процессы, Dwh, AntiFraud, Спутниковые радионавигационные системы, С++.
Обратная связь: it38dato@yandex.ru, telegram - @it38dato.
Услуги:
# Верстка Веб-страниц по макетам.
# Администрирование локальных, виртуальных и облачных серверов.
# Администрирование Веб-сервера Lamp.
# Администрирование Веб-сервера Django.
# Администрирование Веб-сервера React.js.
# Администрирование базы данных.
# Разработка Веб-сайта Django.
# Разработка Веб-сайта React.js.
# Разработка Телеграм бота Переводчик.
# Разработка базы данных.
# Разработка Хранилище данных.
# Написание Sql запросов.
# Обработка и сортировка данных на Python, C++ и Sql-запросах.
# Анализ сетевых технологий.

2011-09-01 - 2018-05-30: Иркутский государственный университет, Иркутск. Должность: Информационные технологии и телекоммуникационные системы - Бакалавр / Электроника и наноэлектроника - Магистр / Информационная безопасность - Дополнительное образование. Дополнительная информация: Навыки - Спутниковые радионавигационные системы, С++. Достижения: Для защиты магистерской диссертации по теме "Использование данных одночастотных приемников Спутниковых Радионавигационных Систем для коррекции модели ионосферы" разработал программу "Обработка и сортировка данных в файлах Rinex формата"

Show

Цель:
# Рассмотреть возможность использования одночастотных приёмников спутниковых радионавигационных систем (СРНС) для получения информации с целью оперативной коррекции её модели.
# Установить и настроить программные обеспечения для обработки и сортировки данных.
# Получить данные с одночастотного приемника СРНС.
# Описать формат файлов RINEX.
# Разработать программу, которая считывает файлы в формате Rinex и сортирует псевдодальности (ПД) от времени (эпохи) по каждому навигационному спутнику.
# Получить результаты.
Skills:
# Спутниковые радионаввигационные системы.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Task:
Методика коррекции ионосферы по данному приёмнику СРНС.
# Спутниковые радионаввигационные системы.
Decision:
Рассмотрим возможность оценки вклада ионосферы в измерения псевдодальности (ПД) с помощью одночастотного приёмника ГЛОНАСС/GPS. Для этого будем считать, что координаты приемника x, y, z неизменны и известны с высокой точностью. ПД от i-го навигационного спутника (НС) с координатами xi, yi, zi определим как измеренную в свободном пространстве дальность D'i=сτ'i, отличающуюся от геометрической дальности (ГД) Di на неизвестную величину ρ: 

D'i=сτ'i определяется по разности моментов приема и передачи навигационного сигнала между временными шкалами НС и приемника. Если точность шкалы НС с учетом её коррекции достаточна для определения момента излучения, то из-за нестабильности шкалы приемника необходимо учитывать её мгновенный сдвиг относительно шкалы НС на неизвестную величину t' и ρ=ct'. В многоканальных одночастотных приемниках t' не зависит от i и значение ρ одинаково для всех НС в данный момент времени [6].
В реальных условиях результаты измерения ПД D''i отличаются от значения D'i из формулы (4.1) из-за влияния различных факторов на распространение навигационных сигналов. Основные ошибки, возникающие при этом, вызваны запаздыванием сигнала в ионосфере и тропосфере , а также погрешностями определения эфемерид . Другие ошибки хорошо компенсируются алгоритмами обработки сигналов. Например, влияние многолучевости значительно уменьшается усреднением данных на интервале 30 с и более, а релятивистские эффекты устраняются коррекцией навигационного сигнала [6].
Таким образом, ошибка измерения ПД в основном определяется следующими составляющими:

Отсюда можно найти вклад ионосферы в ошибку измерения ПД для i-го НС: 

Величина ρ в формуле (4.3) зависит от расхождения шкал времени НС и приемника и меняется в очень широких пределах. 
При решении навигационной задачи относительно времени, шкала приемника может быть привязана к шкале НС, но найденная при этом величина ρ' будет характеризовать эффективное значение с учетом других ошибок определения ПД, и в этом случае будет иметь смысл отклонения от некоторого среднего значения для всех НС, участвующих в навигационном решении [6].
Для устранения влияния нестабильности временнόй шкалы приемника построим разность ошибок ионосферы для i-го и j-го НС в один момент времени: 

Как видно, точность определения зависит от погрешностей определения эфемерид выбранных НС и разностей тропосферных задержек сигналов от них. обычно составляет единицы метров. Если из созвездия выбирать НС со «свежими» эфемеридами, то эта погрешность уменьшится до ~1 м. Применение постобработки и использование уточнённых эфемерид дает погрешность значительно меньше 1 м. Если предположить, что ошибки для двух НС независимы, а их величины примерно одинаковы, то общая ошибка из-за погрешностей задания эфемерид [6]:

Задержка в тропосфере приводит к ошибке в измерении ПД, которая хорошо компенсируется простыми моделями тропосферы. В нормальных условиях в тропосфере для углов места β > 5º и достижения погрешности после коррекции < 0.5 м можно воспользоваться формулой:

Здесь - задержка сигнала в тропосфере для зенитных НС, равная ~7 нс, что соответствует пути 2.1 м. Для увеличения точности коррекции можно применить модель Саастамойнена.
Таким образом, общая величина ошибки определения в благоприятных условиях составляет ~1÷2 м и вариации ионосферной задержки величиной ~10 и более метров могут быть определены одночастотным приемником ГНСС.
Task:
Получение данных с одночастотного приемника СРНС.
# Спутниковые радионаввигационные системы.
Decision:
Для проверки способа определения вариации ионосферной задержки 19 мая 2015 года провел обработку данных измерения навигационных сигналов от нескольких НС. Для измерений использовался 20 канальный одночастотный приемник BU-353 фирмы GlobalSat. Этот приемник предназначен для ГНСС GPS, работает на частоте L1 по C/A коду и имеет высокую чувствительность 159 дБм. Данные о псевдодальностях доступны только при использовании бинарного протокола, поэтому при наблюдениях применялась программа SirfTech версии 2.20 и данные записывались в RINEX файл. Описание программы SirfTech можно увидеть прил. 5. 
Task:
Алгоритм обработки данных.
# Спутниковые радионаввигационные системы.
Decision:
Большая часть программного обеспечения обработки данных GPS использует определенный набор наблюдений:
- фазовые измерения на одной или двух несущих частотах;
- измерения псевдодальности (кода), которые соответствуют разности между временем получения и временем передачи отдельных сигналов спутника;
- время наблюдения считывается с часов приемника в момент измерения фазы несущей и/или кода.
При обработке необходимыми являются: фаза, код и время, определение которых дано выше, и некоторая информация относительно станции, такая как название станции, высота антенны и другие [5].
Task:
Описание формата RINEX.
# Спутниковые радионаввигационные системы.
Decision:
Для решении навигационной задачи исходными данными являются файлы в формате RINEX - независимый от приемника формат для обмена данными СРНС. RINEX - формат состоит из трех типов ASCII файлов - файлы метеорологических параметров (meteorological data file), файлы результатов измерений (observation data file), файлы с оперативной эфемеридной информацией, полученные в составе навигационного сообщения (navigation message file) [5].
Файлы имеют различную длину, максимальное значение равно 80 символам в строке (табл. 2). Каждый файл содержит секцию заголовков и секцию данных. Файл навигационного сообщения располагается независимо, в то время как файлы измерений и метеорологических данных должны быть созданы для каждого используемого при наблюдениях пункта [5]. 
Таблица 2. Общая структура формата RINEX: 

Результаты измерений располагаются по эпохам. Каждой эпохе наблюдения соответствует структура, представленная на табл. 3 (количество и порядок расположения результатов различных типов измерений указывается в заголовке файла в строке "# / TYPES OF OBSERV", например, «7 LI L2C1 PI P2»), где у у mm dd hh mm sec момент приема сигнала по часам потребителя (tp); № - номера спутников, результаты наблюдений которых в соответствующем порядке записываются для каждой эпохи (при совместной обработке наблюдений ИСЗ обеих систем либо резервируются номера с 1 по 32 для спутников GPS и с 33 по 64 для спутников ГЛОНАСС, либо вводятся дополнительные признаки G и R); ИСЗ фаза L1 - псевдодальность, измеренная по фазе несущей на частоте L1; ИСЗ фаза L2 - псевдодальность, измеренная по фазе несущей на частоте L2; ИСЗ код С1 - псевдодальность, измеренная по С/А-коду на частоте L1; ИСЗ код Р1 - псевдодальность, измеренная по Р-коду на частоте L1; ИСЗ код Р2 - псевдодальность, измеренная по Р-коду на частоте L2 [2].
Таблица 3. Результаты СРНС-измерений на одну эпоху 

Оперативная эфемеридная информация, полученная в составе навигационного сообщения, сгруппирована по номерам ИСЗ. Фрагменты файла измерений в формате RINEX представлены в прил. 2 [5].
GPS наблюдения включают в себя три основных понятия, которым необходимо дать определение: время, фаза и псевдодальность. Время измерений - это время приемника в момент приема сигналов. Оно одинаково для измерений фазы и псевдодальности и одинаково для всех наблюдаемых спутников в данную эпоху. Время выражается в единицах GPS-времени (не в мировом времени, UTC).
Псевдодальность (ПД) - это расстояние от приемной антенны до антенны спутника, включая сдвиги шкалы времени приемника и спутниковых часов (и другие сдвиги, такие как атмосферные задержки). Псевдодальность отражает реальное поведение часов приемника и передатчика. Псевдодальность указывается в единицах длины - метрах.
Фаза - это фаза несущей, измеренная в целых циклах. Измеряемое количество полуциклов квадратурными приемниками должно быть конвертировано в целые циклы и соответственно изменено значение длины волны в заголовке файла (только для GPS) [5].
Изменения фазы положительно коррелированны с изменениями дальности (негативный эффект Доплера). Фазовые наблюдения между эпохами должны быть скорректированы включением целого числа циклов. Фазовые наблюдения не будут содержать никаких систематических сдвигов от намеренных сдвигов опорных генераторов.
Данные всех измерений не скорректированы на внешние эффекты, такие как атмосферная рефракция, сдвиг часов спутникового передатчика и другие. Если приемник или программа конвертера производят измерения сдвига часов приемника в реальном времени dT(изм), то соответственно три параметра (фаза, псевдодальность, эпоха) должны быть исправлены. Знак доплеровского сдвига частоты определяется как обычно - положительный знак при приближении спутника [5].
Task:
Для дальнейшей обработки и сортировки данных мною была написана программа на языке С++, которая считывает файлы в RINEX формате и сортирует псевдодальности по каждому НС, что упрощает дальнейшую обработку. Для основной обработки данных по формуле 4.4 выполнялись вычисления с помощью редактора EXCEL.
Task:
Алгоритм.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
Из текстового файла вывести всю информацию до END OF HEADER в out.txt.
Вывести следующую строку после END OF HEADER - время и название спутников.
Вывести количество спутников и названия спутников в строке.
Вывести название спутников по буквам, то есть создаем динамический массив с указателем.
Вывести вторую строку название спутников.
Вывести буквы спутников в столбец.
Вывести по буквам и по цифрам. если увидим пробел, тогда операция не должна выполняться.
Вывести все спутники в столбец.
Вывести псевдодальности для одной/первой эпохи (время - 0:0).
Вывести названия спутников для первой эпохи.
Вывести псевдодальности для каждого спутника.
Вывести название спутника и расстояние (дальность) спутника. первая строка - для первой эпохи, вторая строка - для второй эпохи
Вывести для всех эпох данные спутников.
Вывести на экран данные для двух спутников.
Вывести данные именно для одного спутника, например первого.
Task:
Вывести названия спутников из файла для первой минуты.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat IRKL1119.txt
2.11 OBSERVATION DATA M (MIXED) RINEX VERSION / TYPE
...
       END OF HEADER
11 11 19 0 0 0.0000000 0 18G 1G28R22G1R12G 7G 3G 6R 6R24R 5G26-0.000381360
    G11G16R13R14G 8R23
23815384.399 23815383.399 125150709.498 3 3673.062 23.000
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
int countSputnik;
in >> countSputnik;
cout<< countSputnik<<endl ;
getline(in,name);
// cout<< name <<endl;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++)
{
  if(name[i] == '-')
  break;
  if(name[i] >= 'A' && name[i] <= 'Z') {
   index++;
   if(tempName != "") {
    allSputnik.push_back(tempName);
    tempName="";
   }
  }
  if (name[i] != ' '){
   tempName = tempName + name[i];
  }
}
if(index != countSputnik -1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
  getline(in,name);
  for(int i=0; i<name.size(); i++){
   if(name[i] >= 'A' && name[i] <= 'Z') {
    index++;
    if(tempName != "") {
     allSputnik.push_back(tempName);
     tempName="";
    }
   }
   if (name[i] != ' '){
   tempName = tempName + name[i];
  }
}
}
for(int i=0; i<countSputnik; i++){
out << allSputnik[i] << endl;
}
/*
delete [] Sputnik;
// ���������� ��������������� ��� ����� �����
double *information = new double [countSputnik];
for(int i=0; i<countSputnik; i++){
in >> information[i];
getline (in,name);
getline (in,name);
}
for(int i=0; i<countSputnik; i++){
out << information[i] << endl;
}
*/
/*
double *information = new double [countSputnik];
double information1;
for(int i=0; i<countSputnik; i++){
in >> information1;
in >> information[i];
getline (in,name);
getline (in,name);
}
for(int i=0; i<countSputnik; i++){
cout << information[i] << endl;
}
*/
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1
G28
R22
G1
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
Task:
Читаем Каждую Букву И Цифру После строки названия спутников для первой минуты.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat igs19236-1.txt
#cP2016 11 19 0 0 0.00000000 96 ORBIT IGb08 HLM IGS
...
* 2016 11 19 0 0 0.00000000
PG01 -20514.995126 -11031.142105 12884.404415 40.929190 7 8 4 99 
PG02 12690.844622 22403.534044 7723.409365 523.430781 9 7 9 125 
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <fstream>
#include <string.h>
#include <vector>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("igs19236-1.txt");
ofstream out ("out.txt"); 
// ���������� �� ORB:CMB CLK:CMB
string stroka;
for (int i=0; i<22; i++)
{
  getline (in,stroka);
}
// ��������� �����
string epoha;
getline(in,epoha);
//out << epoha << endl;
/*char zvezda;
int data;
int vremia;
double sostoianie;
in >> zvezda;
in >> data;
in >> data;
in >> data;
in >> vremia;
in >> vremia;
in >> sostoianie;
//out << sostoianie;*/
// ��������� ������� ���� ��������� � �� ����������
/*
string position; // ��� ���� ������
getline (in,position);
out << position << endl;
*/
/*//������ ������� +
string sputnik; //������� ��������
string e; // EOF � * 2016 11 19
string x;
string y;
string z;
string signali; //������ �������
for (int j=0; j<96; j++){ //��� ���� ��������� � ��� ���� ����
  for (int i=0; i<32; i++){ //��� ���� ��������� � ��� ����� �����
   in >> sputnik >> x >> y >> z;
   getline (in, signali);
   out << sputnik << " " << x << " " << y << " " << z << " " << endl;
  }
  getline (in, e); // ��������� EOF � * 2016 11 19
}*/
/* //������ ������� -
int b;
//getline (in,position);
//out << position << endl;

string *info = new string [b];
for (int i=0; i<b; i++)
{
  info[i]="";
}
int index=-1;
for(int i=0; i< position.size(); i++) {
  if(position[i] >= 'A' && position[i] <= 'Z') {
   index++;
  }
  if (position[i] !=' '){
   info[index] = info[index] + position[i];
  }
}
for(int i=0; i<b; i++)
{
  out << " " << info[i] << " " << endl;
}
delete [] info;
*/
/*
double *information = new double [b];

for(int i=0; i<b; i++)
{
  in >> information[i];
  //getline (in,position);
  //getline (in,position);
}
for(int i=0; i<b; i++){
  out << information[i] << endl;
}
*/
string position; // ��� ���� ������
getline (in,position); // ����������� ��� ������ �������
//out << position << endl;
// ������� ������ ������� ���� ������
vector <string> infa; // ������� ������ ������ � ����� ������� �����
int index=-1; // ��������� ��������
string infaPosition = ""; // ������ ������� ��� ������ ��������
for(int i=0; i< position.size(); i++) { // ��� ������ ���� ������� ����� �������
  if(position[i] >= 'A' && position[i] <= 'Z') { // 1 ���� ��������� �����
   index++; // �������� ���������
   /*if(infaPosition != "") { // ����
    infa.push_back(infaPosition);
    infaPosition="";
   } */
  }
  /*if (position[i] != ' '){
   infaPosition = infaPosition + position[i];
  }*/
  out << position[i] << endl; // ������� � ������ ������ �������
}
/* string a;
getline (in,a);
out << a << endl; 
*/
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat out.txt
P
G
0
1
-
2
0
5
1
4
.
9
9
5
1
2
6
-
1
1
0
3
1
.
1
4
2
1
0
5
1
2
8
8
4
.
4
0
4
4
1
5
4
0
.
9
2
9
1
9
0
7
8
4
9
9
Task:
Мы Выводим Данные Спутников G16 В Текстовый Файл для каждой эпохи.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat IRKL1119.txt
...
    G11G16R13R14G 8R23
23815384.399 23815383.399 125150709.498 3 3673.062 23.000
23815389.819 97520038.895 3 2862.131 23.000
23418101.271 23418101.711 123062972.555 4 2734.873 26.000
23418102.171 95893253.756 4 2131.058 26.000
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(53);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
for(int i=0; i<2880; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
/*out << countSputnik << endl; */
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/*
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
*/
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
/*
in >> dalnost;
in >> dalnost;
*/
string b = "";
getline (in, b);
a ="";
if(b.size()>=30){
a=b.substr(17,13);
}
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
  if (a[i]>='0' && a[i]<='9'){
   dalnost = dalnost *10 + (a[i]-'0');
  }
  else {
   if(a[i]== '.') {
    tochka=i;
   }
  }
}
if(tochka != -1){
  chislo = a.size()-1 - tochka;
  chislo = (int) pow(10, (double)chislo);
  dalnost=dalnost/chislo;
}
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
}
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
out << endl;
for(int i=0; i<info.size(); i++){
out << setw(13) << setprecision(13) << info[i][0] << setw(14) << info[i][1] << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
...
23418101.711
23402498.509
23386912.128
23371345.657
23355798.957
...
Task:
Выводим список спутников и их псевдоальность для первой эпохи.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
int countSputnik;
in >> countSputnik;
//out<< countSputnik<<endl ;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
getline(in,name);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ���������
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
//out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setprecision(12) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G9 R23 
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
Task:
Выводим количество спутников, название спутников и пссевдодальность для каждого спутника за первые 15 минут.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
int countSputnik;
in >> countSputnik;
cout<< countSputnik<<endl ;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
getline(in,name);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
/*
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setprecision(12) << info[i][j] << " ";
}
out << endl;
}
*/
////////////////////////////////////////////////////////////////////
//getline(in, name);
//out << endl << name << endl;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
out << countSputnik << endl;
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G9
R23
19472155.069 81015603.872 8 1314.191 51.000
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
R23
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G9 R23 G8 
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
23794425.041 23402498.509 20141642.176 20584598.589 20869652.326 20367330.686 22196301.185 23890335.007 23883323.395 22925234.933 23334086.485 23188096.286 21881612.664 24830710.969 19148858.027 21645612.101 0 19462739.332 20642124.208 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
/*
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
cout<< countSputnik<<endl ;
getline(in,name);
allSputnik.resize(0);// ������ ������ � ������� ����������
*/
/*
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
*/
/*
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << Sputnik[j] << " ";
}
out << endl;

for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setprecision(12) << info[i][j] << " ";
}
out << endl;
}
*/
////////////////////////////////////////////////////////////////////
//getline(in, name);
//out << endl << name << endl;
for(int i=0; i<4; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
out << countSputnik << endl;
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);

for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;

getline (in,name);
getline (in,name);

index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}

}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G9
R23
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
R23
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
R23
1243988
19453454.30480937797.92821279.27712.000
19453454.30480937797.92821279.27712.000
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G9 R23 G8 19453454.30480937797.92821279.27712.000 
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
23794425.041 23402498.509 20141642.176 20584598.589 20869652.326 20367330.686 22196301.185 23890335.007 23883323.395 22925234.933 23334086.485 23188096.286 21881612.664 24830710.969 19148858.027 21645612.101 0 19462739.332 20642124.208 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
23773491.181 23386912.128 -2723.261 20592788.59 20888815.333 20373592.145 22215405.514 23909502.132 23869784.311 22901490.193 23338082.579 23181639.852 21865044.244 24849525.498 19146493.433 21624473.283 0 19453451.974 20633403.301 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 19453451.974 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
Task:
Выведем данные Псевдодальности для каждого спутника.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat IRKL1119.txt
2.11 OBSERVATION DATA M (MIXED) RINEX VERSION / TYPE
...
       END OF HEADER
11 11 19 0 0 0.0000000 0 18G 1G28R22G19R12G 7G 3G 6R 6R24R 5G26-0.000381360
    G11G16R13R14G 8R23
23815384.399 23815383.399 125150709.498 3 3673.062 23.000
23815389.819 97520038.895 3 2862.131 23.000
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
for(int i=0; i<2880; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
/*out << countSputnik << endl; */
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/*
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
*/
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
/*
in >> dalnost;
in >> dalnost;
*/
string b = "";
getline (in, b);
a ="";
if(b.size()>=30){
a=b.substr(17,13);
}
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
  if (a[i]>='0' && a[i]<='9'){
   dalnost = dalnost *10 + (a[i]-'0');
  }
  else {
   if(a[i]== '.') {
    tochka=i;
   }
  }
}
if(tochka != -1){
  chislo = a.size()-1 - tochka;
  chislo = (int) pow(10, (double)chislo);
  dalnost=dalnost/chislo;
}
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G8 R23 G15 G22 G17 R7 R15 R17 R8 G32 G20 G9 R1 R16 G4 R18 R2 G12 G2 R9 G10 G23 G25 R19 R10 G13 G5 R4 G31 R11 G29 R20 G30 G21 R21 G18 G14 
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
23794425.041 23402498.509 20141642.176 20584598.589 20869652.326 20367330.686 22196301.185 23890335.007 23883323.395 22925234.933 23334086.485 23188096.286 21881612.664 24830710.969 19148858.027 21645612.101 20642124.208 19462739.332 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
...
Task:
Выведем данные из файла Sirftin.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat sirfrin1.txt
2.10 OBSERVATION DATA G (GPS) RINEX VERSION / TYPE
...
       END OF HEADER
15 5 18 0 41 23.0000613 0 11G 1G12G32G24G22G14G11G 4G18G31G17
23527987.056 8
21972648.359 8
22403365.951 6
23784621.452 7
21148286.543 6
20408196.378 4
24488332.428 4
23108442.059 5
23196661.017 4
22514486.129 5
25648439.341 3
15 5 18 0 41 24.0000613 0 11G 1G12G32G24G22G14G11G 4G18G31G17
23527900.192 8
21972620.569 8
22402887.237 5
23785272.449 7
21148685.515 6
20408150.444 4
24488620.925 4
23108699.683 5
23197296.183 4
22513871.405 5
25648503.069 3
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("sirfrin1.txt");
ofstream out ("output.txt");
//////// ������� END OF HEADER
string a;
while(true) {
  getline (in,a);
  if(a == "       END OF HEADER") {
    break;
  }
}
//////// ����
string name; // ������� ������
int temp; // �����
double temp2; // ����� � ������
int countSputnik; // ���������� ���������
vector <vector <double> > info; // ������� ������
vector <double> tempVector;
tempVector.resize(15); // ������ �������
vector <string> Sputnik; // ������� ������� �� ���������
vector <string> allSputnik; // ������� ������� �� ����� ����������
int index=-1; // ��������� ��������
string tempName = ""; // ������ �������� ���������
//////// ������� ������� � ���������
for(int i=0; i<4797; i++) { // ������� ����� ����
  in >> temp;
  in >> temp;
  in >> temp;
  in >> temp;
  in >> temp;
  in >> temp2;
  in >> temp;
  in >> countSputnik;
  // out << countSputnik << endl; // ���������� ���������
//////////////// ��������� �� �����
  getline(in,name);
  // out << name; // �������� ���������
//////////////// ��������� �������� ���������
  allSputnik.resize(0);
  index=-1;
  tempName = "";
  for(int i=0; i< name.size(); i++) {
   if(name[i] == '-') // ���� ��������� -
   break; // �� ���������
   if(name[i] >= 'A' && name[i] <= 'Z') { // ���� ��������� �����
    index++; // �� ��������� ������
    if(tempName != "") {
     allSputnik.push_back(tempName);
     tempName="";
    }
   }
   if (name[i] != ' '){
    tempName = tempName + name[i];
   }
  }
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
  //tempName="";
//////////////// ������� ��� ��������� ��������� ������ ������
  /* for(int i=0; i<allSputnik.size(); i++){
   out << allSputnik[i] << endl;
  } */
//////////////// 2�� ������ �������� ���������
  /*if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
   //getline(in,name); // ��������� ������ �� �����
   for(int i=0; i<name.size(); i++){
    if(name[i] >= 'A' && name[i] <= 'Z') {
     index++;
     if(tempName != "") {
      allSputnik.push_back(tempName);
      tempName="";
     }
    }
    if (name[i] != ' '){
     tempName = tempName + name[i];
    }
   }
  }
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
  /* for(int i=0; i<allSputnik.size(); i++){
   out << allSputnik[i] << endl; // ������� ��� ��������� ��������� � ������ � ������ ������
  } */
/////////////// ��������� ���������� ���������
  info.push_back(tempVector);
  for(int i = 0; i<allSputnik.size(); i++) {
   double dalnost=0;
   string b = "";
   getline (in, b);
   a ="";
   if(b.size()>=14){
    a=b.substr(2,12); // �������� ������ ��� ����������
   }
   // out << a << endl; // ������� ���������� ���������
/////////////////////// ����������� ������ ������� � ������� ��� ���� ����� ���������� ������ ������
   int tochka=-1;
   int chislo=0;
   for (int i=0; i<a.size(); i++){
    if (a[i]>='0' && a[i]<='9'){
     dalnost = dalnost *10 + (a[i]-'0');
    }
    else {
     if(a[i]== '.') {
      tochka=i;
     }
    }
   }
   if(tochka != -1){
    chislo = a.size()-1 - tochka;
    chislo = (int) pow(10, (double)chislo);
    dalnost=dalnost/chislo;
   }
/////////////////////// ��������� �������� � ��������� � ������������
   // getline (in,name); //��������� �� ����� ����� ������
   index=-1;
   for(int j=0; j<Sputnik.size(); j++) {
    if(allSputnik[i] == Sputnik[j]) {
     index = j;
     break;
    }
   }
   if(index == -1) {
    Sputnik.push_back(allSputnik[i]);
    info[info.size()-1][Sputnik.size()-1] = dalnost;
   }
   else {
    info[info.size()-1][index] = dalnost;
   }

  }
}
//////// ������� ���������� �� �������
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
  out << setw(13) << Sputnik[j] << " ";
}
out << endl;
/////// ������� ���������� �� �������
for(int i=0; i<info.size(); i++) {
  for(int j=0; j<info[i].size(); j++) {
   out << setw(13) << setprecision(13) << info[i][j] << " ";
  }
  out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1 G12 G32 G24 G22 G14 G11 G4 G18 G31 G17 G3 G25 G26 G29 
23527987.056 21972648.359 22403365.951 23784621.452 21148286.543 20408196.378 24488332.428 23108442.059 23196661.017 22514486.129 25648439.341 0 0 0 0 
23527900.192 21972620.569 22402887.237 23785272.449 21148685.515 20408150.444 24488620.925 23108699.683 23197296.183 22513871.405 25648503.069 0 0 0 0 
23527815.469 21972594.92 22402410.598 23785925.499 21149086.34 20408107.268 24488915.359 23108959.419 23197938.791 22513258.71 25648554.131 0 0 0 0 
23527729.677 21972568.22 22401932.854 23786577.382 21149485.828 20408062.76 24489187.365 23109217.855 23198578.688 22512592.942 25648601.174 0 0 0 0 
...
Task:
Нарисуйте График с файла Sirfrin1.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("sirfrin1.txt");
ofstream out ("output.txt");
//////// ������� END OF HEADER
string a;
while(true) {
  getline (in,a);
  if(a == "       END OF HEADER") {
    break;
  }
}
//////// ����
string name; // ������� ������
int temp; // �����
double temp2; // ����� � ������
int countSputnik; // ���������� ���������
vector <vector <double> > info; // ������� ������
vector <double> tempVector;
tempVector.resize(15); // ������ �������
vector <string> Sputnik; // ������� ������� �� ���������
vector <string> allSputnik; // ������� ������� �� ����� ����������
int index=-1; // ��������� ��������
string tempName = ""; // ������ �������� ���������
//////// ������� ������� � ���������
for(int i=0; i<4797; i++) { // ������� ����� ����
  in >> temp;
  in >> temp;
  in >> temp;
  in >> temp;
  in >> temp;
  in >> temp2;
  in >> temp;
  in >> countSputnik;
  // out << countSputnik << endl; // ���������� ���������
//////////////// ��������� �� �����
  getline(in,name);
  // out << name; // �������� ���������
//////////////// ��������� �������� ���������
  allSputnik.resize(0);
  index=-1;
  tempName = "";
  for(int i=0; i< name.size(); i++) {
   if(name[i] == '-') // ���� ��������� -
   break; // �� ���������
   if(name[i] >= 'A' && name[i] <= 'Z') { // ���� ��������� �����
    index++; // �� ��������� ������
    if(tempName != "") {
     allSputnik.push_back(tempName);
     tempName="";
    }
   }
   if (name[i] != ' '){
    tempName = tempName + name[i];
   }
  }
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
  //tempName="";
//////////////// ������� ��� ��������� ��������� ������ ������
  /* for(int i=0; i<allSputnik.size(); i++){
   out << allSputnik[i] << endl;
  } */
//////////////// 2�� ������ �������� ���������
  /*if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
   //getline(in,name); // ��������� ������ �� �����
   for(int i=0; i<name.size(); i++){
    if(name[i] >= 'A' && name[i] <= 'Z') {
     index++;
     if(tempName != "") {
      allSputnik.push_back(tempName);
      tempName="";
     }
    }
    if (name[i] != ' '){
     tempName = tempName + name[i];
    }
   }
  }
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
  /* for(int i=0; i<allSputnik.size(); i++){
   out << allSputnik[i] << endl; // ������� ��� ��������� ��������� � ������ � ������ ������
  } */
/////////////// ��������� ���������� ���������
  info.push_back(tempVector);
  for(int i = 0; i<allSputnik.size(); i++) {
   double dalnost=0;
   string b = "";
   getline (in, b);
   a ="";
   if(b.size()>=14){
    a=b.substr(2,12); // �������� ������ ��� ����������
   }
   // out << a << endl; // ������� ���������� ���������
/////////////////////// ����������� ������ ������� � ������� ��� ���� ����� ���������� ������ ������
   int tochka=-1;
   int chislo=0;
   for (int i=0; i<a.size(); i++){
    if (a[i]>='0' && a[i]<='9'){
     dalnost = dalnost *10 + (a[i]-'0');
    }
    else {
     if(a[i]== '.') {
      tochka=i;
     }
    }
   }
   if(tochka != -1){
    chislo = a.size()-1 - tochka;
    chislo = (int) pow(10, (double)chislo);
    dalnost=dalnost/chislo;
   }
/////////////////////// ��������� �������� � ��������� � ������������
   // getline (in,name); //��������� �� ����� ����� ������
   index=-1;
   for(int j=0; j<Sputnik.size(); j++) {
    if(allSputnik[i] == Sputnik[j]) {
     index = j;
     break;
    }
   }
   if(index == -1) {
    Sputnik.push_back(allSputnik[i]);
    info[info.size()-1][Sputnik.size()-1] = dalnost;
   }
   else {
    info[info.size()-1][index] = dalnost;
   }

  }
}
//////// ������� ���������� �� �������
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
  out << setw(13) << Sputnik[j] << " ";
}
out << endl;
/////// ������� ���������� �� �������
for(int i=0; i<info.size(); i++) {
  for(int j=0; j<info[i].size(); j++) {
   out << setw(13) << setprecision(13) << info[i][j] << " ";
  }
  out << endl;
}
out << endl;
for(int i=0; i<info.size(); i++){
  out << setw(13) << setprecision(13) << info[i][0] << setw(14) << info[i][1] << endl;
Form1->Memo1->Lines->Add(info[i][0]);
Form1->Memo2->Lines->Add(info[i][1]);
Form1->Memo3->Lines->Add(info[i][0]-info[i][1]);
//Series1->Add(info[i][0],clBlue);
//Series2->Add(info[i][1],clBlue);
Series2->Add(info[i][0]-info[i][1],clBlue);
}
}
//---------------------------------------------------------------------------
Task:
Нарисуйте График И Вычислите Разницу.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == "       END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(53);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
for(int i=0; i<2880; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
/*out << countSputnik << endl; */
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
  index++;
  if(tempName != "") {
   allSputnik.push_back(tempName);
   tempName="";
  }
}
if (name[i] != ' '){
  tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/*
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
*/
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
/*
in >> dalnost;
in >> dalnost;
*/
string b = "";
getline (in, b);
a ="";
if(b.size()>=14){
a=b.substr(1,13);
}
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
  if (a[i]>='0' && a[i]<='9'){
   dalnost = dalnost *10 + (a[i]-'0');
  }
  else {
   if(a[i]== '.') {
    tochka=i;
   }
  }
}
if(tochka != -1){
  chislo = a.size()-1 - tochka;
  chislo = (int) pow(10, (double)chislo);
  dalnost=dalnost/chislo;
}
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
  if(allSputnik[i] == Sputnik[j]) {
   index = j;
   break;
  }
}
if(index == -1) {
  Sputnik.push_back(allSputnik[i]);
  info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
  info[info.size()-1][index] = dalnost;
}
}
}
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
  out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
out << endl;
for(int i=0; i<info.size(); i++){
Form1->Memo1->Lines->Add(info[i][0]);
Form1->Memo2->Lines->Add(info[i][1]);
//Form1->Memo3->Lines->Add(info[i][0]-info[i][1]);
Series1->Add(info[i][0],clBlue);
Series2->Add(info[i][1],clBlue);
//out << setw(13) << setprecision(13) << info[i][0] << setw(14) << info[i][1] << endl;
}
}
//---------------------------------------------------------------------------
Task:
Попробуем просто сделать это все на графическом интерфейсе, вывести данные двух спутников, плюс еще нарисовать графики этих данных для двух спутников.
Теперь нужно обработать и сортировать данные с геометрической дальностью для спутников. Файл называется igr18451.txt. Вот как выглядят данные, которые нужно отсортировать. Тут уже одна эпоха равняется 15 минутам.
То что я выделил, это координаты спутника X,Y,Z. Именно их и нужно отсортировать. PG01 - название первого спутника. Попробуем вывести данные именно для него.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
PG04 16571.755106 7091.147720 19268.134634
PG04 17081.817393 9222.831883 17918.904800
PG04 17633.922784 11162.951350 16256.328779
PG04 18194.986791 12883.319207 14310.615388
PG04 18728.745598 14363.489413 12116.696076
PG04 19197.262350 15591.290270 9713.525830
PG04 19562.489921 16563.032391 7143.333216
PG04 19787.827341 17283.391677 4450.837454
PG04 19839.609715 17764.980464 1682.449322
PG04 19688.476298 18027.631365 -1114.528992
PG04 19310.568086 18097.427949 -3892.691773
PG04 18688.514262 18005.524210 -6605.294077
PG04 17812.175758 17786.800613 -9206.983073
PG04 16679.123638 17478.408399 -11654.488894
PG04 15294.839581 17118.255856 -13907.269035
PG04 13672.635233 16743.490595 -15928.101701
PG04 11833.296299 16389.030589 -17683.624513
PG04 9804.465824 16086.194088 -19144.815686
PG04 7619.789018 15861.474593 -20287.415200
...
Task:
Это мы вывели координаты X,Y,Z и название спутника. Но мне теперь нужно вывести данные только Х для первого спутника.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
14736.411108
15414.203154
16201.304785
17074.555745
18004.439905
18956.228081
19891.328698
20768.792721
21546.914135
...
Task:
Аналогично выводятся только для Y и Z выводим данные. Просто нужно поменять в коде x на y или z.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Task:
Аналогично нужно вывести данные псевдодальности полученные с моего приемника. файл - sirfrin1.txt.
Тут мы увидим только две данные для спутников. Первые, это дальность, которую как раз и нужно отсортировать по каждом НС, а вторые - сигналы спутника, которые нас не особо не интересуют. А также эпоха здесь начинается не с нуля, как мы видим, а с 41 минуты 23 секунды. Это тоже нужно учитывать, когда нужно будет сравнивать эти данные с ГД.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
Мы задачу выполнили (отсортировать данные ГД и ПД по каждом навигационному спутнику), остается только обработать эти данные по формулам и посторить по ним графики через Эксель.
Task:
Экспериментальные результаты.
# Спутниковые радионаввигационные системы.
Decision:
Для наглядности на рис. 14 показан пример измерения навигационных сигналов, принимаемых с НС GPS1 и GPS20, взятый из статьи [6]. По горизонтали отложены номера 30-ти секундных интервалов (эпох) от 0 часов всемирного времени. В верхней части рисунка даны результаты вычислений углов места НС GPS1 (пунктир) и GPS20 (точки). В нижней части рисунка тонкой линией в соответствии с формулой (3.4) показана разность измеренных псевдодальностей за вычетом разности ГД от этих НС с коррекцией запаздывания лучей в тропосфере по формуле (4.5). Для расчета ГД использовались уточненные эфемериды SP3 [6]. 
Рис. 14. Пример измерений вариаций ионосферы:

Здесь же для сравнения жирной линией приведена разность ионосферных ошибок для этих же НС по данным двухчастотного приемника JPS EGGDT станции Иркутск (IRKL). 
На рис. 15 показана средняя часть рис. 14 для детального сравнения данных о вариациях ионосферной ошибки, определяемой вариациями ПЭС между двумя НС [6].
Рис.15. Сравнение данных одночастотного и двухчастотного приемников: 

Видно, что для высоких углов места в среднем наблюдается хорошее совпадение результатов, полученных разными методами. Следует отметить, что случайные отклонения у одночастотного приемника примерно в два раза больше. При приближении НС к горизонту данные одночастотного приемника начинают сильно отличаться от двухчастотного, и при углах места около 5° различие доходит до 15-20 м (~ 60 нс). Вероятно, это связано с влиянием тропосферы и неучтенной при коррекции «влажной» составляющей тропосферной погрешности [6].
Приведенные данные свидетельствуют о близости результатов определения разности ионосферных ошибок двухчастотным и одночастотным приемниками. Как видно из сравнения описанных результатов, в диапазоне углов места больше 15° две кривые хорошо совпадают, различие между ними в 90% случаев не превышает 2 м (7 нс), при регулярном ходе около 20 м (70 нс) [6].
Для сравнения, я пытался построить свои примеры измерения навигационных сигналов. В прил. 3 показан код программы на языке С++, которая обрабатывает и сортирует данные ПД по каждому НС. На табл. 4 показан пример вычислений и интерпретаций данных с помощью EXCEL:
Таблица 4. Вычисления данных с помощью Excel: 

где x, y, z – координаты приёмника, а Xi, Yi, Zi – координаты спутника, ГД- вычисленная по формуле (4.1) геометрическая дальность, ПД – пседодальность и последний столбец является разностью ошибок ионосферы для двух спутников G1и G28 вычисленная по формуле (4.4).
На рис. 16-19 построены разности ошибок ионосферы для двух НС в один момент времени. Первая часть данных взяты из собственных измерений, а вторая часть данных для сравнения давались мне руководителем. По горизонтали отложены номера 15-ти минутных интервалов. 
Рис. 16. Пример измерений, принимаемых с GPS 1 и GPS 28, Рис. 17. Пример измерений, принимаемых с GPS 20 и GPS 32, Рис. 18. Пример измерений, принимаемых с GPS 2 и GPS 5, Рис. 19. Пример измерений, принимаемых с GPS 1 и GPS 4: 

Разность псевдодальности и геометрической дальности на рис. 16-17 является разностью ионосферной задержки и может быть использована для коррекции ионосферы. На рис. 18-19 получились большие разности, и такие данные использовать для уточнения состояния ионосферы при её оперативной коррекции нельзя.
Decision:
Для защиты диссертации по теме "Использование данных с одночастотных приемников спутниковых радионавигационных систем для коррекции модели ионосферы" освоил технологию приёма получения данных с одночастотных приемников спутниковых радионавигационных систем, получил данные, разработал программу на C++, которая обрабатывает и сортирует данные двух координат из файла по столбцам, рисует график, чтобы увидеть желаемый результат в точности определения координат спутников, рассмотрел способы уменьшения ошибок измерения псевдодальности и показал, что из-за нестабильности аппаратуры потребителя информация о состоянии ионосферы может быть получена в каждый момент времени по разностям ПД двух навигационных спутников 
Decision:
Защитил диплом выпускной квалификационной работы бакалавра и магистерскую диссертацию.
Source:
# https://forum.calculate-linux.org/t/windows-qemu-kvm-libvirt/9357
# https://blog.sedicomm.com/2019/07/21/rdesktop-klient-rdp-dlya-podklyucheniya-rabochego-stola-windows-iz-linux/
# https://learn.microsoft.com/ru-ru/windows-server/administration/openssh/openssh_install_firstuse
# https://learn.microsoft.com/ru-ru/powershell/scripting/learn/remoting/ssh-remoting-in-powershell-core?view=powershell-7.3
# https://learn.microsoft.com/en-us/powershell/module/storage/dismount-diskimage?view=windowsserver2022-ps
# https://superuser.com/questions/499264/how-can-i-mount-an-iso-via-powershell-programmatically
# https://winitpro.ru/index.php/2016/03/31/sftp-ssh-ftp-na-windows-server-2012-r2/
# https://mhelp.pro/ru/kak-zapustit-powershell-ot-imeni-administratora/?ysclid=lmzwqntxfi559273426
# https://remontka.pro/text-files-cmd-powershell/?ysclid=lmzy2p5172409325479
# https://www.digitalocean.com/community/tutorials/sftp-ru
# http://ftp.glonass-iac.ru/guide/navfaq.php
# https://ru.wtuseripedia.org/wtuseri/%D0%A1%D0%BF%D1%83%D1%82%D0%BD%D0%B8%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D0%BD%D0%B0%D0%B2%D0%B8%D0%B3%D0%B0%D1%86%D0%B8%D0%B8
# Харисов В.Н. Глобальная спутниковая радионавигационная система ГЛОНАСС
# Грудинская Г.П. Распространение радиоволн
# http://begin.clan.su/news/speciftusera_provedenija_psevdodalnomernykh_i_fazov/2013-09-18-135
# http://rrv.iszf.irk.ru/sites/default/files/conf2014/articles/tom2/17-20.pdf
# http://www.u-blox.com
# Дэвис К. Радиоволны в ионосфере
# https://www.youtube.com/watch?v=bDvVosvyVp0&list=LL&index=315

2018-03-01 - 2022-11-01: Всероссийский государственный университет юстиции, Иркутск. Должность: Технический специалист. Дополнительная информация: Обязанности - работа с сайтами, поддержка функционирования серверов и сервисов СУБД, техническая поддержка пользователей / Навыки - Python, Html. Достижения: Разработал программу Обработка и сортировка Html-кода с целью упрощения корректировки тегов на сайте организации по запросам от руководства.

Show

Цель:
# Преобразовать текст в html код формат.
# Изменить тэги по по запросу руководства.
Skills:
# Обработка и сортирока Html-кода.
# Парсинг сайта.
Task:
В файле input.txt надо обычный текст преобразовать в html код формат, добавив только необходимые тэги, и записать результат в output.html.
# Обработка и сортирока Html-кода.
Decision:
root@kvmubuntu:~# vim py.py
root@kvmubuntu:~# cat py.py
#tag - теги html
#file - переменная файла
#fileread - считывание файла
#replacetags - замена тегов
#addtags - добавление тега p в файле
#tags=["<p>","</p>","<br>","   "]
tags=["<", ">", "<br>", "  ", "<strong>Task:</strong>", "<strong>Decision:</strong>", "<strong>Source:</strong>"]
#print(tags[2])
with open("text.txt") as file:
  fileread = file.read()
  #print(fileread)
replacetags=fileread.replace("<", tags[0]).replace(">",tags[1]).replace("\n", tags[2]).replace("  ", tags[3]).replace("Task:", tags[4]).replace("Decision:", tags[5]).replace("Source:", tags[6])
#print(replacetags)
addtags = "<p>"+replacetags+"</p>"
with open("output.txt", "w") as file:
  file.write(addtags+"\n")
Source:
# https://pythonworld.ru/tipy-dannyx-v-python/fajly-rabota-s-fajlami.html - Чтение из файла.
# https://docs-python.ru/tutorial/chtenie-zapis-fajl/odnovremennoe-chtenie-zapis-raznye-fajly/ - Работа с несколькими файлами в Python.
# https://sky.pro/media/chtenie-fajla-postrochno-v-spisok-na-python/ - Использование встроенной функции open().
# https://sky.pro/media/chtenie-fajla-postrochno-v-spisok-na-python/?ysclid=lv9i6ky8w2363195830 - Использование генераторов.
# https://translated.turbopages.org/proxy_u/en-ru.ru.8446d21d-66250fbc-1552d51e-74722d776562/https/stackoverflow.com/questions/54785152/replace-tab-with-space-in-entire-text-file-python?__ya_mt_enable_static_translations=1 - Заменить табуляцию пробелом во всем текстовом файле python.
# https://pythonturbo.ru/kak-v-python-dobavit-tekst-v-fajl/ - Добавление текста в файл с помощью оператора with
Task:
На странице https://tdomain.ru/sveden/education под поле "Образовательная программа, направленность, профиль, шифр и наименование научной специальности" в таблице "Образование" (информация по образовательным программам) необходимо добавить тег itemprop="eduProf". В данной таблице тег <tr itemprop="eduOp">, который нужно заменить на <tr itemprop="eduOprog">.
# Парсинг сайта.
Decision:
БУДЕТ КОД. ИНФОРМАЦИЯ БУДЕТ ДОПОЛНЯТЬСЯ. ШАГИ ВЫПОЛНЕНИЯ:
1. Прорамма будет апрашивать страницу для парсинга.
2. Парсинг страницы сайта по тегам
3. Поиск тега который нужно аменить
4. амена тега
5. Публикация именения на сайт

2011-09-01 - 2018-05-30: Иркутский государственный университет, Иркутск. Должность: Информационные технологии и телекоммуникационные системы - Бакалавр / Электроника и наноэлектроника - Магистр / Информационная безопасность - Дополнительное образование. Дополнительная информация: Навыки - Windows, Виртуализация, Linux, Bash. Достижения: Для защиты выпускной экзаменационной работы по теме "Утилита для сканирования безопасности сети Nmap" подготовил несколько тестовых виртуальных машин ОС, настроил Vsftpd-сервер, Nginx-сервер, Proxy-сервер, просканировал виртуальные машины и настроил Iptables.

Show

Цель:
# Установить и настроить несколько виртуальных машин.
# Настроить Iptables в Centos.
# Анализ сетевых портов в Centos.
# Написать скрипт для набора правил iptables.
# Установить и настроить Ftp сервер.
# Установить и настроить Nginx сервер.
# Установить и настроить Proxy сервер.
# Анализ сетевых портов утилитой Nmap.
# Перехват трафиков утилитой Tcpdump.
# Блокировка подозрительных IP-адресов.
# Установить и настроить tripwire.
Skills:
# Администрирование локальных, виртуальных и облачных серверов.
# Написаниие скриптов.
# Анализ сетевых технологий.
Task:
Выводим список текущих правил iptables и проанализируем какие порты открыты в сервере Centos.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipcentos
[root@kvmcentos ~]# yum install iptables-services
[root@kvmcentos ~]# iptables --version
[root@kvmcentos ~]# iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
[root@kvmcentos ~]# systemctl start iptables
[root@kvmcentos ~]# systemctl enable iptables
[root@kvmcentos ~]# service iptables status
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipcentos
...
PORT STATE SERVICE
22/tcp open ssh
Task:
В сервере Centos Напишем набор правил iptables, в котором мы разрешаем все исходящие соединения и строго ограничиваем входящие.
Доступ будет возможен по портам TCP: 21, 22, 25, 53, 80, 143, 443, по портам UDP: 20, 21, 53, также мы пропускаем пакеты для уже установленных соединений.
С удаленной машины просканируем порты на нашем сервере.
# Написание скриптов.
Decision:
[root@kvmcentos ~]# vim firewall.sh
[root@kvmcentos ~]# cat firewall.sh
#!/bin/bash
IPT="/sbin/iptables"
# Очищаем правила и удаляем цепочки.
root@aw:/# IPT -F
root@aw:/# IPT -X
# По умолчанию доступ запрещен.
root@aw:/# IPT -P INPUT DROP
root@aw:/# IPT -P FORWARD DROP
root@aw:/# IPT -P OUTPUT DROP
# Список разрешенных TCP и UDP портов.
TCP_PORTS="21,22,25,53,80,143,443"
UDP_PORTS="53,21,20"
# Разрешаем пакеты для интерфейса обратной петли.
root@aw:/# IPT -A INPUT -i lo -j ACCEPT
root@aw:/# IPT -A OUTPUT -o lo -j ACCEPT
# Разрешаем пакеты для установленных соединений.
root@aw:/# IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Разрешаем исходящие соединения.
root@aw:/# IPT -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Разрешаем доступ к портам, описанным в переменных TCP_PORTS и UDP_PORTS.
root@aw:/# IPT -A INPUT -p tcp -m multiport --dport root@aw:/#TCP_PORTS -j ACCEPT
root@aw:/# IPT -A INPUT -p udp -m multiport --dport root@aw:/#UDP_PORTS -j ACCEPT
# Разрешаем исходящий ping.
root@aw:/# IPT -A INPUT -p icmp -m icmp --icmp-type echo-reply -j ACCEPT
[root@kvmcentos ~]# chmod +x firewall.sh
[root@kvmcentos ~]# ./firewall.sh
[root@kvmcentos ~]# iptables -L -v
Chain INPUT (policy DROP 1986 packets, 87384 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
79 5604 ACCEPT all -- any any anywhere anywhere state RELATED,ESTABLISHED
9 396 ACCEPT tcp -- any any anywhere anywhere multiport dports ftp,ssh,smtp,domain,http,imap,https
0 0 ACCEPT udp -- any any anywhere anywhere multiport dports domain,ftp,ftp-data
0 0 ACCEPT icmp -- any any anywhere anywhere icmp echo-reply
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- any lo anywhere anywhere
60 7920 ACCEPT all -- any any anywhere anywhere state NEW,RELATED,ESTABLISHED
[root@kvmcentos ~]# service iptables save
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipcentos
...
PORT STATE SERVICE
21/tcp closed ftp
22/tcp open ssh
25/tcp closed smtp
53/tcp closed domain
80/tcp closed http
143/tcp closed imap
443/tcp closed https
...
Source:
# https://blog.sedicomm.com/2016/12/16/iptables-ustanovka-i-nastrojka/?ysclid=ln3wplng53988958006#1
Task:
Обнаружим активные устройства в сети.
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ apt install nmap
┌──(tuser㉿kvmkali)-[~]
└─$ nmap -sL tipcentos/24
...
Nmap scan report for KvmKali (tipkali)
...
Nmap scan report for kvmcentos (tipcentos)
...
Nmap scan report for kvmubuntu (tipubuntu)
...
Task:
Просканируем хост и проанализируем порт ftp. В некоторых случаях можно вытащить логин и пароль. Такое происходит, когда используются параметры входа по умолчанию.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# nmap -sV tipcentos
...
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 8.7 (protocol 2.0)
80/tcp open http nginx 1.22.1
3128/tcp open http-proxy Squid http proxy 5.5
...
[root@kvmcentos ~]# nmap -sC tipcentos -p 21
...
PORT STATE SERVICE
21/tcp open ftp
...
root@aw:/# find /usr/share/nmap/scripts/ -name '*.nse' | grep ftp
...
/usr/share/nmap/scripts/ftp-brute.nse
...
root@aw:/# nmap --script-help ftp-brute.nse
...
Performs brute force password auditing against FTP servers.
...
[root@kvmcentos ~]# nmap --script ftp-brute.nse tipcentos -p 21
...
PORT STATE SERVICE
21/tcp open ftp
| ftp-brute:
| Accounts: No valid accounts found
| Statistics: Performed 324 guesses in 642 seconds, average tps: 7.5
|_ ERROR: The service seems to have failed or is heavily firewalled...
...
Task:
Сканируем диапазон портов.
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap -sT -p 21-80 tipcentos
...
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
25/tcp closed smtp
53/tcp closed domain
Task:
Просканируем удаленный хост (агрессивный режим).
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap -A -T4 tipcentos
...
PORT STATE SERVICE VERSION
20/tcp closed ftp-data
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 8.7 (protocol 2.0)
25/tcp closed smtp
53/tcp closed domain
143/tcp closed imap
443/tcp closed https
3128/tcp open http-proxy Squid http proxy 5.5
|_http-server-header: squid/5.5
|_http-title: ERROR: The requested URL could not be retrieved
MAC Address: tmaccentos (QEMU virtual NIC)
Aggressive OS guesses: Linux 2.6.32 - 3.13 (94%), Linux 2.6.22 - 2.6.36 (92%), Linux 3.10 (92%), Linux 3.10 - 4.11 (92%), Linux 2.6.39 (92%), Linux 2.6.32 (91%), Linux 3.2 - 4.9 (91%), Linux 2.6.32 - 3.10 (91%), Linux 2.6.18 (90%), Linux 3.16 - 4.6 (90%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 1 hop
Service Info: OS: Unix
TRACEROUTE
HOP RTT ADDRESS
1 0.83 ms tipcentos
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 24.18 seconds
Source:
# https://losst.ru/kak-polzovatsya-nmap-dlya-skanirovaniya-seti
Task:
Перехватываем DNS-трафик между сервером и каким-нибудь узлом в сети.
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipwindows12
...
PORT STATE SERVICE
53/tcp open domain
80/tcp open http
443/tcp open https
...
[root@kvmcentos ~]# tcpdump -i enp1s0 -n -nn -ttt 'host tipwindows12 and port 53'
Task:
Перехватываем весь трафик для MAC-адреса tmaccentos на сетевом интерфейсе enp1s0.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# ifconfig
enp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
...
ether tmaccentos txqueuelen 1000 (Ethernet)
...
[root@kvmcentos ~]# tcpdump -n -i enp1s0 "ether host tmaccentos"
...
┌──(tuser㉿kvmkali)-[~]
└─$ ssh tuser@tipcentos
[root@kvmcentos ~]# tcpdump -n -i enp1s0 "ether host tmaccentos"
...
00:11:12.536934 IP tipcentos.58598 > tipubuntu.22: Flags [.], ack 3730, win 501, options [nop,nop,TS val 3107339434 ecr 2462889902], length 0
00:11:12.586762 IP tipubuntu.22 > tipcentos.58598: Flags [P.], seq 3730:3782, ack 2242, win 501, options [nop,nop,TS val 2462889993 ecr 3107339434], length 52
00:11:12.586838 IP tipubuntu.22 > tipcentos.58598: Flags [P.], seq 3782:3898, ack 2242, win 501, options [nop,nop,TS val 2462889993 ecr 3107339434], length 116
00:11:12.588205 IP tipcentos.58598 > tipubuntu.22: Flags [.], ack 3782, win 501, options [nop,nop,TS val 3107339485 ecr 2462889993], length 0
00:11:12.588207 IP tipcentos.58598 > tipubuntu.22: Flags [.], ack 3898, win 501, options [nop,nop,TS val 3107339486 ecr 2462889993], length 0
Task:
Перехватываем только ICMP-пакеты.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# tcpdump -i enp1s0 -n -nn -ttt 'ip proto \icmp'
...
┌──(tuser㉿kvmkali)-[~]
└─$ ping tipcentos
...
64 bytes from tipcentos: icmp_seq=1 ttl=64 time=0.692 ms
64 bytes from tipcentos: icmp_seq=2 ttl=64 time=0.764 ms
64 bytes from tipcentos: icmp_seq=3 ttl=64 time=0.868 ms
64 bytes from tipcentos: icmp_seq=4 ttl=64 time=1.01 ms
64 bytes from tipcentos: icmp_seq=5 ttl=64 time=1.13 ms
^C
...
[root@kvmcentos ~]# tcpdump -i enp1s0 -n -nn -ttt 'ip proto \icmp'
...
00:00:00.000000 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 1, length 64
00:00:00.000171 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 1, length 64
00:00:01.001145 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 2, length 64
00:00:00.000077 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 2, length 64
00:00:01.001365 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 3, length 64
00:00:00.000077 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 3, length 64
00:00:01.001375 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 4, length 64
00:00:00.000077 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 4, length 64
00:00:01.001573 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 5, length 64
00:00:00.000125 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 5, length 64
Task:
Перехватываем входящий трафик на порт 80. сохраняем статистику в файл my.log для первых 500 пакетов. Будет создан бинарный файл my.log.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# tcpdump -v -i enp1s0 dst port 80
...
┌──(tuser㉿kvmkali)-[~]
└─$ firefox tipcentos:80
[root@kvmcentos ~]# tcpdump -v -i enp1s0 dst port 80
...
14:07:24.835633 IP (tos 0x0, ttl 64, id 15549, offset 0, flags [DF], proto TCP (6), length 60)
kvmcentos.45022 > kvmubuntu.http: Flags [S], cksum 0x76d1 (incorrect -> 0x9a90), seq 1076682845, win 64240, options [mss 1460,sackOK,TS val 3110711737 ecr 0,nop,wscale 7], length 0
14:07:24.836511 IP (tos 0x0, ttl 64, id 15550, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [.], cksum 0x76c9 (incorrect -> 0x51b3), ack 2952605173, win 502, options [nop,nop,TS val 3110711738 ecr 3247231251], length 0
14:07:24.836838 IP (tos 0x0, ttl 64, id 15551, offset 0, flags [DF], proto TCP (6), length 412)
kvmcentos.45022 > kvmubuntu.http: Flags [P.], cksum 0x7831 (incorrect -> 0xb0a2), seq 0:360, ack 1, win 502, options [nop,nop,TS val 3110711739 ecr 3247231251], length 360: HTTP, length: 360
GET / HTTP/1.1
Host: tipubuntu
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
14:07:24.844920 IP (tos 0x0, ttl 64, id 15552, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [.], cksum 0x76c9 (incorrect -> 0x4285), ack 3522, win 489, options [nop,nop,TS val 3110711747 ecr 3247231260], length 0
14:07:29.846014 IP (tos 0x0, ttl 64, id 15553, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [F.], cksum 0x76c9 (incorrect -> 0x2eef), seq 360, ack 3522, win 501, options [nop,nop,TS val 3110716748 ecr 3247231260], length 0
14:07:29.846795 IP (tos 0x0, ttl 64, id 15554, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [.], cksum 0x76c9 (incorrect -> 0x1b63), ack 3523, win 501, options [nop,nop,TS val 3110716749 ecr 3247236262], length 0
[root@kvmcentos ~]# tcpdump -v -n -w my.log dst port 80 -c 500
...
[root@kvmcentos ~]# ls my.log
my.log
[root@kvmcentos ~]# tcpdump -nr my.log | awk '{print root@aw:/#3}' | grep -oE '[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}' | sort | uniq -c | sort -rn
reading from file my.log, link-type EN10MB (Ethernet), snapshot length 262144
dropped privs to tcpdump
6 tipkali
 

2018-03-01 - 2022-11-01: Всероссийский государственный университет юстиции, Иркутск. Должность: Технический специалист. Дополнительная информация: Обязанности - работа с сайтами, поддержка функционирования серверов и сервисов СУБД, техническая поддержка пользователей. / Навыки - Виртуализация, Linux, Windows. Достижения: В рамках "Импортозамещения с Windows на Linux" настроил виртуальный сервер с Linux системой и развернул в нем раздачу по сети установщика образа на компьютеры, на что сэкономило время на установку Linux в компьютерных классах.

Show

Цель:
# Установить и настроить Hyper-V в Windows Server 2012.
# Протестировать клиентский компьютер RedOS на работоспособность с ПО.
# Развернуть Pxe сервер для развертывания Redos с загрузкой в Uefi по сети.
# Установить и настроить web-сервер для публикации файлов дистрибутива РЕД ОС в локальной сети.
# Установить и настроить tftp.
# Создать файл kickstart, Записать файл на локальный или удаленный носитель, Создать загрузочный диск, с которого будет запускаться установка, Предоставить доступ к установочной структуре, Начать процесс установки.
# Установить и настроить РЕД ОС по PXE через VNC вместо Kickstart.
# Установить виртуальный сервер AltLinux в Hyper-V.
# Протестировать клиентский компьютер Alt Linux на работоспособность с ПО.
Skills:
# Администрирование локальных, виртуальных и облачных серверов.
Task: 
Ввод компьютера в домен Windows и изменение имени хоста.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum install join-to-domain
[root@hvredos ~]# join-to-domain.sh
[root@hvredos ~]# reboot
[root@hvredos ~]# hostname hvredos.tdomain.ru
[root@hvredos ~]# hostname
hvredos.tdomain.ru
[root@hvredos ~]# vim /etc/hostname
[root@hvredos ~]# cat /etc/hostname
hvredos.tdomain.ru
[root@hvredos ~]# vim /etc/hosts
[root@hvredos ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
127.0.0.1 hvredos.tdomain.ru hvredos
Task:
Установка Консультант Плюс и подключение сетевых директорий с использованием automount и механизма Kerberos.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum install wine winetricks
# winetricks riched30 winhttp
- Wine - Wine Configuration - Графика - уберите галочку в пункте "Разрешить менеджеру окон декорировать окна". - Для запуска «Консультант Плюс» на рабочей станции подключите сетевой диск с «Консультантом» - Сделать это можно с помощью подключения сетевых директорий с использованием automount и механизма Kerberos
[root@hvredos ~]# smbclient -L hvredos -k
Sharename Type Comment
--------- ---- -------
Consultant Disk
ConsultantR Disk
S Disk
...
[root@hvredos ~]# yum install cifs-utils autofs
[root@hvredos ~]# klist
Ticket cache: FILE:/tmp/krb5cc_1
Default principal: tuser@tdomain.ru
Valid starting Expires service principal
31.08.2020 09:51:47 31.08.2020 19:51:47 krbtgt/tdomain.ru@tdomain.ru
renew until 31.08.2020 19:51:47
[root@hvredos ~]# vim /etc/auto.master
[root@hvredos ~]# cat /etc/auto.master
...
/mnt/.hpwindows  /etc/auto.samba  --ghost
[root@hvredos ~]# vim /etc/auto.samba
[root@hvredos ~]# cat /etc/auto.samba
Students   -fstype=cifs,multiuser,cruid=tuser,sec=krb5,domain=tdomain.ru,vers=2.1   ://hpwindows/Students
Task:
Для подключения скрытых сетевых каталогов необходимо экранировать символ $.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Consultant  -fstype=cifs,multiuser,cruid=tuser,sec=krb5,domain=tdomain.ru,vers=2.1   ://hpwindows/Consultant\$
Task:
В файле /etc/krb5.conf нужно закомментировать строку (если она ещё не закомментирована).
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# cat /etc/krb5.conf
...
default_ccache_name = KEYRING:persistent:%{uid}
...
[root@hvredos ~]# vim/etc/krb5.conf
[root@hvredos ~]# cat /etc/krb5.conf
...
default_ccache_name = FILE:/tmp/krb5cc_%{uid}
...
[root@hvredos ~]# systemctl start autofs.service
[root@hvredos ~]# systemctl enable autofs --now
[root@hvredos ~]# ls /mnt/.hpwindows/Consultant/
... cons.exe ...
[root@hvredos ~]# winecfg
[root@hvredos ~]# wine /mnt/.hpwindows/Сonsultant/cons.exe /linux /yes
Task:
Создать ярлык Консультант +.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /home/tuser@tdomain.ru/Рабочий\ стол/Consultant.desktop
[root@hvredos ~]# cat /home/tuser@tdomain.ru/Рабочий\ стол/Consultant.desktop
[Desktop Entry]
Name=ConsultantPlus
Exec=wine /mnt/.hpwindows/Сonsultant/cons.exe
Type=Application
StartupNotify=true
Comment=ConsultantPlus
icon=43D4_Cons.0
StartupWMClass=c.exe
Task:
В случае замедленной работы можно добавить ключ /sprocess=0.
При нормальной работе, не добавляйте этот ключ. 
Ключ /yes необходим для подавления сообщения об ошибке 
[WNetGetUniversalName ...] : NO_NETWORK
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ln -s /mnt/.hvredos/S /home/tuser@tdomain.ru/Рабочий\ стол/S
Task:
Создание ярлыка для сетевой директории
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ln -s /mnt/.hvredos/S /home/tuser@tdomain.ru/Рабочий\ стол/S
Task:
Oграничение доступа к USB накопителям.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /etc/udev/rules.d/99-usb.rules
[root@hvredos ~]# cat /etc/udev/rules.d/99-usb.rules
ENV{ID_USB_DRIVER}=="usb-storage",ENV{UDISKS_IGNORE}="1"
# udevadm control --reload-rules
Task:
Запрет создания ярлыков и файлов на рабочем столе.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# tune2fs -l /dev/sda1 | grep "Default mount options:"
Default mount options: user_xattr acl
[root@hvredos ~]# vim /home/tuser@tdomain.ru/.config/user-dirs.dirs
[root@hvredos ~]# cat /home/tuser@tdomain.ru/.config/user-dirs.dirs
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="$HOME/Рабочий стол"
XDG_DOWNLOAD_DIR="$HOME/Загрузки"
XDG_TEMPLATES_DIR="$HOME/Шаблоны"
XDG_PUBLICSHARE_DIR="$HOME/Общедоступные"
XDG_DOCUMENTS_DIR="$HOME/Документы"
XDG_MUSIC_DIR="$HOME/Музыка"
XDG_PICTURES_DIR="$HOME/Изображения"
XDG_VIDEOS_DIR="$HOME/Видео"
[root@hvredos ~]# ls -la /home/tuser@tdomain.ru/.config
...
-rw-------. 1 tuser ïîëüçîâàòåëè äîìåíà 714 àâã 31 09:52 user-dirs.dirs
-rw-r--r--. 1 tuser ïîëüçîâàòåëè äîìåíà 5 àâã 31 09:52 user-dirs.locale
[root@hvredos ~]# chown root:root /home/tuser@tdomain.ru/.config/user-dirs.dirs
[root@hvredos ~]# ls -la /home/tuser@tdomain.ru/.config
...
-rw-------. 1 root root 714 àâã 31 09:52 user-dirs.dirs
-rw-r--r--. 1 tuser ïîëüçîâàòåëè äîìåíà 5 àâã 31 09:52 user-dirs.locale
[root@hvredos ~]# ls -la
...
drwxr-xr-x. 2 tuser ïîëüçîâàòåëè äîìåíà 4096 àâã 31 09:52 'Рабочий стол'
...
[root@hvredos ~]# chown -R root:root /home/tuser@tdomain.ru/Рабочий\ стол/
[root@hvredos ~]# ls -la
...
drwxr-xr-x. 2 root root 4096 àâã 31 09:52 'Рабочий стол'
drwxr-xr-x. 2 tuser ïîëüçîâàòåëè äîìåíà 4096 àâã 31 09:52 Øàáëîíû
Task:
Как скрыть пользователей от экрана входа.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# cat /var/lib/AccountsService/users/user
[User]
Language=
XSession=
SystemAccount=false
[root@hvredos ~]# vim /var/lib/AccountsService/users/user
[root@hvredos ~]# cat /var/lib/AccountsService/users/user
[User]
Language=
XSession=gnome
SystemAccount=true
Task:
Добавление пользователя из доменной сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /var/lib/AccountsService/users/tuser
[root@hvredos ~]# cat /var/lib/AccountsService/users/tuser
[User]
Language=
XSession=
Icon=/home/tuser@tdomain.ru/.face
SystemAccount=false
Task:
Отключить сетевые принтеры.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /etc/avahi/avahi-daemon.conf
cat /etc/avahi/avahi-daemon.conf
# This file is part of avahi.
...
enable-dbus=no
...
[root@hvredos ~]# reboot
Task:
Установка Gimp.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum provides gimp
[root@hvredos ~]# yum list | grep gimp
gimp.hvredos86 2:2.8.22-2.el7 base
[root@hvredos ~]# yum -y install gimp
Task:
Запись дисков Linux в терминале.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ls
13 'MyTestX Tests'
[root@hvredos ~]# pwd
/home/tuser@tdomain.ru/Documents
[root@hvredos ~]# mkisofs -o first.iso /home/tuser@tdomain.ru/Documents/13
[root@hvredos ~]# ls
13 first.iso 'MyTestX Tests'
[root@hvredos ~]# mkisofs -l -o first.iso /home/tuser@tdomain.ru/Documents/13
[root@hvredos ~]# ls
13 first.iso 'MyTestX Tests'
[root@hvredos ~]# mkisofs -D -l -o first.iso /home/tuser@tdomain.ru/Documents/13
[root@hvredos ~]# mkisofs -J -l -o first.iso /home/tuser@tdomain.ru/Documents/13
Warning: creating filesystem with Joliet extensions but without Rock Ridge
extensions. It is highly recommended to add Rock Ridge.
...
Joliet tree sort failed. The -joliet-long switch may help you.
[root@hvredos ~]# -joliet-long
[root@hvredos ~]# mkisofs -J -joliet-long -l -o first.iso /home/tuser@tdomain.ru/Documents/13
Warning: creating filesystem with Joliet extensions but without Rock Ridge
extensions. It is highly recommended to add Rock Ridge.
...
99.90% done, estimate finish Fri Jan 15 11:29:00 2021
Total translation table size: 0
Total rockridge attributes bytes: 0
Total directory bytes: 0
Path table size(bytes): 10
Task:
Если вы собираетесь записать образ на диск ubuntu DVD-RW/CD-RW необходимо сначала его очистить. 
Здесь и ниже /dev/sr0 - адрес файла вашего привода.
first.iso - это файл образа, 16 - скорость записи, а опция -eject - заставляет извлечь диск из привода после записи. 
Можно указывать скорость 4, 8 и 16. Желательно выбирать минимальную скорость, так диск запишется более качественно.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# cdrecord -dev=/dev/sr0 -v blank=fast
[root@hvredos ~]# cd Documents/
[root@hvredos ~]# ls
13 first.iso 'MyTestX Tests'
[root@hvredos ~]# cdrecord -dev=/dev/sr0 -speed=16 -eject -v first.iso
Task:
Установка клиента 1С.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum -y install webkitgtk3
[root@hvredos ~]# ls
1C_Enterprise83-client-8.3.14-17.x86_64.rpm
1C_Enterprise83-common-8.3.14-17.x86_64.rpm
1C_Enterprise83-server-8.3.14-17.x86_64.rpm
[root@hvredos ~]# yum -y install 1C_Enterprise83-*
Task:
Установка аналога paint.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum list | grep paint
kolourpaint.hvredos86 17.12.1-2.el7 base
kolourpaint.x86_64 17.12.1-2.el7 base
kolourpaint-libs.hvredos86 17.12.1-2.el7 base
kolourpaint-libs.x86_64 17.12.1-2.el7 base
[root@hvredos ~]# yum -y install kolourpaint
Task:
Настройка оповещения и автоматического обновления пакетов в РЕД ОС с помощью yum-cron.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum -y install yum-cron
[root@hvredos ~]# vim /etc/yum/yum-cron.conf
[root@hvredos ~]# cat /etc/yum/yum-cron.conf
[commands]
...
apply_updates = no
[root@hvredos ~]# vim /etc/yum/yum-cron.conf
[root@hvredos ~]# cat /etc/yum/yum-cron.conf
[commands]
...
apply_updates = yes
[root@hvredos ~]# systemctl enable yum-cron --now
Task: 
Если не требуется обновлять определенные пакеты (как вручную, так и автоматически), то добавляем их в исключение.
Например, kernel и php.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# nano /etc/yum.conf
exclude=kernel, php
Task: 
Если не требуется обновлять пакеты ТОЛЬКО в автоматическом режиме, тогда в /etc/yum/yum-cron.conf , в раздел [base], добавляем следующую строку:
# Администрирование локальных, виртуальных и облачных серверов.
Decision
[root@hvredos ~]# nano /etc/yum/yum-cron.conf
exclude=kernel* php*
Task:
Установка Anydesk.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# tee /etc/yum.repos.d/AnyDesk-RHEL.repo <<EOF
> [anydesk]
> name=AnyDesk RHEL - stable
> baseurl=http://rpm.anydesk.com/rhel/\$basearch/
> gpgcheck=1
> repo_gpgcheck=1
> gpgkey=https://keys.anydesk.com/repos/RPM-GPG-KEY
> EOF
[root@hvredos ~]# dnf makecache
[root@hvredos ~]# dnf install -y redhat-lsb-core
[root@hvredos ~]# dnf install anydesk
[root@hvredos ~]# rpm -qi anydesk
[root@hvredos ~]# systemctl status anydesk.service
[root@hvredos ~]# exit
[root@hvredos ~]# anydesk
Task:
Создание загрузочных носителей.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# fdisk -l
...
Диск /dev/sdb: 3,61 GiB, 3880452096 байт, 7579008 секторов
Disk model: Silicon-Power4G
Единицы: секторов по 1 * 512 = 512 байт
Размер сектора (логический/физический): 512 байт / 512 байт
Размер I/O (минимальный/оптимальный): 512 байт / 512 байт
Тип метки диска: gpt
Идентификатор диска: 2BAC44AA-F36D-461C-B12A-D251E7E9373F
Устр-во  начало Конец Секторы Размер Тип
/dev/sdb1  2048 7578974 7576927 3,6G Microsoft basic data
[root@hvredos ~]# ls
Zorin-OS-16.1-Core-64-bit.iso
[root@hvredos ~]# dd if=Zorin-OS-16.1-Core-64-bit.iso of=/dev/sdb1 bs=8MB status=progress oflag=direct
3058237440 байт (3,1 GB, 2,8 GiB) скопирован, 659 s, 4,6 MB/s
382+1 записей получено
382+1 записей отправлено
3058237440 байт (3,1 GB, 2,8 GiB) скопирован, 659,39 s, 4,6 MB/s
Task:
Развернуть Pxe сервер для развертывания Redos с загрузкой в Uefi по сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ifconfig
enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  inet tipwindowsserver netmask Mask1 broadcast IpAddr.255
  ...
[root@hvredos ~]# dnf install dhcp tftp-server syslinux httpd dnf-plugins-core -y
[root@hvredos ~]# mkdir /var/lib/tftpboot/pxelinux.cfg
[root@hvredos ~]# mkdir /var/lib/tftpboot/uefi
[root@hvredos ~]# mkdir -p /var/lib/tftpboot/images/REDOS
[root@hvredos ~]# cp /usr/share/syslinux/{chain.c32,mboot.c32,memdisk,menu.c32,pxelinux.0,ldlinux.c32,libutil.c32} /var/lib/tftpboot/
[root@hvredos ~]# chmod 777 /var/lib/tftpboot/pxelinux.0
[root@hvredos ~]# dnf download shim-x64 grub2-efi-x64 --downloaddir=/root/
[root@hvredos ~]# cd /root/
[root@hvredos ~]# rpm2cpio shim-x64-*.rpm | cpio -dimv
[root@hvredos ~]# rpm2cpio grub2-efi-x64-*.rpm | cpio -dimv
[root@hvredos ~]# cp ./boot/efi/EFI/BOOT/BOOTX64.EFI /var/lib/tftpboot/uefi
[root@hvredos ~]# cp ./boot/efi/EFI/redos/grubx64.efi /var/lib/tftpboot/uefi
[root@hvredos ~]# chmod 777 /var/lib/tftpboot/uefi/*.*
[root@hvredos ~]# umount -t iso9660 -o loop redos-MUROM-7.3.2-20211027.0-Everything-x86_64-DVD1.iso /mnt/
[root@hvredos ~]# cp -vR /mnt/* /var/lib/tftpboot/images/REDOS/
[root@hvredos ~]# umount /mnt/
[root@hvredos ~]# vim /etc/dhcp/dhcpd.conf
[root@hvredos ~]# cat /etc/dhcp/dhcpd.conf
non-authoritative;
allow bootp;
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
subnet tipwindowsserver.0 netmask Mask1 {
option routers tipwindowsserver;
range tipwindowsserver.70 tipwindowsserver.80;
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
next-server tipwindowsserver;
if option architecture-type = 00:07 {
filename "uefi/grubx64.efi";

else {
filename "pxelinux.0";
}
}

# systemctl enable dhcpd --now
[root@hvredos ~]# vim /var/lib/tftpboot/pxelinux.cfg/default
[root@hvredos ~]# cat /var/lib/tftpboot/pxelinux.cfg/default
default menu.c32
PROMPT 0
TIMEOUT 150
MENU TITLE PXE Menu
LABEL REDOS 7.3
MENU LABEL REDOS 7.3
KERNEL images/REDOS/images/pxeboot/vmlinuz
APPEND initrd=images/REDOS/images/pxeboot/initrd.img ramdisk_size=128000 ip=dhcp inst.repo=http://tipwindowsserver/images/REDOS/ 
[root@hvredos ~]# vim /var/lib/tftpboot/uefi/grub.cfg 
[root@hvredos ~]# cat /var/lib/tftpboot/uefi/grub.cfg 
function load_video { 
insmod efi_gop 
insmod efi_uga 
insmod video_bochs 
insmod video_cirrus 
insmod all_video 

load_video 
set gfxpayload=keep 
insmod gzio 
menuentry 'REDOS 7.3' { 
linux images/REDOS/images/pxeboot/vmlinuz ip=dhcp inst.repo=http://tipwindowsserver/images/REDOS/ 
initrd images/REDOS/images/pxeboot/initrd.img 

Task:
Используем web-сервер для публикации файлов дистрибутива РЕД ОС в локальной сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /etc/httpd/conf.d/pxeboot.conf 
[root@hvredos ~]# cat /etc/httpd/conf.d/pxeboot.conf 
Alias /images /var/lib/tftpboot/images 
<Directory /var/lib/tftpboot/images> 
Options Indexes FollowSymLinks 
Require ip 127.0.0.1 tipwindowsserver.0/24 
</Directory>
[root@hvredos ~]# systemctl enable httpd --now
Task:
Настройка и запуск службы tftp.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /usr/lib/systemd/system/tftp.service
[root@hvredos ~]# cat /usr/lib/systemd/system/tftp.service
...
[Install]:
WantedBy=multi-user.target 
Also=tftp.socket
...
[root@hvredos ~]# vim /usr/lib/systemd/system/tftp.socket
[root@hvredos ~]# cat /usr/lib/systemd/system/tftp.socket
...
[Unit] 
Description=Tftp Server Activation Socket 
[Socket] 
ListenDatagram=0.0.0.0:69 
[Install] 
WantedBy=sockets.target 
...
[root@hvredos ~]# systemctl daemon-reload
[root@hvredos ~]# systemctl enable tftp --now
[root@hvredos ~]# ausearch -c 'httpd' --raw | audit2allow -M my-httpd
[root@hvredos ~]# semodule -i my-httpd.pp
Task:
Автоматизация развертывания (kickstart)
Создать файл kickstart, Записать файл на локальный или удаленный носитель, Создать загрузочный диск, с которого будет запускаться установка, Предоставить доступ к установочной структуре, Начать процесс установки.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# dnf install pykickstart -y
[root@hvredos ~]# cp /root/anaconda-ks.cfg /var/lib/tftpboot
[root@hvredos ~]# mv /var/lib/tftpboot/anaconda-ks.cfg /var/lib/tftpboot/ks.cfg
[root@hvredos ~]# vim /var/lib/tftpboot/pxelinux.cfg/default
[root@hvredos ~]# cat /var/lib/tftpboot/pxelinux.cfg/default
...
APPEND initrd=images/REDOS/images/pxeboot/initrd.img ramdisk_size=128000 ip=dhcp method=http://tipwindowsserver/images/REDOS/ devfs=nomount inst.ks=http://tipwindowsserver/ks.cfg 
[root@hvredos ~]# vim /var/lib/tftpboot/uefi/grub.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/uefi/grub.cfg
... { 
linux images/REDOS/images/pxeboot/vmlinuz ip=dhcp kernel vmlinuz inst.repo=http://tipwindowsserver/images/REDOS/ inst.ks=http://tipwindowsserver/ks.cfg 
initrd images/REDOS/images/pxeboot/initrd.img

[root@hvredos ~]# vim /var/lib/tftpboot/ks.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/ks.cfg
# Здесь указываем раскладку клавиатуры
keyboard --vckeymap=us --xlayouts='us','ru' --switch='grp:alt_shift_toggle'
# Системная локаль
lang ru_RU.UTF-8
# Информация о сетевом интерфейсе и имя машины
network --bootproto=dhcp --device=enp2s0 --noipv6 --activate
network --hostname=hostname1337
# Пароль Root представлен в виде хэш-суммы
rootpw --iscrypted $6$DUu0yyOYMRbGS8gL$9zHYPsxROGEZdDKG0wnf7h8SGnKOp3V272De6oGTVUsz2uBLmEeiR6T6cInRN5dyWcxNXh5fVluEUTQ/3rmzB0
# Настройка сервисов (в данном случае сервис по обновлению меток времени и дат)
services --enabled="chronyd"
# Настройка временной зоны
timezone Europe/Moscow --isUtc
#Настройка локального пользователя
user --groups=wheel --name=mekka --password=$6$83fyYZ7KMS7G9t6A$E5/99/ffOwjUOo8THr1ngqGDdKMimpTZf3IT9S/SI98BTV7dta7GksLYnQEZjtqqyZQrwibSRlvYccRqHB7m8/ --iscrypted --gecos="Mekka"
# Настройка xorg при загрузке
xconfig --startxonboot
# Указание загрузочного сектора и тип структуры
bootloader --location=mbr --boot-drive=sda
# Удаление всей информации с партиций для последующей установки
clearpart --none --initlabel
# Здесь указана вся разметка диска
part /boot --fstype="xfs" --onpart=sda2
part biosboot --fstype="biosboot" --noformat --onpart=sda4
part pv.31 --fstype="lvmpv" --noformat --onpart=sda3
part /boot/efi --fstype="efi" --onpart=sda1 --fsoptions="umask=0077,shortname=winnt"
volgroup ro --noformat --useexisting
logvol swap --fstype="swap" --useexisting --name=swap --vgname=ro
logvol /home --fstype="xfs" --noformat --useexisting --name=home --vgname=ro
logvol / --fstype="ext4" --useexisting --name=root --vgname=ro
#дополнительные пакеты для установки
%packages
@^mate-desktop-environment
@backup-client
@base
@branding
@core
@desktop-debugging
@dial-up
@directory-client
@fonts
@guest-agents
@guest-desktop-agents
@input-methods
@internet-applications
@internet-browser
@java-platform
@mate-desktop
@multimedia
@network-file-system-client
@print-client
@x11
chrony
%end
#настройка аварийных дампов памяти в случае сбоев (оставить как есть)
%addon com_redhat_kdump --enable --reserve-mb='auto'
%end
#настройка анаконды (оставить как есть)
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end
Task:
Настройка установки РЕД ОС по PXE через VNC вместо Kickstart.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /var/lib/tftpboot/pxelinux.cfg/default
[root@hvredos ~]# cat /var/lib/tftpboot/pxelinux.cfg/default
...
APPEND initrd=images/REDOS/images/pxeboot/initrd.img ramdisk_size=128000 ip=dhcp method=http://tipwindowsserver/images/REDOS/ devfs=nomount inst.vnc inst.vncpassword=tpassword
[root@hvredos ~]# vim /var/lib/tftpboot/uefi/grub.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/uefi/grub.cfg
... { 
linux images/REDOS/images/pxeboot/vmlinuz ip=dhcp kernel vmlinuz inst.repo=http://tipwindowsserver/images/REDOS/ inst.vnc inst.vncpassword=tpassword
initrd images/REDOS/images/pxeboot/initrd.img
}
Source:
# https://redos.red-soft.ru/base/other-soft/other-other/consultant/?sphrase_id=53349
# https://wtuseri.astralinutdomain.ru/pages/viewpage.action?pageId=61574227
# https://askubuntu.ru/questions/21203/kak-skry-t-pol-zovatelej-ot-e-krana-vxoda-v-gdm
# https://computingforgeeks.com/how-to-install-gimp-on-centos-rhel-8-desktop/
# https://ru.wtuserihow.com/%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C-ISO-%D1%84%D0%B0%D0%B9%D0%BB-%D0%B2-Linux
# https://losst.ru/zapis-diskov-v-ubuntu
# https://losst.ru/luchshie-analogi-paint-dlya-linux 
# https://computingforgeeks.com/how-to-install-anydesk-on-centos-rhel-8/
Task:
Set up an ActiveDirectory/Login.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# apt-get install task-auth-ad-sssd
root@hvalt:~# net time set -S tdomain.ru
# system-auth write ad tdomain.ru hvalt hvalt 'tuser' 'tpassword'
Using short domain name -- hvalt
Joined 'hvalt' to dns domain 'tdomain.ru'
Successfully registered hostname with DNS
failed to call wbcGetDisplayName: WBC_ERR_WINBIND_NOT_AVAILABLE
Could not lookup sid S-1-5-21-965402400-3010625364-1855727791-513
failed to call wbcGetDisplayName: WBC_ERR_WINBIND_NOT_AVAILABLE
Could not lookup sid S-1-5-21-965402400-3010625364-1855727791-512
root@hvalt:~# wbinfo -t
checking the trust secret for domain hvredos via RPC calls succeeded
root@hvalt:~# acc
2 keyboards found
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platfohvredos plugin "xcb" in "" even though it was found.
This applhvredosation failed to start because no Qt platfohvredos plugin could be initialized. Reinstalling the applhvredosation may fix this problem.
Available platfohvredos plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, xcb.
root@hvalt:~# exit
root@hvalt:~# exit
[root@hvredos ~]# ssh -X tuser@hvalt
root@hvalt:~# su -
root@hvalt:~# acc
2 keyboards found
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/.private/root/runtime-root'
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
WARNING: (alterator lookout evaluation): imported module (alterator presentation events) overrides core binding `when'
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
root@hvalt:~# vim /etc/net/ifaces/eth0/resolv.conf
root@hvalt:~# cat /etc/net/ifaces/eth0/resolv.conf
nameserver tipwindowsserver
root@hvalt:~# hostnamectl set-hostname hvalt.tdomain.ru
root@hvalt:~# cat /etc/resolv.conf
search tdomain.ru
nameserver 127.0.0.1
root@hvalt:~# vim /etc/resolv.conf
root@hvalt:~# cat /etc/resolv.conf
# Configuration for resolv(8)
# See resolv.conf(5) for details
resolv_conf_head='# Do not edit manually, use\n# /etc/net/ifaces/<interface>/resolv.conf instead.'
resolv_conf=/etc/resolv.conf
# These interfaces will always be processed first.
interface_order='lo lo[0-9]* lo.*'
# These interfaces will be processed next, unless they have a metrhvredos.
dynamhvredos_order='tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*'
#Configuration files for named subscriber.
named_zones=/var/lib/bind/etc/resolv-zones.conf
named_options=/var/lib/bind/etc/resolv-options.conf
#Configuration files for dnsmasq subscriber.
dnsmasq_conf=/etc/dnsmasq.conf.d/60-resolv
dnsmasq_resolv=/etc/resolv.conf.dnsmasq
name_servers=127.0.0.1
root@hvalt:~# vim /etc/resolv.conf
root@hvalt:~# cat /etc/resolv.conf
# Configuration for resolv(8)
# See resolv.conf(5) for details
resolv_conf_head='# Do not edit manually, use\n# /etc/net/ifaces/<interface>/resolv.conf instead.'
resolv_conf=/etc/resolv.conf
# These interfaces will always be processed first.
#interface_order='lo lo[0-9]* lo.*'
interface_order='lo lo[0-9]* lo.* eth0'
search_domains=tdomain.ru
# These interfaces will be processed next, unless they have a metrhvredos.
dynamhvredos_order='tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*'
#Configuration files for named subscriber.
named_zones=/var/lib/bind/etc/resolv-zones.conf
named_options=/var/lib/bind/etc/resolv-options.conf
#Configuration files for dnsmasq subscriber.
dnsmasq_conf=/etc/dnsmasq.conf.d/60-resolv
dnsmasq_resolv=/etc/resolv.conf.dnsmasq
#name_servers=127.0.0.1
root@hvalt:~# resolv -u
root@hvalt:~# cat /etc/resolv.conf
# Generated by resolv
# Do not edit manually, use
# /etc/net/ifaces/<interface>/resolv.conf instead.
search tdomain.ru
nameserver tipwindowsserver
...
nameserver 8.8.8.8
root@hvalt:~# hostname
hvalt.tdomain.ru
root@hvalt:~# dig _kerberos._udp.tdomain.ru SRV | grep ^_kerberos
_kerberos._udp.tdomain.ru. 600 IN SRV 0 100 88 M-1.tdomain.ru.
...
_kerberos._udp.tdomain.ru. 600 IN SRV 0 100 88 T-1.tdomain.ru.
root@hvalt:~# dig _kerberos._tcp.tdomain.ru SRV | grep ^_kerberos
_kerberos._tcp.tdomain.ru. 600 IN SRV 0 100 88 M-c.tdomain.ru.
...
_kerberos._tcp.tdomain.ru. 600 IN SRV 0 100 88 T-1.tdomain.ru.
root@hvalt:~# cat /etc/krb5.conf | grep default_realm
default_realm = tdomain.ru
# default_realm = EXAMPLE.COM
root@hvalt:~# cat /etc/krb5.conf | grep dns_lookup_realm
dns_lookup_realm = false
root@hvalt:~# cat /etc/krb5.conf | grep dns_lookup_kdc
dns_lookup_kdc = true
root@hvalt:~# exit
[root@hvredos ~]# ssh -X tuser@hvalt
root@hvalt:~# kinit tuser
root@hvalt:~# klist
Thvredosket cache: KEYRING:persistent:0:krb_ccache_CCjHpN1
Default principal: a-r@tdomain.ru
Valid starting   Expires       Servhvredose principal
10.08.2022 12:30:34 10.08.2022 22:30:34 krbtgt/tdomain.ru@tdomain.ru
  renew until 17.08.2022 12:30:32
root@hvalt:~# apt-get install samba-client
root@hvalt:~# cat /etc/samba/smb.conf | grep realm
  realm = tdomain.ru
root@hvalt:~# cat /etc/samba/smb.conf | grep workgroup
  workgroup = hvalt
root@hvalt:~# cat /etc/samba/smb.conf | grep netbios
  netbios name = hvalt
root@hvalt:~# cat /etc/samba/smb.conf | grep security
  security = ads
root@hvalt:~# cat /etc/samba/smb.conf | grep method
  kerberos method = system keytab
root@hvalt:~# cat /etc/samba/smb.conf | grep idmap
    idmap config * : range = 200000-2000200000
    idmap config * : backend = sss
root@hvalt:~# vim /etc/samba/smb.conf
root@hvalt:~# cat /etc/samba/smb.conf | grep idmap
    idmap config * : range = 200000-2000200000
;    idmap config * : backend = sss
  idmap config * : backend = tdb
# testpahvalt
Load smb config files from /etc/samba/smb.conf
Loaded servhvredoses file OK.
Weak crypto is allowed
Server role: ROLE_DOMAIN_MEMBER
Press enter to see a dump of your servhvredose definitions
# Global parameters
[global]
  kerberos method = system keytab
  machine password timeout = 0
  realm = tdomain.ru
  security = ADS
  template homedir = /home/tdomain.ru/%U
  template shell = /bin/bash
  winbind use default domain = Yes
  workgroup = hvredos
  idmap config * : range = 200000-2000200000
  idmap config * : backend = tdb
[share]
  comment = Commonplace
  path = /srv/share
  read only = No
[homes]
  browseable = No
  comment = Home Directory for '%u'
  read only = No
root@hvalt:~# cat /etc/hosts
127.0.0.1 localhost.localdomain localhost
root@hvalt:~# vim /etc/hosts
root@hvalt:~# cat /etc/hosts
127.0.0.1 hvalt.tdomain.ru hvalt
root@hvalt:~# net ads join -U tuser
Enter tuser's password:
Using short domain name -- hvalt
Joined 'hvalt' to dns domain 'tdomain.ru'
root@hvalt:~# klist -k -e
Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
9 host/hvalt.tdomain.ru@tdomain.ru (aes256-cts-hmac-sha1-96)
9 host/hvalt@tdomain.ru (aes256-cts-hmac-sha1-96)
9 host/hvalt.tdomain.ru@tdomain.ru (aes128-cts-hmac-sha1-96)
9 host/hvalt@tdomain.ru (aes128-cts-hmac-sha1-96)
9 host/hvalt.tdomain.ru@tdomain.ru (DEPRECATED:arcfour-hmac)
9 host/hvalt@tdomain.ru (DEPRECATED:arcfour-hmac)
9 hvalt$@tdomain.ru (aes256-cts-hmac-sha1-96)
9 hvalt$@tdomain.ru (aes128-cts-hmac-sha1-96)
9 hvalt$@tdomain.ru (DEPRECATED:arcfour-hmac)
8 host/hvalt.tdomain.ru@tdomain.ru (aes256-cts-hmac-sha1-96)
8 host/hvalt@tdomain.ru (aes256-cts-hmac-sha1-96)
8 host/hvalt.tdomain.ru@tdomain.ru (aes128-cts-hmac-sha1-96)
8 host/hvalt@tdomain.ru (aes128-cts-hmac-sha1-96)
8 host/hvalt.tdomain.ru@tdomain.ru (DEPRECATED:arcfour-hmac)
8 host/hvalt@tdomain.ru (DEPRECATED:arcfour-hmac)
8 hvalt$@tdomain.ru (aes256-cts-hmac-sha1-96)
8 hvalt$@tdomain.ru (aes128-cts-hmac-sha1-96)
8 hvalt$@tdomain.ru (DEPRECATED:arcfour-hmac)
root@hvalt:~# apt-get install sssd-ad
root@hvalt:~# cat /etc/sssd/sssd.conf
[sssd]
config_file_version = 2
servhvredoses = nss, pam
# Managed by system facility command:
## control sssd-drop-privileges unprivileged|privileged|default
user = _sssd
# SSSD will not start if you do not configure any domains.
domains = tdomain.ru
[nss]
[pam]
[domain/tdomain.ru]
id_provider = ad
auth_provider = ad
chpass_provider = ad
access_provider = ad
default_shell = /bin/bash
fallback_homedir = /home/%d/%u
debug_level = 0
; cache_credentials = false
ad_gpo_ignore_unreadable = true
ad_gpo_access_control = pehvredosissive
ad_update_samba_machine_account_password = true
root@hvalt:~# vim /etc/sssd/sssd.conf
root@hvalt:~# cat /etc/sssd/sssd.conf
[sssd]
config_file_version = 2
servhvredoses = nss, pam
# Managed by system facility command:
## control sssd-drop-privileges unprivileged|privileged|default
#user = _sssd
user=root
# SSSD will not start if you do not configure any domains.
domains = tdomain.ru
[nss]
[pam]
[domain/tdomain.ru]
id_provider = ad
auth_provider = ad
chpass_provider = ad
access_provider = ad
;ldap_id_mapping = False
default_shell = /bin/bash
fallback_homedir = /home/%d/%u
debug_level = 0
;use_fully_qualified_names = True
; cache_credentials = True
ad_gpo_ignore_unreadable = true
ad_gpo_access_control = pehvredosissive
ad_update_samba_machine_account_password = true
root@hvalt:~# grep sss /etc/nsswitch.conf
passwd: files sss
shadow: tcb files sss
group: files [SUCCESS=merge] sss role
root@hvalt:~# control system-auth sss
root@hvalt:~# servhvredose sssd status
active
root@hvalt:~# servhvredose sssd start
root@hvalt:~# getent passwd tuser
tuser:*:1-9:1-3:в-н:/home/tdomain.ru/tuser:/bin/bash
root@hvalt:~# id tuser
uid=1-9(tuser) gid=1-3(пользователи домена) группы=1-3(пользователи домена),1-8(администраторы dhcp),1-0(tuser),11-0(пользователи филиалы),1-1(tuser),1-9(i-n)
root@hvalt:~# net ads info
LDAP server: IpAddr2
LDAP server name: MOW-1.tdomain.ru
Realm: tdomain.ru
Bind Path: dc=hvalt,dc=RU
LDAP port: 389
Server time: Ср, 10 авг 2022 13:08:06 +08
KDC server: IpAddr2
Server time offset: 0
Last machine account password change: Ср, 10 авг 2022 12:50:51 +08
root@hvalt:~# net ads testjoin
Join is OK
root@hvalt:~# cat /etc/lightdm/lightdm.conf | grep greeter-hide-
# greeter-hide-users = True to hide the user list
#greeter-hide-users=false
greeter-hide-users = true
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep show-language
show-language-selector = false
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep show-indhvredosators
root@hvalt:~# vim /etc/lightdm/lightdm-gtk-greeter.conf
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep show-indhvredosators
show-indhvredosators=~a11y;~power;~session;~language
root@hvalt:~# vim /etc/lightdm/lightdm-gtk-greeter.conf
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep enter-
enter-username = true
root@hvalt:~# reboot
Task:
Учетные записи и групп в Альт Линукс.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# apt-get install libnss-role
root@hvalt:~# groupadd -r localadmins
groupadd: группа «localadmins» уже существует
root@hvalt:~# groupadd -r remote
groupadd: группа «remote» уже существует
root@hvalt:~# control sshd-allow-groups enabled
root@hvalt:~# sed -i 's/AllowGroups.*/AllowGroups = remote/' /etc/openssh/sshd_config
root@hvalt:~# roleadd users cdwriter cdrom audio proc radio camera floppy xgrp scanner uucp fuse
root@hvalt:~# roleadd localadmins wheel remote vboxusers
root@hvalt:~# roleadd 'Domain Users' users
Error 156: No such group
root@hvalt:~# roleadd 'Пользователи домена' users
root@hvalt:~# roleadd 'Администраторы домена' localadmins
root@hvalt:~# rolelst
users:cdwriter,cdrom,audio,proc,radio,camera,floppy,xgrp,scanner,uucp,fuse,video,vboxusers,vboxadd
localadmins:wheel,remote,vboxusers,vboxadd
пользователи домена:users
администраторы домена:localadmins
powerusers:remote,vboxadd,vboxusers
vboxadd:vboxsf
root@hvalt:~# id tuser
uid=1-9(tuser) gid=1-3(пользователи домена) группы=1-3(пользователи домена),11-0(пользователи филиалы),1-9(i-n),1-0(tuser),1-1(tuser),1-8(администраторы dhcp),100(users),80(cdwriter),22(cdrom),81(audio),19(proc),83(radio),440(camera),71(floppy),466(xgrp),467(scanner),14(uucp),483(fuse),488(video),481(vboxusers),455(vboxadd),454(vboxsf)
root@hvalt:~# roleadd 'tuser' localadmins
root@hvalt:~# roleadd 'tuser' wheel
root@hvalt:~# rolelst
users:cdwriter,cdrom,audio,proc,radio,camera,floppy,xgrp,scanner,uucp,fuse,video,vboxusers,vboxadd
localadmins:wheel,remote,vboxusers,vboxadd
пользователи домена:users
администраторы домена:localadmins
tuser:localadmins
powerusers:remote,vboxadd,vboxusers
vboxadd:vboxsf
root@hvalt:~# exit
[root@hvredos ~]# ssh -X tuser@hvalt
root@hvalt:~# id tuser
uid=1-9(tuser) gid=1-3(пользователи домена) группы=1-3(пользователи домена),11-0(пользователи филиалы),1-9(i-n),1-0(tuser),1-1(tuser),1-8(администраторы dhcp),100(users),80(cdwriter),22(cdrom),81(audio),19(proc),83(radio),440(camera),71(floppy),466(xgrp),467(scanner),14(uucp),483(fuse),488(video),481(vboxusers),455(vboxadd),454(vboxsf),101(localadmins),10(wheel),110(remote)
Task:
Добавить сетевые папки.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# apt-get install autofs
root@hvalt:~# vim /etc/auto.master
root@hvalt:~# cat /etc/auto.master
# Fohvredosat of this file:
# mountpoint map options
# For details of the fohvredosat look at autofs(8).
/mnt/auto /etc/auto.tab -t 5
/mnt/net  /etc/auto.avahi -t 120
/mnt/.tdirectory /etc/auto.samba --ghost
root@hvalt:~# vim /etc/auto.samba
root@hvalt:~# cat /etc/auto.samba
s  -fstype=cifs,multiuser,cruid=$USER,sec=krb5,domain=tdomain.ru,vers=1.0 ://hvalt/tdirectory1
o  -fstype=cifs,multiuser,cruid=$USER,sec=krb5,domain=tdomain.ru,vers=1.0 ://hvalt/tdirectory2
root@hvalt:~# systemctl enable autofs
root@hvalt:~# systemctl start autofs
root@hvalt:~# ls -la /mnt/.tdirectory/
drwxr-xr-x 4 root root  0 авг 11 14:09 .
drwxr-xr-x 5 root root 4096 авг 11 14:09 ..
d????????? ? ?  ?   ?      ? o
d????????? ? ?  ?   ?      ? s
Task:
Creating a Network Bridge interface.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# mkdir /etc/net/ifaces/tethernet1
root@hvalt:~# cp /etc/net/ifaces/tethernet/* /etc/net/ifaces/tethernet1
root@hvalt:~# rm -f /etc/net/ifaces/tethernet/{i,r}*
root@hvalt:~# ls /etc/net/ifaces/tethernet1/
ipv4address options resolv.conf
root@hvalt:~# cat /etc/net/ifaces/tethernet1/options
BOOTPROTO=dhcp
TYPE=eth
NM_CONTROLLED=yes
DISABLED=yes
CONFIG_WIRELESS=no
SYSTEMD_BOOTPROTO=dhcp4
CONFIG_IPV4=yes
SYSTEMD_CONTROLLED=no
root@hvalt:~# vim /etc/net/ifaces/tethernet1/options
root@hvalt:~# ls /etc/net/ifaces/
default tethernet lo unknown tethernet1
root@hvalt:~# vim /etc/net/ifaces/tethernet1/options
root@hvalt:~# cat /etc/net/ifaces/tethernet1/options
BOOTPROTO=static
CONFIG_WIRELESS=no
CONFIG_IPV4=yes
HOST='tethernet'
ONBOOT=yes
TYPE=bri
root@hvalt:~# ls /etc/net/ifaces/tethernet/
ipv4address options resolv.conf
root@hvalt:~# service network restart
Source: 
# https://www.altlinux.org/ActiveDirectory/Login#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BE%D0%B2
# https://www.altlinux.org/SSSD/AD
# https://www.altlinux.org/%D0%A1%D0%B5%D1%82%D0%B5%D0%B2%D0%BE%D0%B9_%D0%BC%D0%BE%D1%81%D1%82
# https://www.altlinux.org/PostgreSQL#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B8_%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D0%BA
# https://www.tecmint.com/install-postgresql-and-pgadmin-in-ubuntu/
# https://o7planning.org/11353/install-pgadmin-on-ubuntu
# https://redos.red-soft.ru/base/server-configuring/dbms/install-postgresql/?sphrase_id=53348
# https://redos.red-soft.ru/base/server-configuring/dbms/pgadmin4/

2022-11-01 - 2023-05-30: Сбер Университет, Иркутск. Должность: Инженер с большими данными - Дополнительное образование. Дополнительная информация: Навыки: Linux, Sql, Python, Bash, Etl-процессы, DWH, AntiFraud. Достижения: При работе с транзакционными банковскими данными разработал хранилище данных.

Show

Цель:
# Разработка ETL процесса, получающий ежедневную выгрузку данных (предоставляется за 3 дня), загружающий ее в хранилище данных и ежедневно строящий отчет.
# Создать таблицы в базе данных.
# Придумать Алгоритм работы.
# Заполните файл main.cron расписанием и командой исполнения вашего скрипта. Расписание установите так, как считаете нужным чтобы ваши данные заполнились корректно.
Skills:
# Разработка ETL процесса, для работы с отчетами.
# Написание Sql запросов.
# Администрирование локальных, виртуальных и облачных серверов.
Task:
Разработка ETL процесса, получающий ежедневную выгрузку данных (предоставляется за 3 дня), загружающий ее в хранилище данных и ежедневно строящий отчет.
Ежедневно некие информационные системы выгружают три следующих файла:
1. Список транзакций за текущий день. Формат – CSV.
2. Список терминалов полным срезом. Формат – XLSX.
3. Список паспортов, включенных в «черный список» - с накоплением с начала месяца. Формат – XLSX.
Сведения о картах, счетах и клиентах хранятся в СУБД PostgreSQL. Во вложении реквизиты для подключения. 
Вам предоставляется выгрузка за последние три дня, ее надо обработать. В качестве хранилища выступает ваша учебная база (edu). Данные должны быть загружены в хранилище со следующей структурой (имена сущностей указаны по существу, без особенностей правил нейминга, указанных далее).
Типы данных в полях можно изменять на однородные если для этого есть необходимость. Имена полей менять нельзя. Ко всем таблицам SCD1 должны
быть добавлены технические поля create_dt, update_dt; ко всем таблицам SCD2 должны быть добавлены технические поля effective_from, effective_to, deleted_flg.
По результатам загрузки ежедневно необходимо строить витрину отчетности по мошенническим операциям. Витрина строится накоплением, каждый новый отчет укладывается в эту же таблицу с новым report_dt.
В витрине должны содержаться следующие поля:
1. event_dt - Время наступления события. Если событие наступило по результату нескольких действий – указывается время действия, по которому установлен факт мошенничества.
2. passport - Номер паспорта клиента, совершившего мошенническую операцию.
3. fio - ФИО клиента, совершившего мошенническую операцию.
4. phone - Номер телефона клиента, совершившего мошенническую операцию.
5. event_type - Описание типа мошенничества.
6. report_dt - Время построения отчета.
Признаки мошеннических операций:
1. Совершение операции при просроченном или заблокированном паспорте.
2. Совершение операции при недействующем договоре.
3. Совершение операций в разных городах в течение одного часа.
4. Попытка подбора суммы. В течение 20 минут проходит более 3х операций соследующим шаблоном – каждая последующая меньше предыдущей, при этом отклонены все кроме последней. Последняя операция (успешная) в такой цепочке считается мошеннической.
При именовании таблиц необходимо придерживаться следующих правил (для автоматизации проверки):
1. tuser.<CODE>_STG_<TABLE_NAME> - Таблицы для размещения стейджинговых таблиц (первоначальная загрузка), промежуточное выделение инкремента, если требуется. Временные таблицы, если такие потребуются в расчете, можно также складывать с таким именованием. Имя таблиц можете выбирать произвольное, но смысловое.
2. tuser.<CODE>_DWH_FACT_<TABLE_NAME> - Таблицы фактов, загруженных в хранилище. В качестве фактов выступают сами транзакции и «черный список» паспортов. Имя таблиц – как в ER диаграмме.
3. tuser.<CODE>_DWH_DIM_<TABLE_NAME> - Таблицы измерений, хранящиеся в формате SCD1. Имя таблиц – как в ER диаграмме.
4. tuser.<CODE>_DWH_DIM_<TABLE_NAME>_HIST - Таблицы измерений, хранящиеся в SCD2 формате (только для тех, кто выполняет усложненное задание). Имя таблиц – как в ER диаграмме.
5. tuser.<CODE>_REP_FRAUD - Таблица с отчетом.
6. tuser.<CODE>_META_<TABLE_NAME> - Таблицы для хранения метаданных. Имя таблиц можете выбирать произвольное, но
смысловое.
7. <CODE> - 4 буквы вашего персонального кода.
Выгружаемые файлы именуются согласно следующему шаблону:
1. transactions_DDMMYYYY.txt
2. passport_blacklist_DDMMYYYY.xlsx
3. terminals_DDMMYYYY.xlsx
Предполагается что в один день приходит по одному такому файлу. После загрузки соответствующего файла он должен быть переименован в файл с расширением .backup чтобы при следующем запуске файл не искался и перемещен в каталог archive:
1. transactions_DDMMYYYY.txt.backup
2. passport_blacklist_DDMMYYYY.xlsx.back
3. upterminals_DDMMYYYY.xlsx.backup
Желающие могут придумать, обосновать и реализовать более технологичные и учитывающие сбои способы обработки (за это будет повышен балл).
В classroom выкладывается zip-архив, содержащий следующие файлы и каталоги:
1. main.py - Файл, обязательный. Основной процесс обработки.
2. файлы с данными - Файл, обязательный. Те файлы, которые вы получили в качестве задания. Просто скопируйте все 9 файлов.
3. main.ddl - Файл, обязательный. Файл с SQL кодом для создания всех необходимых объектов в базе edu.
4. main.cron - Файл, обязательный. Файл для постановки вашего процесса на расписание, в формате crontab
5. archive - Каталог, обязательный. Пустой, сюда должны перемещаться отработанные файлы
6. sql_scripts - Каталог, необязательный. Если вы включаете в main.py какие-то SQL скрипты, вынесенные в отдельные файлы – помещайте их сюда.
7. py_scripts - Каталог, необязательный. Если вы включаете в main.py какие-то python скрипты, вынесенные в отдельные файлы – помещайте их сюда.
# Разработка ETL процесса, для работы с отчетами.
Decision:
root@kvmubuntu:~# mkdir archive
root@kvmubuntu:~# touch main.py
root@kvmubuntu:~# chmod +x main.py
root@kvmubuntu:~# touch main.ddl
root@kvmubuntu:~# touch main.cron
root@kvmubuntu:~# cat snippet_pg.py
import psycopg2
import pandas as pd
# Создание подключения к PostgreSQL
conn = psycopg2.connect (
    database = "YOUR-DB",
    host =   "YOUR-IP",
    user =   "tuser",
    password = "YOUR-PASSWORD",
    port =   "5432"
  )
# Отключение автокоммита
conn.autocommit = False
# Создание курсора
cursor = conn.cursor()
####################################################
# Выполнение SQL кода в базе данных без возврата результата
cursor.execute( "INSERT INTO tuser.testtable( id, val ) VALUES ( 1, 'ABC' )" )
conn.commit()
# Выполнение SQL кода в базе данных с возвратом результата
cursor.execute( "SELECT * FROM tuser.testtable" )
records = cursor.fetchall()
for row in records:
  print( row )
####################################################
# Формирование DataFrame
names = [ x[0] for x in cursor.description ]
df = pd.DataFrame( records, columns = names )
# Запись в файл
df.to_excel( 'pandas_out.xlsx', sheet_name='sheet1', header=True, index=False )
####################################################
# Чтение из файла
df = pd.read_excel( 'pandas.xlsx', sheet_name='sheet1', header=0, index_col=None )
# Запись DataFrame в таблицу базы данных
cursor.executemany( "INSERT INTO tuser.testtable( id, val ) VALUES( %s, %s )", df.values.tolist() )
# Закрываем соединение
cursor.close()
conn.close()
root@kvmubuntu:~# /SCD1_incremental_full_script.sql 
----------------------------------------------------------------------------
-- Подготока данных

create table tuser.XXXX_source( 
    id integer,
    val varchar(50),
    update_dt timestamp(0)
);
insert into tuser.XXXX_source ( id, val, update_dt ) values ( 1, 'A', now() );
insert into tuser.XXXX_source ( id, val, update_dt ) values ( 2, 'B', now() );
insert into tuser.XXXX_source ( id, val, update_dt ) values ( 3, 'C', now() );
update tuser.XXXX_source set val = 'X', update_dt = now() where id = 3;
delete from tuser.XXXX_source where id = 3;
create table tuser.XXXX_stg( 
    id integer,
    val varchar(50),
    update_dt timestamp(0)
);
create table tuser.XXXX_target (
    id integer,
    val varchar(50),
    create_dt timestamp(0),
    update_dt timestamp(0)
);
create table tuser.XXXX_meta(
  schema_name varchar(30),
  table_name varchar(30),
  max_update_dt timestamp(0)
);
insert into tuser.XXXX_meta( schema_name, table_name, max_update_dt )
values( 'tuser','XXXX_SOURCE', to_timestamp('1900-01-01','YYYY-MM-DD') );
create table tuser.XXXX_stg_del( 
    id integer
);
----------------------------------------------------------------------------
-- Инкрементальная загрузка
-- 1. Очистка стейджинговых таблиц
delete from tuser.XXXX_stg;
delete from tuser.XXXX_stg_del;
-- 2. Захват данных из источника (измененных с момента последней загрузки) в стейджинг
insert into tuser.XXXX_stg( id, val, update_dt )
select id, val, update_dt from tuser.XXXX_source
where update_dt > ( select max_update_dt from tuser.XXXX_meta where schema_name='tuser' and table_name='XXXX_SOURCE' );
-- 3. Захват в стейджинг ключей из источника полным срезом для вычисления удалений.
insert into tuser.XXXX_stg_del( id )
select id from tuser.XXXX_source;
-- 4. Загрузка в приемник "вставок" на источнике (формат SCD1).
insert into tuser.XXXX_target( id, val, create_dt, update_dt )
select 
    stg.id, 
    stg.val, 
    stg.update_dt, 
    null 
from tuser.XXXX_stg stg
left join tuser.XXXX_target trg
on stg.id = trg.id
where trg.id is null;
-- 5. Обновление в приемнике "обновлений" на источнике (формат SCD1).
update tuser.XXXX_target
set 
    val = tmp.val,
    update_dt = tmp.update_dt
from (
    select 
        stg.id, 
        stg.val, 
        stg.update_dt, 
        null 
    from tuser.XXXX_stg stg
        inner join tuser.XXXX_target trg on stg.id = trg.id
    where stg.val <> trg.val 
        or ( stg.val is null and trg.val is not null ) or ( stg.val is not null and trg.val is null )
) tmp
where XXXX_target.id = tmp.id; 
-- 6. Удаление в приемнике удаленных в источнике записей (формат SCD1).
delete from tuser.XXXX_target
where id in (
    select trg.id
    from tuser.XXXX_target trg
    left join tuser.XXXX_stg_del stg
    on stg.id = trg.id
    where stg.id is null
);
-- 7. Обновление метаданных.
update tuser.XXXX_meta
set max_update_dt = coalesce( (select max( update_dt ) from tuser.XXXX_stg ), ( select max_update_dt from tuser.XXXX_meta where schema_name='tuser' and table_name='XXXX_SOURCE' ) )
where schema_name='tuser' and table_name = 'XXXX_SOURCE';
-- 8. Фиксация транзакции.
commit;
SCD1_incremental_full_script.sql
SCD1_incremental_full_script.sql. На экране
root@kvmubuntu:~# wget https://drive.google.com/file/d/13WSK0C1Z36hhsopebgEv2bGLAoxEsLUp/view?usp=drive_web&authuser=0
root@kvmubuntu:~# unzip data.zip
Task:
Создадите таблицы в базе.
# Написание Sql запросов.
Decision:
root@kvmubuntu:~# vim main.ddl
root@kvmubuntu:~# cat main.ddl
#!/usr/bin/python3
----------------------------------
create table tuser.XXXX_stg_transactions(
  trans_id varchar(15),
  trans_date timestamp(0),
  --amt decimal(10,2),
  amt numeric(10,2),  
  card_num varchar(20),
  oper_type varchar(10),
  oper_result varchar(10),
  terminal varchar(10),
  create_dt timestamp(0), 
  update_dt timestamp(0));
create table tuser.XXXX_dwh_fact_transactions(
  trans_id varchar(15),
  trans_date timestamp(0),
  --amt numeric(10,2),
  amt decimal(10,2),
  card_num varchar(20),
  oper_type varchar(10),
  oper_result varchar(10),
  terminal varchar(10),
  create_dt timestamp(0), 
  update_dt timestamp(0));
----------------------------------
create table tuser.XXXX_stg_terminals(
  terminal_id varchar(10),
  terminal_type varchar(10),
  terminal_city varchar(30),
  terminal_address varchar(70),
  create_dt timestamp(0), 
  update_dt timestamp(0));
create table tuser.XXXX_dwh_dim_terminals(
  terminal_id varchar(10),
  terminal_type varchar(10),
  terminal_city varchar(30),
  terminal_address varchar(70),
  create_dt timestamp(0),
  update_dt timestamp(0),
  effective_from timestamp(0),
  effective_to timestamp(0),
  deleted_flg char(1));
create table tuser.XXXX_stg_del_terminals(
  terminal_id varchar(10));
----------------------------------
create table tuser.XXXX_stg_blacklist(
  passport_num varchar(15),
  --entry_dt date
  entry_dt timestamp(0),
  create_dt timestamp(0), 
  update_dt timestamp(0));
create table tuser.XXXX_dwh_fact_passport_blacklist(
  passport_num varchar(30),
  --entry_dt date
  --date timestamp(0)
  entry_dt timestamp(0),
  create_dt timestamp(0), 
  update_dt timestamp(0));
----------------------------------
create table tuser.XXXX_stg_cards(
  card_num varchar(20),
  account_num varchar(20),
  create_dt timestamp(0),
  update_dt timestamp(0));
create table tuser.XXXX_dwh_dim_cards(
  card_num varchar(20),
  account_num varchar(20),
  --create_dt date,
  --update_dt date
  create_dt timestamp(0), 
  update_dt timestamp(0),
  effective_from timestamp(0),
  effective_to timestamp(0),
  deleted_flg char(1));
create table tuser.XXXX_stg_del_cards(
  card_num varchar(20));
----------------------------------
create table tuser.XXXX_stg_accounts(
  account_num varchar(20),
  --valid_to date,
  valid_to timestamp(0), 
  client varchar(10),
  create_dt timestamp(0),
  update_dt timestamp(0));
create table tuser.XXXX_dwh_dim_accounts(
  account_num varchar(20),
  --valid_to date,
  valid_to timestamp(0), 
  client varchar(10),
  --create_dt date,
  --update_dt date
  create_dt timestamp(0), 
  update_dt timestamp(0),
  effective_from timestamp(0),
  effective_to timestamp(0),
  deleted_flg char(1)); 
create table tuser.XXXX_stg_del_accounts(
  account_num varchar(20));
----------------------------------
create table tuser.XXXX_stg_clients(
  client_id varchar(10),
  last_name varchar(20),
  first_name varchar(20),
  patronymic varchar(20),
  --date_of_birth date,
  date_of_birth timestamp(0), 
  passport_num varchar(15),
  --passport_valid_to date,
  passport_valid_to timestamp(0),
  phone varchar(16),
  create_dt timestamp(0),
  update_dt timestamp(0));
create table tuser.XXXX_dwh_dim_clients(
  client_id varchar(10),
  last_name varchar(20),
  first_name varchar(20),
  patronymic varchar(20),
  --date_of_birth date,
  date_of_birth timestamp(0),
  passport_num varchar(15),
  --passport_valid_to date,
  passport_valid_to timestamp(0),
  phone varchar(16),
  --create_dt date,
  --update_dt date
  create_dt timestamp(0), 
  update_dt timestamp(0),
  effective_from timestamp(0),
  effective_to timestamp(0),
  deleted_flg char(1));
create table tuser.XXXX_stg_del_clients(
  client_id varchar(10));
----------------------------------
create table tuser.XXXX_rep_fraud(
  event_dt timestamp(0), 
  passport varchar(20), 
  fio varchar(50),
  phone varchar(16), 
  event_type varchar(120), 
  --report_dt date
  report_dt timestamp(0));
----------------------------------
create table tuser.XXXX_meta(
  schema_name varchar(30),
  table_name varchar(30),
  max_update_dt timestamp(0));
----------------------------------
Task:
Алгоритм для файла main.py: Подключитесь к двум базам, Очистите весь стейджинг. Загрузите файлы transactions_01032021.txt, terminals_01032021.xlsx, passport_blacklist_01032021.xlsx в стейджинг. Загрузите таблицы clients, accounts, cards в стейджинг. Используйте следующий подход: Выполните запрос к базе 1, Сохраните полученный результат в DataFrame, Загрузите DataFrame в базу 2. Загрузите данные из стейджинга в целевую таблицу xxxx_dwh_dim_terminals, xxxx_dwh_dim_cards, xxxx_dwh_dim_accounts, xxxx_dwh_dim_clients. Загрузите данные из стейджинга в целевую таблицу xxxx_dwh_fact_passport_blacklist, xxxx_dwh_fact_transactions.. Фактовые таблицы данные перекладываются «простым инсертом», то есть необходимо выполнить один INSERT INTO ... SELECT .... Напишите скрипт, соединяющий нужные таблицы для поиска операций, совершенных при недействующем договоре. Отладьте ваш скрипт для одной даты PgAdmin, он должен выдавать результат. Результат выполнения скрипта загружайте в таблицу xxxx_rep_fraud. Незабывайте сформировать поле report_dt. Зафиксируйте изменения. Отключитесь от баз. Переименуйте обработанные файлы и перенесите их в другой каталог. отладить его работоспособность навсех трех днях загрузки.
# Разработка ETL процесса, для работы с отчетами
Decision:
root@kvmubuntu:~# vim main.py
root@kvmubuntu:~# cat main.py
#!/usr/bin/python3
import pandas as pd
import psycopg2
import os
###################Подключение к базам
#conn_YOUR-DB1 = psycopg2.connect(database = "YOUR-DB",host = "YOUR-IP",user = "tuser",password = "YOUR-PASSWORD",port = "5432")
conn_YOUR-DB1 = psycopg2.connect(database = "YOUR-DB1",host = "YOUR-HOST",user = "tuser1",password = "YOUR-PASSWORD1",port = "5432")
conn_YOUR-DB2 = psycopg2.connect(database = "bank",host = "YOUR-HOST",user = "tuser2",password = "YOUR-PASSWORD2",port = "5432")
conn_YOUR-DB1.autocommit = False
conn_YOUR-DB2.autocommit = False
cursor_YOUR-DB1 = conn_YOUR-DB1.cursor()
cursor_YOUR-DB2 = conn_YOUR-DB2.cursor()
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM tuser1.XXXX_stg_transactions;""")
###################Загрузка данных в стейджинг
#df = pd.read_csv(f'/home/tuser1/XXXX/project/data/transactions_01032021.txt', sep=';', decimal=',', header=0, index_col=None)
dirpath = "/home/tuser1/XXXX/project"
project_files = os.listdir(dirpath)
for transactions in project_files:
    if transactions.endswith('.txt'):
        df = pd.read_csv(transactions, sep=";")
        datenow_w_txt = transactions.rsplit('_')[1]
        datenow_str = datenow_w_txt.split('.')[0]
df['amount'] = df['amount'].map(lambda z: z.strip().replace(',', '.')).astype('float')
df['create_dt'] = datenow_str
df['update_dt'] = datenow_str
###################
cursor_YOUR-DB1.executemany("""
        INSERT INTO tuser1.XXXX_stg_transactions(trans_id, trans_date, amt, card_num, oper_type, oper_result, terminal , create_dt, update_dt) 
        VALUES(%s, %s , %s , %s , %s , %s , %s, to_timestamp(%s,'DDMMYYYY'), to_timestamp(%s,'DDMMYYYY'))
    """, df.values.tolist())
###################Загрузка данных в целевые таблицы фактов
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_dwh_fact_transactions (trans_id,trans_date,card_num,oper_type,amt,oper_result,terminal,create_dt,update_dt)
        SELECT stg.trans_id,stg.trans_date,stg.card_num,stg.oper_type,stg.amt,stg.oper_result,stg.terminal,stg.create_dt,stg.update_dt 
        FROM tuser1.XXXX_stg_transactions as stg;
    """)
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM tuser1.XXXX_stg_terminals;""")
###################Загрузка данных в стейджинг
#df = pd.read_excel(f'/home/tuser1/XXXX/project/data/terminals_01032021.xlsx', sheet_name='terminals', header=0, index_col=None)
for terminals in project_files:
    if terminals.startswith('terminals'):
        df = pd.read_excel(terminals, sheet_name='terminals', header=0, index_col=None )
        datenow_w_txt = terminals.rsplit('_')[1]
        datenow_str = datenow_w_txt.split('.')[0]
df['create_dt'] = datenow_str
df['update_dt'] = datenow_str
#df.insert(4, 'update_dt','2021-03-01')
###################
cursor_YOUR-DB1.executemany("""
        INSERT INTO tuser1.XXXX_stg_terminals(terminal_id,terminal_type,terminal_city,terminal_address,create_dt,update_dt)
        VALUES(%s,%s,%s,%s,to_timestamp(%s,'DDMMYYYY'),to_timestamp(%s,'DDMMYYYY'));
    """, df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_dwh_dim_terminals (terminal_id,terminal_type,terminal_city,terminal_address,create_dt,update_dt)
        SELECT stg.terminal_id, stg.terminal_type, stg.terminal_city, stg.terminal_address, stg.update_dt,to_timestamp('9999-12-31', 'YYYY-MM-DD') 
        from tuser1.XXXX_stg_terminals as stg 
        left join tuser1.XXXX_dwh_dim_terminals as trg
        on stg.terminal_id = trg.terminal_id 
        where trg.terminal_id is null;
    """)
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM tuser1.XXXX_stg_blacklist;""")
###################Загрузка данных в стейджинг
#df = pd.read_excel(f'/home/tuser1/XXXX/project/data/passport_blacklist_01032021.xlsx', sheet_name='blacklist', header=0, index_col=None)
for blacklist in project_files:
    if blacklist.startswith('passport_blacklist'):
        df = pd.read_excel(blacklist, sheet_name='blacklist', header=0, index_col=None )
        datenow_w_ext = blacklist.rsplit('blacklist_')[1]
        datenow_str = datenow_w_ext.split('.')[0]
df['create_dt'] = datenow_str
df['update_dt'] = datenow_str
###################
cursor_YOUR-DB1.executemany("""
        INSERT INTO tuser1.XXXX_stg_blacklist(entry_dt,passport_num,create_dt,update_dt)
        VALUES(%s, %s,to_timestamp(%s,'DDMMYYYY'),to_timestamp(%s,'DDMMYYYY'));
    """, df.values.tolist())
###################Загрузка данных в целевые таблицы фактов
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_dwh_fact_passport_blacklist 
        SELECT stg.passport_num,stg.entry_dt,stg.create_dt,stg.update_dt
        from tuser1.XXXX_stg_blacklist as stg 
        left join tuser1.XXXX_dwh_fact_passport_blacklist as trg 
        on stg.passport_num = trg.passport_num 
        where trg.passport_num is null;
    """)
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM tuser1.XXXX_stg_cards;""")
###################Загрузка данных в стейджинг
cursor_YOUR-DB2.execute("""
        --SELECT regexp_replace(card_num, '\s+$', '') as card_num,account,create_dt,update_dt 
        SELECT card_num,account,create_dt,update_dt 
        FROM info.cards
    """)
records = cursor_YOUR-DB2.fetchall()
#for row in records:
#  print(row)
names = [ x[0] for x in cursor_YOUR-DB2.description ]
df = pd.DataFrame( records, columns = names )
cursor_YOUR-DB1.executemany("""
        INSERT INTO tuser1.XXXX_stg_cards(card_num,account_num,create_dt,update_dt) 
        VALUES(%s,%s,%s,%s)
    """, df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_dwh_dim_cards (card_num,account_num,create_dt,update_dt)
        SELECT stg.card_num, stg.account_num, stg.create_dt, to_timestamp('9999-12-31', 'YYYY-MM-DD')
        from tuser1.XXXX_stg_cards as stg 
        left join tuser1.XXXX_dwh_dim_cards as trg 
        on stg.card_num = trg.card_num 
        where trg.card_num is null;
    """)
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM tuser1.XXXX_stg_accounts;""")
###################Загрузка данных в стейджинг
cursor_YOUR-DB2.execute("""
        SELECT account,valid_to,client,create_dt,update_dt 
        FROM info.accounts
    """)
records = cursor_YOUR-DB2.fetchall()
#for row in records:
#    print(row)
names = [ x[0] for x in cursor_YOUR-DB2.description ]
df = pd.DataFrame( records, columns = names )
cursor_YOUR-DB1.executemany("""
        INSERT INTO tuser1.XXXX_stg_accounts(account_num,valid_to,client,create_dt,update_dt)
        VALUES( %s, %s, %s, %s, %s)
    """, df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_dwh_dim_accounts(account_num,valid_to,client,create_dt,update_dt) 
        SELECT stg.account_num,stg.valid_to,stg.client,stg.create_dt,to_timestamp('9999-12-31', 'YYYY-MM-DD')
            from tuser1.XXXX_stg_accounts as stg 
        left join tuser1.XXXX_dwh_dim_accounts as trg
        on stg.account_num = trg.account_num 
        where trg.account_num is null;
    """)
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM tuser1.XXXX_stg_clients;""")
###################Загрузка данных в стейджинг
cursor_YOUR-DB2.execute("""
        SELECT client_id,last_name,first_name,patronymic,date_of_birth,passport_num,passport_valid_to,phone,create_dt,update_dt
        FROM info.clients; 
    """)
records = cursor_YOUR-DB2.fetchall()
#for row in records:
#    print(row)
names = [ x[0] for x in cursor_YOUR-DB2.description ]
df = pd.DataFrame(records, columns = names) 
cursor_YOUR-DB1.executemany(""" 
        INSERT INTO tuser1.XXXX_stg_clients(client_id,last_name,first_name,patronymic,date_of_birth,passport_num,passport_valid_to,phone,create_dt,update_dt)
        VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);
    """, df.values.tolist()) 
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_dwh_dim_clients(client_id,last_name,first_name,patronymic,date_of_birth,passport_num,passport_valid_to,phone,create_dt,update_dt)
        SELECT stg.client_id,stg.last_name,stg.first_name,stg.patronymic,stg.date_of_birth,stg.passport_num,stg.passport_valid_to,stg.phone,stg.create_dt,now()
        from tuser1.XXXX_stg_clients as stg
        left join tuser1.XXXX_dwh_dim_clients as trg
        on stg.client_id = trg.client_id 
        where trg.client_id is null;
    """)
###################
######################################
###################Выявление мошеннических операций и построение отчёта
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_rep_fraud 
        select 
            min(t2.trans_date) as trans_date, 
            tgddcl.passport_num as passport_num, 
            (tgddcl.last_name || ' ' || tgddcl.first_name || ' ' || tgddcl.patronymic ) as fio, 
            tgddcl.phone as phone, 
            '1' as event_type,
            now() as report_dt
        from (
            select *
            from (
                with current_dt as ( 
                    select trans_date 
                    from tuser1.XXXX_stg_transactions) 
                select tgdft.*, tgddca.account_num 
                from tuser1.XXXX_dwh_fact_transactions as tgdft 
                left join tuser1.XXXX_dwh_dim_cards as tgddca
                on trim(tgdft.card_num) = trim(tgddca.card_num ) 
                where tgdft.oper_result = 'SUCCESS' 
                    and tgdft.trans_date in (
                        select trans_date 
                        from current_dt)) as t 
            left join tuser1.XXXX_dwh_dim_accounts as gdda 
            on t.account_num = gdda.account_num ) as t2 
        left join tuser1.XXXX_dwh_dim_clients as tgddcl 
        on t2.client = tgddcl.client_id 
        where (tgddcl.passport_valid_to < t2.trans_date 
            or tgddcl.passport_num in (
                select passport_num
                from tuser1.XXXX_dwh_fact_passport_blacklist)) 
        group by tgddcl.passport_num, (tgddcl.last_name || ' ' || tgddcl.first_name || ' ' || tgddcl.patronymic ), tgddcl.phone;
    """)
###################Совершение операции при недействующем договоре.
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_rep_fraud
        select 
            t2.trans_date as trans_date, 
            gddcl.passport_num as passport_num, 
            (gddcl.last_name || ' ' || gddcl.first_name || ' ' || gddcl.patronymic ) as fio, 
            gddcl.phone as phone, 
            '2' as event_type,
            now() as report_dt
        from (
            select min(t.trans_date) trans_date, t.account_num, gdda.client
            from (
                with current_dt as ( 
                    select trans_date 
                    from tuser1.XXXX_stg_transactions) 
                select gdft.trans_date, gdft.card_num, gddca.account_num
                from tuser1.XXXX_dwh_fact_transactions as gdft 
                left join tuser1.XXXX_dwh_dim_cards as gddca
                on trim(gdft.card_num) = trim(gddca.card_num ) 
                where gdft.oper_result = 'SUCCESS'
                    and gdft.trans_date in (
                        select trans_date 
                        from current_dt)) as t 
            left join tuser1.XXXX_dwh_dim_accounts as gdda 
            on t.account_num = gdda.account_num 
            where t.trans_date > gdda.valid_to
            group by t.account_num, gdda.client ) as t2 
        left join tuser1.XXXX_dwh_dim_clients as gddcl 
        on t2.client = gddcl.client_id;
    """)
###################Совершение операций в разных городах в течение одного часа
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_rep_fraud
        select
            t3.trans_date,
            t3.passport_num,
            (t3.last_name || ' ' || t3.first_name || ' ' || t3.patronymic ) as fio,
            t3.phone ,
            '3' as event_type,
            now() as report_dt
        from (
            select trans_date, trg2.*
            from (
                select * from (
                    select * from (
                        with lds as (
                            select
                                trans_date,
                                terminal_city,
                                card_num,
                                lag(terminal_city) over (
                                    partition by gdftr.card_num
                                    order by gdftr.trans_date) as lag_c,
                                lag(trans_date) over (
                                    partition by card_num
                                    order by gdftr.trans_date) as lag_t,
                                lead(terminal_city) over (
                                    partition by gdftr.card_num
                                    order by gdftr.trans_date) as lead_c ,
                                lead(trans_date) over (
                                    partition by card_num
                                    order by gdftr.trans_date) as lead_t
                            from tuser1.XXXX_dwh_fact_transactions as gdftr
                            left join tuser1.XXXX_dwh_dim_terminals as gddt
                            on gdftr.terminal = gddt.terminal_id
                            where gdftr.oper_result = 'SUCCESS'),
                        current_dt as (
                            select trans_date
                            from tuser1.XXXX_stg_transactions )
                        select
                            min(trans_date) as trans_date,
                            card_num
                        from lds
                        where lag_c <> terminal_city
                            and (trans_date-lag_t) < '01:00:00'
                            and trans_date in (
                                select trans_date
                                from current_dt)
                        group by card_num) as trg
                    left join tuser1.XXXX_dwh_dim_cards as trg3
                    on trim(trg.card_num) = trim(trg3.card_num )) as t
                left join tuser1.XXXX_dwh_dim_accounts as trg4
                on t.account_num = trg4.account_num ) as t2
            left join tuser1.XXXX_dwh_dim_clients as trg2
            on t2.client = trg2.client_id ) as t3;
    """)
###################Попытка подбора суммы
cursor_YOUR-DB1.execute("""
        INSERT INTO tuser1.XXXX_rep_fraud 
        select 
            t3.trans_date, 
            t3.passport_num , 
            (t3.last_name || ' ' || t3.first_name || ' ' || t3.patronymic ) as fio, 
            t3.phone , 
            '4' as event_type,
            now() as report_dt
        from (
            select 
                trans_date, 
                gddcl.*
            from (
                select * from (
                    select * from(
                        with rj as (
                            select 
                                *,
                                lag(amt) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as lag_a,
                                lag(amt,2) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as lag_a2,
                                lag(amt,3) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as lag_a3,
                                lag(oper_result) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as lag_r,
                                lag(oper_result,2) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as lag_r2,
                                lag(oper_result,3) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as lag_r3,
                                lag(trans_date,3) over (
                                    partition by gdft.card_num 
                                    order by gdft.trans_date) as min_t
                            from tuser1.XXXX_dwh_fact_transactions gdft),
                        current_dt as ( 
                            select trans_date 
                            from tuser1.XXXX_stg_transactions )
                        select * 
                        from rj
                        where oper_result = 'SUCCESS' 
                            and lag_r = 'REJECT' 
                            and lag_a > amt 
                            and lag_r2 = 'REJECT' 
                            and lag_a2 > lag_a
                            and lag_r3 = 'REJECT' 
                            and lag_a3 > lag_a2
                            and (trans_date - min_t) <= '00:20:00'
                            and trans_date in (
                                select trans_date 
                                from current_dt)) as trg
                    left join tuser1.XXXX_dwh_dim_cards as gddca
                    on trim(trg.card_num) = trim(gddca.card_num )) as t 
                left join tuser1.XXXX_dwh_dim_accounts as gdda 
                on t.account_num = gdda.account_num ) as t2 
            left join tuser1.XXXX_dwh_dim_clients as gddcl 
            on t2.client = gddcl.client_id ) as t3;
    """)
######################################
conn_YOUR-DB2.commit()
conn_YOUR-DB1.commit()
######################################Запись файлов в архив
os.rename('/home/tuser1/XXXX/project/terminals_01032021.xlsx', '/home/tuser1/XXXX/project/archive/terminals_01032021.xlsx.backup')
os.rename('/home/tuser1/XXXX/project/transactions_01032021.txt', '/home/tuser1/XXXX/project/archive/transactions_01032021.txt.backup')
os.rename('/home/tuser1/XXXX/project/passport_blacklist_01032021.xlsx', '/home/tuser1/XXXX/project/archive/passport_blacklist_01032021.xlsx.backup')
os.rename('/home/tuser1/XXXX/project/terminals_02032021.xlsx', '/home/tuser1/XXXX/project/archive/terminals_02032021.xlsx.backup')
os.rename('/home/tuser1/XXXX/project/transactions_02032021.txt', '/home/tuser1/XXXX/project/archive/transactions_02032021.txt.backup')
os.rename('/home/tuser1/XXXX/project/passport_blacklist_02032021.xlsx', '/home/tuser1/XXXX/project/archive/passport_blacklist_02032021.xlsx.backup')
os.rename('/home/tuser1/XXXX/project/terminals_03032021.xlsx', '/home/tuser1/XXXX/project/archive/terminals_03032021.xlsx.backup')
os.rename('/home/tuser1/XXXX/project/transactions_03032021.txt', '/home/tuser1/XXXX/project/archive/transactions_03032021.txt.backup')
os.rename('/home/tuser1/XXXX/project/passport_blacklist_03032021.xlsx', '/home/tuser1/XXXX/project/archive/passport_blacklist_03032021.xlsx.backup')
######################################
cursor_YOUR-DB2.close();
cursor_YOUR-DB1.close();
root@kvmubuntu:~# apt install python3.10-venv
root@kvmubuntu:~# python3.10 -m venv venv
root@kvmubuntu:~# source venv/bin/activate
root@kvmubuntu:~# pip install pandas
root@kvmubuntu:~# python3 main.py
root@kvmubuntu:~# ls archive/
passport_blacklist_01032021.xlsx.backup terminals_01032021.xlsx.backup transactions_01032021.txt.backup
root@kvmubuntu:~# psql -U tuser
tuser=# select * from tuser.XXXX_rep_fraud;
Task:
Заполните файл main.cron расписанием и командой исполнения вашего скрипта. Расписание установите так, как считаете нужным чтобы ваши данные заполнились корректно.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@kvmubuntu:~# vim main.cron 
root@kvmubuntu:~# cat main.cron 
0 0 * * * /home/david/project/main.py
Decision:
По следующим признакам получилось выявить мошеннические транзакции: выполнение операции с просроченным или заблокированным паспортом, выполнение операции с недействительным контрактом, выполнение операций в разных городах в течение одного часа
Source:
# https://dzen.ru/a/Ypr65Wh4jmLimA3o
# https://www.youtube.com/watch?v=Je3Y8up0Qbs&list=LL&index=5&t=98s
# https://losst.pro/kak-posmotret-otkrytye-porty-v-linux?ysclid=lpe5qjsv9o445640330
# https://www.pgadmin.org/download/pgadmin-4-apt/
# https://losst.pro/spisok-kontejnerov-docker

2019-11-01 - 2021-05-30: Easy School, Иркутск. Должность: English Level Elementary A - Дополнительное образование. Дополнительная информация: Навыки - English, Python, Clouds. Достижения: Разработал программу телеграмм бот Переводчик.

Show

Цель:
# Написать функцию Меню без бота.
# Раработка программы Переводчик без бота.
# Использовать программу Переводчик с готовой функцией Меню.
# Разработать базу данных с таблицами Словарь.
# Разработать Телеграм Бот с программой Переводчик, используя Aiogram.
# Добавить в Телеграм Бот Psycopg2, для написания Sql запросов.
# Разработать Телеграм Бот с программой Переводчик, используя Telebot вместо Aiogram.
# Добавить информацию в базу данных в телеграм бот.
# Перенести все логины и пароли в отдельный файл.
# Применить в Телеграм Бот программ Переводчик.
# Настроить службу для ручного запуска Бота.
Skills:
# Разработка Телеграм ботов.
# Написание Sql запросов.
# Администрирование локальных, виртуальных и облачных серверов.
Task:
Разработка функции меню.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyMenu.py
root@kvmubuntu:~# cat pyMenu.py
def fun(t1, b, t2, l, t3):
while True: 
cmd1 = input("Привет!\nЯ тестовый бот.\nВыбери программу, которую ты хочешь выполнить\n(0, translator, dictionary): ")
if cmd1 == "0":
break
elif cmd1 == t1:
while True:
  cmd2 = input("Что именно нужно сделать\n(translate, back): ")
  if cmd2 == b:
   print('Вы вернулись в главное меню')
   break
  elif cmd2 == t2:
   print("Здесь программа переведет вам текст")
  else:
   print("Я такую команду не знаю")
elif cmd1 == "dictionary":
while True:
  cmd2 = input("Что именно нужно сделать\n(list,term,back): ")
  if cmd2 == b:
   print('Вы вернулись в главное меню')
   break
  elif cmd2 == l:
   print("Вывести список слов")
  elif cmd2 == t3:
   print("Выбрать слово их списка")
  else:
   print("Я такую команду не знаю")
else:
print("Я такую команду не знаю")
return t1
print(fun("translator","back","translate","list","term"))
root@kvmubuntu:~# python3 pyMenu.py
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): translator
Что именно нужно сделать
(translate, back): translate 
Здесь программа переведет вам текст
Что именно нужно сделать
(translate, back): back
Вы вернулись в главное меню
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): dictionary
Что именно нужно сделать
(list,term,back): list
Вывести список слов
Что именно нужно сделать
(list,term,back): term
Выбрать слово их списка
Что именно нужно сделать
(list,term,back): back
Вы вернулись в главное меню
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): 0
translator
Task:
Подготовка библиотек.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# python3 -m venv tenv
root@kvmubuntu:~# source tenv/bin/activate
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
translate==3.6.1
pyTelegramBotAPI==4.21.0
googletrans==3.1.0a0
langdetect==1.0.9
aiogram==2.23.1
psycopg2-binary==2.9.9
root@kvmubuntu:~# pip install -r requirements.txt
Task:
Раработка программы переводчик.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslator.py
root@kvmubuntu:~# cat pyTranslator.py
from translate import Translator
translator=Translator(from_lang="russian", to_lang="english")
translation=translator.translate("Вчера я забронировал у вас номер в отеле.")
print(translation)
root@kvmubuntu:~# python3 translator.py
Hi, how are you?
root@kvmubuntu:~# vim pyTranslator1.py
root@kvmubuntu:~# cat pyTranslator1.py
from translate import Translator
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
cmd3 = input("Введите текст, который вы хотите перевести: ")
if cmd3[0].lower() in ru_letters:
translator = Translator(from_lang="russian", to_lang="english")
elif cmd3[0].lower() in en_letters:
translator = Translator(from_lang="english", to_lang="russian")
else:
print('Я тебя не понимаю')
translation = translator.translate(cmd3)
print(translation)
root@kvmubuntu:~# python3 pyTranslator1.py
Введите текст, который вы хотите перевести: Вчера я забронировал у вас номер в отеле.
Tom booked a room at the hotel
root@kvmubuntu:~# vim pyTranslator2.py
root@kvmubuntu:~# cat pyTranslator2.py
from googletrans import Translator
from langdetect import detect
translator = Translator()
cmd3 = input("Введите текст, который вы хотите перевести: ")
translation = translator.translate(cmd3, src=detect(cmd3), dest='en').text
print(translation.encode('utf-8', 'replace').decode())
root@kvmubuntu:~# python3 pyTranslator2.py
Введите текст, который вы хотите перевести: Вчера я забронировал у вас номер в отеле.
Yesterday I booked a hotel room with you.
root@kvmubuntu:~# vim pyTranslator3.py
root@kvmubuntu:~# cat pyTranslator3.py
from googletrans import Translator
from langdetect import detect
translator = Translator()
cmd3 = input("Введите текст, который вы хотите перевести: ")
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
if cmd3[0].lower() in ru_letters:
translation = translator.translate(cmd3, src=detect(cmd3), dest='en').text
print(translation.encode('utf-8', 'replace').decode())
elif cmd3[0].lower() in en_letters:
translation = translator.translate(cmd3, src=detect(cmd3), dest='ru').text
print(translation.encode('utf-8', 'replace').decode())
else:
print('Я тебя не понимаю')
root@kvmubuntu:~# python3 pyTranslator3.py
Введите текст, который вы хотите перевести: Для тестирования работоспособности телеграм бота переводчик, написанном на Python мы использовали готовый преднастроенный телеграм бот сервер.
To test the functionality of the telegram bot translator, written in Python, we used a ready-made pre-configured telegram bot server.
root@kvmubuntu:~# python3 pyTranslator3.py
Введите текст, который вы хотите перевести: To test the functionality of the telegram bot translator, written in Python, we used a ready-made pre-configured telegram bot server.
Для проверки работоспособности переводчика телеграмм-бота, написанного на Python, мы использовали готовый, предварительно настроенный сервер телеграм-бота.
root@kvmubuntu:~# python3 pyTranslator3.py
Введите текст, который вы хотите перевести: ;
Я тебя не понимаю
Task:
Применение библиотеки Translator и готовой функции меню.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyMenuTranslator.py
root@kvmubuntu:~# cat pyMenuTranslator.py
from translate import Translator
def fun(t1, b, t2, l, t3):
while True: 
cmd1 = input("Привет!\nЯ тестовый бот.\nВыбери программу, которую ты хочешь выполнить\n(0, translator, dictionary): ")
if cmd1 == "0":
break
elif cmd1 == t1:
while True:
  cmd2 = input("Что именно нужно сделать\n(translate, back): ")
  if cmd2 == b:
   print('Вы вернулись в главное меню')
   break
  elif cmd2 == t2:
   cmd3 = input("Введите текст, который вы хотите перевести: ")
   t4=Translator(from_lang="russian", to_lang="english")
   translation=t4.translate(cmd3)
   print(translation)
  else:
   print("Я такую команду не знаю")
elif cmd1 == "dictionary":
while True:
  cmd2 = input("Что именно нужно сделать\n(list,term,back): ")
  if cmd2 == b:
   print('Вы вернулись в главное меню')
   break
  elif cmd2 == l:
   print("Вывести список слов")
  elif cmd2 == t3:
   print("Выбрать слово их списка")
  else:
   print("Я такую команду не знаю")
else:
print("Я такую команду не знаю")
return t1
print(fun("translator","back","translate","list","term"))
root@kvmubuntu:~# python3 pyMenuTranslator.py
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): translator
Что именно нужно сделать
(translate, back): translate 
Введите текст, который вы хотите перевести: Привет. Как дела?
Hi, how are you?
Что именно нужно сделать
(translate, back): back 
Вы вернулись в главное меню
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): 0
translator
Task:
Разработать схему БД Словарь.
# Написание Sql запросов.
Decision:

Task:
Разработка базы данных.
Словарь
id | words | translate 
1 | Текст1 | Text1 
2 | Текст2 | Text2 
3 | Текст3 | Text3 
Термины
id | words_id | description | translate
1 | 1 | Текст4 | Text4 
2 | 3 | Текст5 | Text5
# Написание Sql запросов.
Decision:
root@kvmubuntu:~# psql -U tuser -d tbase2 -h tipubuntu
tbase2=# CREATE TABLE tbase2 (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
words VARCHAR(1000),
translate VARCHAR(1000)
);
tbase2=# CREATE TABLE Terms (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
words_id INT,
description VARCHAR(1000),
translate VARCHAR(1000),
CONSTRAINT fk_tbase2
FOREIGN KEY(words_id)
REFERENCES tbase2(id)
);
tbase2=# INSERT INTO tbase2 (words, translate)
VALUES ('Текст1', 'Text1'), ('Текст2', 'Text2'), ('Текст3', 'Text3');
tbase2=# INSERT INTO Terms (words_id, description, translate)
VALUES (1, 'Текст4', 'Text4'), (3, 'Текст5','Text5');
Task:
Вывести таблицу такой формы:
words | translate | description | translate
Текст1 | Text1 | Текст4 | Text4
Текст3 | Text3 | Текст5 | Text5
# Написание Sql запросов.
Decision:
tbase2=# select words, dictionary.translate, description, Terms.translate 
from dictionary
inner join Terms 
on dictionary.id = Terms.words_id;
words | translate | description | translate 
--------+-----------+-------------+-----------
Текст1 | Text1 | Текст4 | Text4
Текст3 | Text3 | Текст5 | Text5
(2 rows)
Source:
# https://proglib.io/p/rukovodstvo-po-sql-dlya-nachinayushchih-chast-1-sozdanie-bazy-dannyh-tablic-i-ustanovka-svyazey-mezhdu-tablicami-2022-02-07 - Руководство по SQL для начинающих. Часть 1: создание базы данных, таблиц и установка связей между таблицами.
# https://sql-academy.org/ru/guide/inner-join - Внутреннее соединение INNER JOIN.
Task:
Применение библиотеки Aiogram.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslatorAiogram.py
root@kvmubuntu:~# cat pyTranslatorAiogram.py
import logging
from translate import Translator
from aiogram import Bot, Dispatcher, executor, types
API_TOKEN = 'ttoken'
# Configure logging
logging.basicConfig(level=logging.INFO)
# Initialize bot and dispatcher
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
@dp.message_handler(commands=['start', 'help'])
async def send_welcome(message: types.Message):
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
@dp.message_handler()
async def echo(message: types.Message):
text = message.text
if text[0].lower() in ru_letters:
translator = Translator(from_lang="russian", to_lang="english")
elif text[0].lower() in en_letters:
translator = Translator(from_lang="english", to_lang="russian")
else:
await message.answer('Я тебя не понимаю')
return
translation = translator.translate(text)
await message.answer(translation)
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
root@kvmubuntu:~# python3 pyTranslatorAiogram.py
Source:
# https://www.youtube.com/@shcoder001 - Переводчик бот в telegram на python за 5 минут aiogram.
Task:
Применение библиотеки psycopg2.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyPsql.py
root@kvmubuntu:~# cat pyPsql.py
import psycopg2
try:
# пытаемся подключиться к базе данных
conn = psycopg2.connect(dbname='tbase2', user='tuser', password='tpassword', host='tipubuntu')
except:
# в случае сбоя подключения будет выведено сообщение в STDOUT
print('Can`t establish connection to database')
root@kvmubuntu:~# python3 pyPsql.py
root@kvmubuntu:~# vim pyPsql.py
root@kvmubuntu:~# cat pyPsql.py
import psycopg2
try:
# пытаемся подключиться к базе данных
conn = psycopg2.connect(dbname='tbase2', user='tuser', password='tpassword', host='tipubuntu')
cursor = conn.cursor()
cursor.execute('select words, dictionary.translate, description, Terms.translate from dictionary inner join Terms on dictionary.id = Terms.words_id')
#records = cursor.fetchall()
#print(records)
for row in cursor:
print(row)
cursor.close()
conn.close()
except:
# в случае сбоя подключения будет выведено сообщение в STDOUT
print('Can`t establish connection to database')
root@kvmubuntu:~# python3 pyPsql.py
('Внутреннее соединение', 'Inner join', 'Возвращаются только те строки, где ключевые значения совпадают в обеих таблицах', 'Only those rows are returned where the key values match in both tables')
('Полное соединение', 'Cross Join', 'Позволяет получить декартово произведение нескольких таблиц. особенно полезен, когда между таблицами нет определенной связи, и вам нужно создать полную комбинацию записей из каждой таблицы', '-')
('Декартово произведение', 'Cartesian product', 'Результат соединения строки из первой таблицы с каждой строкой из второй таблицы', '-')
root@kvmubuntu:~# vim pyPsql.py
root@kvmubuntu:~# cat pyPsql.py
import psycopg2
from contextlib import closing
with closing(psycopg2.connect(dbname='dictionary', user='tuser', password='tpassword', host='tipubuntu')) as conn:
with conn.cursor() as cursor:
cursor.execute('select words, dictionary.translate, description, Terms.translate from dictionary inner join Terms on dictionary.id = Terms.words_id')
for row in cursor:
print(row)
root@kvmubuntu:~# python3 pyPsql.py
('Внутреннее соединение', 'Inner join', 'Возвращаются только те строки, где ключевые значения совпадают в обеих таблицах', 'Only those rows are returned where the key values match in both tables')
('Полное соединение', 'Cross Join', 'Позволяет получить декартово произведение нескольких таблиц. особенно полезен, когда между таблицами нет определенной связи, и вам нужно создать полную комбинацию записей из каждой таблицы', '-')
('Декартово произведение', 'Cartesian product', 'Результат соединения строки из первой таблицы с каждой строкой из второй таблицы', '-')
Source:
# https://ru.hexlet.io/blog/posts/python-postgresql - Использование Psycopg2.
# https://khashtamov.com/ru/postgresql-python-psycopg2/ - Начало работы.
Task:
Применение библиотеки Telebot.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslatorMenuTelebot.py
root@kvmubuntu:~# cat pyTranslatorMenuTelebot.py
import telebot
from telebot import types
from googletrans import Translator
from langdetect import detect
TOKEN='tkey'
bot = telebot.TeleBot(TOKEN)
translator = Translator()
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
@bot.message_handler(commands=["start"])
def start(message):
message_user = f"Привет, <b>{message.from_user.first_name.title()}</b>! Я тестовый бот.\n" \
     f"<b>Выбери программу, которую ты хочешь выполнить:</b>\n" \
     f"1. Чем полезен данный бот\n" \
     f"2. Функции бота (что может данный бот)\n" \
     f"3. Для тех кто хочет поддержать нас и наш проект"
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
item1 = types.KeyboardButton(text="Чем полезен бот ?")
item2 = types.KeyboardButton(text="Функции бота")
item3 = types.KeyboardButton(text="Поддержать проект")
markup.add(item1, item2, item3)
bot.send_message(message.from_user.id, message_user, reply_markup=markup, parse_mode='html')
bot.register_next_step_handler(message, impact_KEYBORD_bot)
bot.register_next_step_handler(message, fuctional_KEYBORD_bot)
bot.register_next_step_handler(message, donat_user_bot)
def impact_KEYBORD_bot(message):
if message.text == "Чем полезен бот ?":
  message_user = "Этот бот много чем будет полезен для вас. Ознакомьтесь с функционалом бота чтобы понять это." \
     " В этого бота со временем мы будем внедрять новые фичи и полезные функции. Чтобы узнавать о новых фишках бота, слидите за нашим Telegramm каналом"
  key = types.InlineKeyboardMarkup()
  button = types.InlineKeyboardButton(text='Мое портфолио', url="http://dato138it.ru")
  key.add(button)
  bot.send_message(message.from_user.id, message_user, reply_markup=key, parse_mode='html')
  bot.register_next_step_handler(message, fuctional_KEYBORD_bot)
  bot.register_next_step_handler(message, donat_user_bot)
def fuctional_KEYBORD_bot(message):
if message.text == 'Функции бота':
  message_user = "<b>Добро пожаловать главное меню бота</b>\n\n" \
     "В скором будущем мы будем добавлять сюда новые функции!"
  key = types.ReplyKeyboardMarkup(resize_keyboard=True)
  button0 = types.KeyboardButton("Переводчик")
  button1 = types.KeyboardButton("Словарь")
  key.add(button0, button1)
  bot.send_message(message.from_user.id, message_user, reply_markup=key, parse_mode='html')
  bot.register_next_step_handler(message, impact_KEYBORD_bot)
  bot.register_next_step_handler(message, donat_user_bot)
  bot.register_next_step_handler(message, translate_message)
  bot.register_next_step_handler(message, dictionary_message)
def donat_user_bot(message):
if message.text == "Поддержать проект":
  message_users = f"<b>Приветствую уважаемый {message.from_user.first_name.title()}</b>, вы перешли в отдел поддержки нашего проекта \n\n" \
      f"Мы будем благодарны любой поддержки от вас. И также благодарим, что вы пользуетесь нашим ботом - это главная ваша поддержка для нас!\n\n" \
      f"Мы принимаем материальную поддержку на:\n" \
      f"<b>1. Donationalerts</b>\n" \
      f"<b>2. PAYEER</b>\nномер счёта для пополнения: P1091200672\n" \
      f"<b>3. QIWI</b>\n" \
      f"<b>4. Тинькофф банк</b>"
  key = types.InlineKeyboardMarkup()
  button0 = types.InlineKeyboardButton(text="Donationalerts", url="https://www.donationalerts.com/r/tgbot_v")
  button1 = types.InlineKeyboardButton(text="PAYEER", url="https://payeer.com/ru/account/send/")
  button2 = types.InlineKeyboardButton(text="QIWI", url="https://my.qiwi.com/VLADYSLAV-DTJ4Y_MwOA")
  button3 = types.InlineKeyboardButton(text="Тинькофф банк", url="https://www.tinkoff.ru/cf/35TWsWpG8Fe")
  key.add(button0, button1, button2, button3)
  bot.send_message(message.from_user.id, message_users, reply_markup=key, parse_mode='html')
  bot.register_next_step_handler(message, impact_KEYBORD_bot)
  bot.register_next_step_handler(message, fuctional_KEYBORD_bot)
@bot.message_handler(content_types=['text'])
def translate_message(message):
if message.text == 'Переводчик':
  bot.send_message(message.chat.id, 'Напишите сообщения а я переведу его')
  bot.register_next_step_handler(message, translate_message_step_2)
def translate_message_step_2(message):
cmd3 = message.text
if cmd3[0].lower() in ru_letters:
  translation = translator.translate(cmd3, src=detect(cmd3), dest='en').text
  bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
elif cmd3[0].lower() in en_letters:
  translation = translator.translate(cmd3, src=detect(cmd3), dest='ru').text
  bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
else:
  bot.send_message(message.chat.id, 'Я тебя не понимаю')
def dictionary_message(message):
if message.text == 'Словарь':
  bot.send_message(message.chat.id, 'list | term')
  #text = message.text
  #bot.send_message("TEST")
if __name__ == '__main__':
bot.polling(none_stop=True, interval=0)
root@kvmubuntu:~# python3 pyTranslatorMenuTelebot.py
- /Start
- Привет, David! Я тестовый бот.
Выбери программу, которую ты хочешь выполнить:
1. Чем полезен данный бот
2. Функции бота (что может данный бот)
3. Для тех кто хочет поддержать нас и наш проект
- Функции бота
- Добро пожаловать главное меню бота
В скором будущем мы будем добавлять сюда новые функции!
1. Переводчик
2. Словарь
- Переводчик
- Напишите сообщения, а я переведу его
- Привет, Катя!
- Hello, Katya!
- Переводчик
- Напишите сообщения, а я переведу его
- Dear Kate!
- Дорогая Кейт!
Source:
# https://www.w3schools.com/python/ref_func_input.asp - Python input() Function.
# https://proglib.io/p/samouchitel-po-python-dlya-nachinayushchih-chast-10-uslovnyy-cikl-while-2022-12-22 - Управление бесконечным циклом while в Питоне.
# https://letpy.com/python-guide/functions/ - Функции в Python для начинающих.
# https://ru.stackoverflow.com/questions/1211592/%D0%9A%D0%B0%D0%BA-%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C-%D0%BC%D0%B5%D0%BD%D1%8E-%D1%81-%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BE%D0%BC-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D0%BD%D0%B0-python - Как создать меню с выбором функций на Python?
# https://ru.stackoverflow.com/questions/1341916/%D0%9A%D0%B0%D0%BA-%D0%B7%D0%B0%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%82%D1%8C-%D0%BC%D0%B5%D0%BD%D1%8E-%D0%B8%D0%BC%D0%B5%D1%8E%D1%89%D0%B5%D0%B5-%D0%BF%D0%BE%D0%B4%D0%BC%D0%B5%D0%BD%D1%8E-%D0%B2-python - Как зациклить меню, имеющее подменю в python?
# https://docs.python.org/3/tutorial/venv.html - Creating Virtual Environments.
# https://www.youtube.com/watch?v=A1p7bEtTlxc&t=4s - Как сделать меню для Телеграм Бота на Python.
Task:
Добавить информацию в базу данных в телеграм бот.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyPsqlTelebot.py
root@kvmubuntu:~# cat pyPsqlTelebot.py
import psycopg2
import telebot
from telebot import types
from telebot.types import Message
TOKEN='tkey'
bot = telebot.TeleBot(TOKEN)
DATABASE_URL = "postgres://tuser:tpassword@tipubuntu:5432/tbase2"
def connect_to_db():
conn = psycopg2.connect(DATABASE_URL, sslmode='require')
return conn
def insert_data(words, translate):
conn = connect_to_db()
cursor = conn.cursor()
query = "INSERT INTO Dictionary (words, translate) VALUES (%s, %s)"
cursor.execute(query, (words, translate))
conn.commit()
cursor.close()
conn.close()
@bot.message_handler(commands=['start'])
def handle_start(message: Message):
words = "Текст4"
translate = "Text4"
insert_data(words, translate)
bot.reply_to(message, "Add Info in Doctionary.")
if __name__ == '__main__':
bot.polling(none_stop=True, interval=0)
root@kvmubuntu:~# python3 pyPsqlTelebot.py
- /Start
- Add Info in Doctionary.
root@kvmubuntu:~# psql -U tuser -d tbase2 -h tipubuntu
tbase2=# select * from Dictionary;
id |   words   | translate
----+------------------------+-------------------
1 | Внутреннее соединение | Inner join
2 | Доступ     | Available
3 | Полное соединение | Cross Join
4 | Декартово произведение | Cartesian product
5 | Текст4     | Text4
(5 rows)
Task:
Перенести все логины и пароли в отдельный файл.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim .env
root@kvmubuntu:~# cat .env
TOKEN='tkey'
DB_URL='postgres://tuser:tpassword@tipubuntu:5432/tbase2'
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
translate==3.6.1
pyTelegramBotAPI==4.21.0
googletrans==3.1.0a0
langdetect==1.0.9
psycopg2-binary==2.9.9
python-dotenv==1.0.1
python-decouple==3.8
root@kvmubuntu:~# vim .env
root@kvmubuntu:~# cat .env
TOKEN='tkey'
DB_URL='postgres://tuser:tpassword@tipubuntu:5432/tbase2'
Task:
Применить в Телеграм Бот программ Переводчик.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslatorMenuTelebotPsql.py
root@kvmubuntu:~# cat pyTranslatorMenuTelebotPsql.py
import psycopg2
import telebot
from telebot import types
from telebot.types import Message
from googletrans import Translator
from langdetect import detect
from decouple import config
from contextlib import closing
bot = telebot.TeleBot(config('TOKEN'))
translator = Translator()
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
DATABASE_URL = config('DB_URL')
def connect_to_db():
conn = psycopg2.connect(DATABASE_URL, sslmode='require')
return conn
def insert_dictionary(words, translate):
conn = connect_to_db()
cursor = conn.cursor()
query = "INSERT INTO Dictionary (words, translate) VALUES (%s, %s)"
cursor.execute(query, (words, translate))
conn.commit()
cursor.close()
conn.close()
def insert_terms(words_id, description, translate):
conn = connect_to_db()
cursor = conn.cursor()
query = "INSERT INTO Terms (words_id, description, translate) VALUES (%s, %s, %s)"
cursor.execute(query, (words_id, description, translate))
conn.commit()
cursor.close()
conn.close()
@bot.message_handler(commands=['start'])
def start(message):
message_user = f"Привет, <b>{message.from_user.first_name.title()}</b>! Я тестовый бот.\n" \
     f"1. Чем полезен бот\n" \
     f"2. Для тех кто хочет поддержать проект"
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
but1 = types.KeyboardButton(text="Чем полезен бот ?")
but2 = types.KeyboardButton(text="Поддержать проект")
markup.add(but1, but2)
bot.send_message(message.from_user.id, message_user, reply_markup=markup, parse_mode='html')
bot.register_next_step_handler(message, funcs_bot)
bot.register_next_step_handler(message, donat_bot)
def funcs_bot(message):
if message.text == "Чем полезен бот ?":
  message_user = "Функции бота:\n" \
      "1. Переводчик\n" \
      "2. Дополнить словарь\n" \
      "3. Словарь\n" \
      "4. Термины\n" \
      "5. Дополнить описание"
  key = types.ReplyKeyboardMarkup(resize_keyboard=True)
  #button = types.InlineKeyboardButton(text='Мое портфолио', url="http://dato138it.ru")
  but1 = types.KeyboardButton("Переводчик")
  but2 = types.KeyboardButton("Дополнить словарь")
  but3 = types.KeyboardButton("Словарь")
  but4 = types.KeyboardButton("Термины")
  but5 = types.KeyboardButton("Дополнить описание")
  key.add(but1, but2, but3, but4, but5)
  bot.send_message(message.from_user.id, message_user, reply_markup=key, parse_mode='html')
  #bot.register_next_step_handler(message, funcs_mess)
def donat_bot(message):
if message.text == "Поддержать проект":
  message_users = f"<b>Приветствую, уважаемый {message.from_user.first_name.title()}</b>, вы перешли в отдел поддержки нашего проекта\n" \
      f"Мы будем благодарны материальной поддержки от вас на:\n" \
      f"<b>1. ЮMoney</b>\n" \
      f"<b>2. Альфа банк</b>"
  key = types.InlineKeyboardMarkup()
  but1 = types.InlineKeyboardButton(text="ЮMoney", url="http://dato138it.ru")
  but2 = types.InlineKeyboardButton(text="Альфа банк", url="http://dato138it.ru")
  key.add(but1, but2)
  bot.send_message(message.from_user.id, message_users, reply_markup=key, parse_mode='html')
  bot.register_next_step_handler(message, funcs_bot)
@bot.message_handler(content_types=['text'])
def funcs_mess(message):
if message.text == 'Переводчик':
  bot.send_message(message.chat.id, 'Напишите сообщение, которое нужно перевести:')
  bot.register_next_step_handler(message, translator_mess)
elif message.text == 'Дополнить словарь':
  if message.from_user.id == int(config('ADMIN_ID')):
   bot.send_message(message.chat.id, 'Напишите 2 сообщения (предложение и его перевод), которые нужно добавить в словарь:')
   bot.register_next_step_handler(message, AddDictionary_mess1)
  else:
   bot.send_message(message.chat.id, 'У вас нет прав на добавление и удаление предложений из базы')
elif message.text == 'Дополнить описание':
  if message.from_user.id == int(config('ADMIN_ID')):
   bot.send_message(message.chat.id, 'Напишите 3 сообщения (id - в словаре можно увидеть id, описание и его перевод), которые нужно добавить в словарь:')
   bot.register_next_step_handler(message, AddTerms_mess1)
  else:
   bot.send_message(message.chat.id, 'У вас нет прав на добавление и удаление предложений из базы')
elif message.text == 'Словарь':
  bot.send_message(message.chat.id, 'Вывожу список слов:')
  #bot.register_next_step_handler(message, dictionary_mess)
  with closing(connect_to_db()) as conn:
   with conn.cursor() as cursor:
    cursor.execute('select * from Dictionary;')
    for row in cursor:
     bot.send_message(message.chat.id, "| "+str(row[0])+" | "+row[1]+" | "+row[2]+" |")
  bot.reply_to(message, "Selected to the dictionary.")
elif message.text == 'Термины':
  bot.send_message(message.chat.id, 'Вывожу список терминов:')
  with closing(connect_to_db()) as conn:
   with conn.cursor() as cursor:
    cursor.execute('select words, Dictionary.translate, description, Terms.translate from Dictionary inner join Terms on Dictionary.id = Terms.words_id;')
    for row in cursor:
     bot.send_message(message.chat.id, " | "+str(row[0])+" | "+row[1]+" | "+row[2]+" | "+row[3]+" | ")
  bot.reply_to(message, "Selected to the terms.")
else:
  bot.send_message(message.chat.id, 'Я тебя не понимаю')
def translator_mess(message):
cmd1 = message.text
if cmd1[0].lower() in ru_letters:
  translation = translator.translate(cmd1, src=detect(cmd1), dest='en').text
  bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
elif cmd1[0].lower() in en_letters:
  translation = translator.translate(cmd1, src=detect(cmd1), dest='ru').text
  bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
else:
  bot.send_message(message.chat.id, 'Я тебя не понимаю')
def AddDictionary_mess1(message):
global cmd2
cmd2 = message.text
bot.register_next_step_handler(message, AddDictionary_mess2)
def AddDictionary_mess2(message):
global cmd3
cmd3 = message.text
insert_dictionary(cmd2, cmd3)
bot.reply_to(message, "Info added to the dictionary.")
def AddTerms_mess1(message):
global cmd4
cmd4 = message.text
bot.register_next_step_handler(message, AddTerms_mess2)
def AddTerms_mess2(message):
global cmd5
cmd5 = message.text
bot.register_next_step_handler(message, AddTerms_mess3)
def AddTerms_mess3(message):
global cmd6
cmd6 = message.text
insert_terms(cmd4, cmd5, cmd6)
bot.reply_to(message, "Info added to the terms.")
if __name__ == '__main__':
bot.polling(none_stop=True, interval=0)
root@kvmubuntu:~# python3 py.py
- Чем полезен бот ?
- Функции бота:
1. Переводчик
2. Дополнить словарь
- Дополнить словарь
- Напишите 2 сообщения (предложение и его перевод), которые нужно добавить в словарь:
- new
новый
- Info added to the dictionary.
- Словарь
- Вывожу словарь:
| Inner join | Возвращаются только те строки, где ключевые значения совпадают в обеих таблицах | Only those rows are returned where the key values match in both tables |
| Cross Join | Позволяет получить декартово произведение нескольких таблиц. особенно полезен, когда между таблицами нет определенной связи, и вам нужно создать полную комбинацию записей из каждой таблицы | - |
| Cartesian product | Результат соединения строки из первой таблицы с каждой строкой из второй таблицы | - |
Selected to the dictionary.
root@kvmubuntu:~# psql -U tuser -d tbase2 -h tipubuntu
tbase2=# select * from Dictionary;
...
49 | new | новый
tbase2=# TRUNCATE Terms, Dictionary CASCADE;
Source:
# https://stackoverflow.com/questions/75900203/how-do-i-connect-my-telegram-bot-telebot-to-postgresql-url - How do I connect my telegram bot (telebot) to PostgreSQL url.
# https://pythonru.com/osnovy/globalnye-peremennye-python - Правила использования global.
# https://ru.stackoverflow.com/questions/1103332/Авторизация-в-телеграмм-боте-на-python - Авторизация в телеграмм боте на Python.
# https://otvet.mail.ru/question/219454196 - Python. Telegram bot api Как из Username получить user_id.
# https://stackoverflow.com/questions/13223820/postgresql-delete-all-content - PostgreSQL удаляет все содержимое.
Task:
Настройка службы бота.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@kvmubuntu:~# vim /etc/systemd/system/dato38itbot.service
root@kvmubuntu:~# cat /etc/systemd/system/dato38itbot.service
[Unit]
Description=Telegram dato38it-bot
After=network.target
[Service]
User=tuser
Group=tuser
WorkingDirectory=/home/tuser/dato38itbot/
VIRTUAL_ENV=/home/tuser/dato38itbot/telegaenv
Environment=PATH=$VIRTUAL_ENV/bin:$PATH
ExecStart=/home/tuser/dato38itbot/telegaenv/bin/python /home/tuser/dato38itbot/main.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
root@kvmubuntu:~# systemctl daemon-reload
root@kvmubuntu:~# systemctl enable dato38itbot.service
root@kvmubuntu:~# systemctl start dato38itbot.service
root@kvmubuntu:~# systemctl status dato38itbot.service
Source:
# https://thecode.media/systemctl/ - Готовим файл для работы службы.
# https://synay.net/vps/preconfigured/own-telegram-bot-server-debian-12 - Создадим файл службы, чтобы запускать бот автоматически.
# https://gist.github.com/ricferr/90583f608f0b0ae9c3cf6833be04ab85 - How to create a systemd service for python script with virtualenv.

2022-06-01 - 2022-11-01: Yandex Практикум, Иркутск. Должность: Инженер облачных сервисов - Дополнительное образование. Дополнительная информация: Навыки - Html, Виртуализация, Linux, Sql, Bash, Clouds, Docker, Python. Достижения: В рамках программы "Инженер облачных сервисов Yandex" защитил практические работы по темам "Хранение и анализ данных", "Devops и автоматизация", "Serverless" и "Безопасность".

Show

Цель:
# создать ВМ на базе OC Linux из консоли управления и подключиться к ней, получить доступ к серийной консоли, создать снимок диска ВМ, создать новую сеть с подсетями и ВМ, создать группу безопасности и открыть доступ к серверу, создать балансировщик? создать группы виртуальных машин.
# Настроить Автоматическое масштабирование под нагрузкой, Воссоздать виртуальные машин в группе.
# Создание бакетов и загрузка объектов. Хранение статических веб-сайтов в Object Storage.
# Создание кластера базы данных MySQL. Подключение к БД и добавление данных. Создание кластера базы данных PostgreSQL.
# Создание кластера MongoDB.
# Создание кластера ClickHouse и подключение к нему. Работа с данными из объектного хранилища. Добавление данных.
# Создание базы данных. YQL и работа с данными.
# Создание кластера Hadoop. Подключение к кластеру и работа с Hive.
# Создание датасетов, чартов и дашбордов.
# Начало работы в CLI. Создание виртуальных машин с помощью CLI. Использование файлов спецификаций.
# Создаём образ виртуальной машины.
# Создаём виртуальную машину из образа и базу данных.
# Создание докер-образа и загрузка его в Container Registry.
# Создание кластера. Первое приложение в кластере. Балансировка нагрузки. Автомасштабирование в Yandex Managed Kubernetes.
# Сбой виртуальной машины. Сбой зоны доступности. Обновление приложения. Сбой приложения.
# Отправка собственных метрик. Выгрузка метрик в формате Prometheus. Создание алерта.
# Создаём вашу первую функцию. Запускаем функцию с помощью CLI. Создание триггера от Object Storage. Навык Алисы. Проверка доступности.
# Создание HTTP API с помощью Cloud Functions и API Gateway.
# Загрузка данных, выполнение запросов AWS CLI. Запуск тестового приложения.
# Проверка доступности веб-ресурсов. Однократная отправка сообщений.
# Сокращатель ссылок.
# Права доступа и роли для сервисного аккаунта. Организация защищённого канала.
# Выпуск сертификата для сайта. Создание и ротация ключей шифрования.
Skills:
# Администрирование локальных, виртуальных и облачных серверов.
# Администрирование баз данных.
# Написание Sql запросов.
# Разработка Навыки для Алисы.
# Разработка HTTP API запросов.
# Разработка запросов AWS CLI для создания таблиц БД, запись и чтения данных.
# Разработка системы проверки доступности веб-ресурсов URL с доступом по REST API.
# Разработка конвертировании видеофайлы в GIF.
# Разработка Сокращатель ссылок.
Task:
Создание ВМ, SSH-ключи, Удалённое подключение к ВМ, Установка обновления. Получаем доступ к серийной консоли.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Откройте консоль управления облаком. Если это новое облако, которое вы создали в предыдущей практической работе, то вы окажетесь на странице дашборда каталога с именем default. Этот каталог и облачная сеть в нём с таким же именем создаются вместе с облаком автоматически.
Выберите сервис Compute Cloud из списка (Дашборд каталога → Все сервисы → Compute Cloud), нажмите на раздел Виртуальные машины, а затем кнопку Создать ВМ.
В открывшемся окне понадобится указать параметры создаваемой ВМ. Подробно мы разберём их на следующем уроке, а пока используйте наши рекомендации и значения по умолчанию.
В блоке Базовые параметры укажите Имя ВМ, оно может содержать строчные латинские буквы, цифры и дефисы. Поле Описание необязательное — его обычно заполняют, чтобы не запутаться, если ВМ несколько. Выберите из списка Зону доступности ru-central1-a.
На вкладке Операционные системы В блоке Выбор образа/загрузочного диска выберите ОС, которая будет установлена на ВМ, — Ubuntu 22.04.
В блоке Диски оставьте значения по умолчанию: тип — HDD, размер — 18 ГБ.
В блоке Вычислительные ресурсы оставьте значения по умолчанию: платформа Intel Ice Lake, 2 ядра виртуального процессора (vCPU), гарантированная доля vCPU 100% и объём оперативной памяти (RAM) 2 ГБ.
В блоке Сетевые настройки оставьте используемую по умолчанию подсеть, а для Публичного адреса и Внутреннего адреса — опцию Автоматически.
В блоке Доступ заполните поле Логин (имя пользователя создаваемой ВМ).
Не указывайте идентификатор root или другие имена, зарезервированные операционной системой. Для операций, требующих прав суперпользователя, нужно будет использовать команду sudo.
Чтобы подключиться к создаваемой ВМ по протоколу SSH, понадобится пара SSH-ключей. Открытый ключ хранится на ВМ, а закрытый — у пользователя. В публичных образах Linux, предоставляемых Yandex Cloud, возможность подключения по SSH с использованием логина и пароля по умолчанию отключена.
В Linux/macOS или Windows 10/11
Запустите терминал в Linux/macOS, либо CMD или PowerShell в Windows 10/11 и создайте пару ключей с помощью команды:
ssh-keygen -t ed25519 
После запуска команды укажите имена файлов, куда сохранятся ключи, и введите пароль для закрытого ключа. По умолчанию используется имя id_ed25519, ключи создаются в папке ~/.ssh текущего пользователя.
Открытый ключ будет сохранён в файле <имя_ключа>.pub. Содержимое этого файла вставляется в поле SSH-ключ на странице создания ВМ.
В Windows 7/8
Если на вашем компьютере установлена Windows 7 или 8, то создать SSH-ключи можно с помощью приложения PuTTY. Как это сделать, рассказывается в документации.
поле SSH-ключ вставьте содержимое созданного открытого ключа. Убедитесь, что вставляете ключ без переносов строки.
Например:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMJhqJBKbDCrmLfJbXf6ulb2rQp+LTHH+5qliRAXHj+r user@DESKTOP 
Изменяя параметры ВМ, вы увидите, как в блоке Тарифы и цены (справа) меняется её стоимость.
Нажмите кнопку Создать ВМ. 
Развёртывание ВМ занимает примерно минуту. Вы можете отслеживать этот процесс по смене статуса.
Статус ВМ влияет на то, какие операции вы можете с ней выполнять. Например, статус Stopped означает, что машина остановлена и к ней невозможно подключиться. При запуске статус меняется на Provisioning (это значит, что облачная платформа выделяет ресурсы для ВМ), а после загрузки операционной системы — на Running.
Теперь, когда ВМ создана и запущена, к ней можно подключиться. Для этого понадобятся логин пользователя и публичный IP-адрес ВМ (скопируйте его на странице Виртуальные машины).
Чтобы подключиться к ВМ (она должна находиться в статусе RUNNING) по протоколу SSH, используют утилиту ssh в Linux/macOS или Windows 10/11, программу PuTTY в Windows 7/8 или любой другой клиент SSH. 
Откройте терминал в Linux/macOS или запустите PowerShell в Windows 10/11 и выполните команду:
ssh <имя_пользователя>@<публичный_IP-адрес_ВМ> 
Если у вас несколько закрытых ключей, укажите нужный:
ssh -i <путь_к_ключу/имя_файла_ключа> <имя_пользователя>@<публичный_IP-адрес_ВМ> 
При первом подключении может появиться предупреждение о неизвестном хосте:
The authenticity of host '158.160.43.49 (158.160.43.49)' can't be established.
ED25519 key fingerprint is SHA256:GVnaElEw3ryuAOZXwuT4uNhrIfHE8R6mju+65OVDbzs.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? 
Введите в терминале yes, затем пароль для ключа и вы окажетесь в консоли созданной ВМ.
Установите обновления. Для этого запустите в консоли команды:
sudo apt-get update
sudo apt-get upgrade 
Работа серийной консоли зависит от настроек операционной системы. Yandex Compute Cloud обеспечивает канал связи между пользователем и COM-портом ВМ, но не гарантирует стабильность работы консоли со стороны операционной системы ВМ.
Доступ к серийной консоли ВМ с ОС на базе Linux возможен следующими способами:
по протоколу SSH с другого компьютера;
через консоль управления Yandex Cloud;
с помощью интерфейса командной строки Yandex Cloud CLI. Подробнее работу с CLI мы рассмотрим в одной из следующих практических работ.
Вход по протоколу SSH
Подключитесь к ВМ по протоколу SSH. Установите пароль текущему пользователю с помощью утилиты passwd в привилегированном режиме:
sudo passwd <имя_пользователя> 
  После ввода команды дважды наберите одинаковый пароль.
Для доступа к серийной консоли ВМ необходимо знать её идентификатор (ID). В консоли управления перейдите в раздел Compute Cloud. По умолчанию откроется страница со списком ВМ. В столбце справа указан идентификатор каждой ВМ.
Используйте для входа идентификатор ВМ и имя (логин) созданного в ней пользователя. Вот шаблон команды подключения для Linux:
ssh -t -p 9600 -o IdentitiesOnly=yes -i ~/.ssh/<имя закрытого ключа> <ID виртуальной машины>.<имя пользователя>@serialssh.cloud.yandex.net 
Вот так вы подключитесь к консоли, если в ВМ с ID fhm0b28lgfp4tkoa3jl6 есть пользователь yc-user:
ssh -t -p 9600 -o IdentitiesOnly=yes -i ~/.ssh/id_rsa fhm0b28lgfp4tkoa3jl6.yc-user@serialssh.cloud.yandex.net 
Введите установленный ранее пароль.
Чтобы отключиться от серийной консоли, нажмите клавишу Enter, а затем введите символы ~. (тильда и точка). В терминалах Linux для отключения также можно использовать комбинацию клавиш Ctrl + D.
Вход через консоль управления
В консоли управления откройте страницу ВМ и через меню слева перейдите на страницу Серийная консоль. При авторизации используйте логин, указанный при создании ВМ, и пароль, который вы установили после подключения к ней.
Decision:
tuser@kvmubuntu:~$ ssh-keygen -t ed25519 
Enter file in which to save the key (/home/tuser/.ssh/id_ed25519): kvmcloudubuntu
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
tuser@kvmubuntu:~$ cat kvmcloudubuntu.pub
ssh-ed25519 tkey tuser@kvmubuntu
tuser@kvmubuntu:~$ ssh tuser@130.193.44.138
tuser@130.193.44.138: Permission denied (publickey).
tuser@kvmubuntu:~$ ssh -i kvmcloudubuntu tuser@130.193.44.138
Enter passphrase for key 'kvmcloudubuntu': 
tuser@cloudubuntu:~$ sudo apt-get update
tuser@cloudubuntu:~$ sudo apt-get upgrade
tuser@cloudubuntu:~$ sudo passwd tuser
tuser@kvmubuntu:~$ ssh -t -p 9600 -o IdentitiesOnly=yes -i kvmcloudubuntu fv4fbicm16si0qmpu527.tuser@serialssh.cloud.yandex.net
Task:
Создаем ВМ с 20% vCPU и используем мониторинг.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Давайте создадим ВМ с гарантированной долей vCPU, равной 20%, и проверим её производительность.
В консоли управления перейдите в раздел Compute Cloud и нажмите кнопку Создать ВМ. Заполните имя, выберите зону доступности и операционную систему Ubuntu 22.04.
В блоке Вычислительные ресурсы выберите платформу Intel Ice Lake и укажите гарантированную долю vCPU 20%. Другие параметры оставьте по умолчанию.
После создания и запуска ВМ в списке машин нажмите её название. Вы перейдёте на страницу ВМ. Затем на левой боковой панели выберите Мониторинг. Откроется страница, где в динамике показывается информация о загрузке процессора, операциях с диском и сетевой активности. По умолчанию видны данные за одни сутки (1d — 1 day).
Переключитесь на один час: вверху нажмите 1h (1 hour).
На графике видно, что при запуске использование процессорных ресурсов было высоким, а позже снизилось до приемлемого. Чтобы посмотреть точные значения в определённый момент, поместите указатель над линией графика. Вы увидите всплывающее окно с показателями для этой точки времени.
Теперь удалите ВМ. Для этого сначала остановите её — вернитесь в список ВМ, отметьте нужную ВМ и на появившейся внизу контекстной панели нажмите Остановить.
Во всплывающем окне подтвердите действие и нажмите кнопку Остановить. Дождитесь смены статуса на Stopped.
Чтобы удалить ВМ, в списке ВМ справа напротив машины нажмите ... и в раскрывшемся меню выберите Удалить. Подтвердите действие. Через некоторое время ВМ будет удалена.
Подробнее о способе выполнить нагрузочное тестирование ВМ вы узнаете из практического занятия в теме «Группы виртуальных машин».
Task:
Создаем снимок диска ВМ.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Допустим, вы планируете обновить ПО на виртуальной машине ВМ. Вы знаете, что взаимодействие приложений и системных сервисов после обновления может нарушиться, а данные могут быть повреждены. Поэтому хотите иметь резервную копию полностью работоспособной системы на случай неудачи.
Давайте на практике разберём, как сделать снимок и восстановить из него ВМ при повреждении системы. Для этого используем ВМ, на которой развернута система на основе Ubuntu или CentOS.
Целостность данных
В первую очередь обеспечьте целостность данных. Для этого подключитесь к ВМ по SSH и выполните команду sync, чтобы записать кеш операционной системы на диск (иначе изменения файлов, хранящиеся в оперативной памяти, будут потеряны).
Диски в Linux монтируются в ОС в виде файлов. Чтобы узнать нужный файл устройства диска, выполните команду df -h для вывода полного списка устройств и соответствующих точек монтирования. Затем, чтобы заморозить файловую систему, запустите команду sudo fsfreeze --freeze <точка_монтирования>. 
Создание снимка
В консоли управления откройте раздел Compute Cloud и перейдите на вкладку Диски. Справа от диска нажмите ... и выберите Создать снимок.
В открывшемся окне вы увидите автоматически сформированное имя снимка диска. Оно состоит из имени диска и временной отметки. Вы можете переименовать снимок и заполнить его описание. После этого нажмите кнопку Создать.
Откройте Снимки дисков. Как только снимок будет создан, статус операции сменится с Creating на Ready.
Разморозьте файловую систему. Для этого в командной строке с интерфейсом подключения к ВМ по SSH выполните команду sudo fsfreeze --unfreeze <точка монтирования>.
Намеренное повреждение системы
Теперь обновите систему: в командной строке с интерфейсом подключения к ВМ по SSH последовательно выполните команды sudo apt-get update и sudo apt-get dist-upgrade. Дождитесь, пока обновление завершится.
Важно! Перед выполнением следующей команды убедитесь в том, что находитесь в консоли именно тестовой ВМ.
Сымитируйте повреждение системы, запустив команду
sudo rm -rf --no-preserve-root / 
Вы увидите предупреждение, что все данные на диске будут удалены. Подтвердите своё намерение.
Восстановление из снимка
Поскольку снимок создан с загрузочного диска, который всегда подключён к ВМ, для восстановления создайте новую ВМ вместо старой. При создании загрузочного диска машины выберите готовый снимок диска. Для этого в блоке Выбор образа/загрузочного диска перейдите на вкладку Свой образ и нажмите кнопку Выбрать. В открывшемся окне перейдите на вкладку Снимок, выберите нужный снимок и нажмите кнопку Применить.
Дождитесь завершения создания и запуска новой ВМ. Теперь старую ВМ можно остановить и удалить.
Будьте внимательны при создании новых виртуальных машин: в облаке действуют квоты и лимиты на используемые ресурсы.
Task:
Создание новой сети с подсетями и ВМ.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Облачные сети (сервис Virtual Private Cloud или VPC) являются частью публичного облака, которая связывает пользовательские, инфраструктурные, платформенные и прочие ресурсы воедино, где бы они ни находились — в нашем облаке или за его пределами. При этом VPC позволяет не публиковать эти ресурсы в интернете без необходимости, они остаются в пределах вашей изолированной сети.
Когда вы создаёте облако, в нём автоматически создаются сеть default и подсети в каждой зоне доступности. Но иногда их бывает недостаточно. В этой практической работе вы научитесь создавать облачную сеть и добавлять подсети самостоятельно.
Рассмотрим пример, как настроить облачную сеть, чтобы организовать работу сервера с доступом из интернета. Сначала создадим единую для всех ресурсов облака изолированную сеть с подсетями и виртуальной машиной.
В консоли управления перейдите на страницу сервиса Virtual Private Cloud (Дашборд каталога → Virtual Private Cloud) и нажмите кнопку Создать сеть вверху справа.
Введите имя сети (пусть она называется yc), поле Описание заполнять необязательно. Оставьте выбранной опцию Создать подсети и нажмите кнопку Создать сеть.
В результате будут созданы сеть yc и четыре подсети: yc-ru-central1-a, yc-ru-central1-b, yc-ru-central1-c и yc-ru-central1-d.
Для сервера создадим ещё одну подсеть с маской /28. Перейдите на страницу сети yc (Дашборд каталога → Virtual Private Cloud → сеть yc) и нажмите кнопку Добавить подсеть.
Введите параметры подсети: Имя — yc-public, Зона — ru-central1-a, CIDR — 192.168.0.0/28. Нажмите кнопку Создать подсеть.
Доступом пользователей облака к сетевым ресурсам управляют с помощью назначения ролей.
Теперь создайте ВМ с именем web-server и ОС Ubuntu 22.04. Убедитесь, что в блоке Базовые параметры выбрана зона доступности ru-central1-a. В блоке Сетевые настройки выберите Подсеть yc-public. В блоке Доступ введите логин пользователя (например user) и вставьте открытый SSH-ключ в соответствующее поле.
Чтобы ВМ полноценно заработала, для неё нужно организовать доступ в интернет. Есть три способа:
Назначить ВМ публичный IP-адрес. Для этого выберите автоматический способ назначения IP-адреса, когда создаёте ВМ, или привяжите его к уже созданной.
Использовать NAT-шлюз, который позволяет дать ВМ доступ в интернет без назначения ей публичного IP-адреса. Самому шлюзу адрес выделяется из отдельного диапазона публичных IP-адресов.
Использовать NAT-инстанс — отдельную ВМ со статическим публичным IP-адресом и настроенными правилами маршрутизации трафика.
С точки зрения безопасности лучше выбирать второй или третий способ. В этом примере для упрощения присвойте ВМ публичный IP-адрес — в поле Публичный адрес выберите опцию Автоматически.
После создания ВМ проверьте её доступность, чтобы убедиться в том, что сетевая конфигурация настроена правильно. Для этого перейдите на страницу Обзор созданной ВМ (Дашборд каталога → Compute Cloud → ВМ web-server) и в блоке Сеть найдите её публичный IP-адрес.
Откройте интерфейс командной строки на своём компьютере и введите команду:
ping 51.250.78.143 
Если конфигурация корректна, в результате выполнения команды ping вы увидите примерно следующее.
Pinging 51.250.89.16 with 32 bytes of data:
Reply from 51.250.89.16: bytes=32 time=18ms TTL=50
Reply from 51.250.89.16: bytes=32 time=17ms TTL=50
Reply from 51.250.89.16: bytes=32 time=18ms TTL=50
Reply from 51.250.89.16: bytes=32 time=17ms TTL=50
Ping statistics for 51.250.89.16:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
  Minimum = 17ms, Maximum = 18ms, Average = 17ms 
Такая конфигурация подходит для небольшого веб-сервера. Если вы собираетесь строить озеро данных или обрабатывать математические вычисления, не рекомендуется давать к ресурсам прямой доступ из интернета — разместите их за NAT.
Task:
Создаем группу безопасности и открываем доступ к серверу.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Давайте попробуем создать группу безопасности и сделать доступными страницы, предоставляемые веб-сервером NGINX.
Перейдите на вкладку Группы безопасности сервиса Virtual Private Network и нажмите кнопку Создать группу.
Введите имя группы yc-security и выберите сеть yc (вы создали её на одном из предыдущих практических занятий).
В блоке Правила добавьте правила для исходящего трафика. Опишите правило, укажите диапазон портов 80 (HTTP) и протокол TCP, выберите назначение "CIDR" 0.0.0.0/0 . Создайте аналогичные исходящие правила для портов 443 (HTTPS) и 22 (SSH). Для входящего трафика добавьте правила для 80 и 22 портов, чтобы подключаться к веб-серверу и управлять ВМ извне.
Вы увидите созданную группу в списке групп безопасности.
     Если инициировано соединение по определенному порту и протоколу с ВМ и есть исходящее правило, то значит и на входящий трафик будет разрешена передача данных в эту же сеть, на этот же протокол и порт.
       Если назначить сетевому интерфейсу ВМ группу безопасности без правил, ВМ не сможет передавать и принимать трафик.
Создайте виртуальную машину на базе Ubuntu 22.04, выберите сеть yc и вновь созданную группу безопасности yc-security в сетевых настройках создаваемой ВМ, дождитесь запуска машины, подключитесь к ВМ по SSH и установите веб-сервер NGINX (по умолчанию он отсутствует). Для этого выполните команду:
sudo apt-get install nginx 
Установка возможна, поскольку вы открыли порт 80: команда apt-get использует его для получения пакетов с ПО.
После установки сервер автоматически запустится и будет доступен извне благодаря открытому порту 80. Проверьте это, зайдя через браузер на публичный IP-адрес ВМ, который найдёте на странице параметров машины в консоли управления.
Task:
Знакомство с Yandex Cloud CLI.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Пора изучить, как работает сетевой балансировщик, на практике. Для этого создадим в облаке простую систему: развернём две ВМ, запустим на них веб-серверы NGINX и поместим их за балансировщик. В этой практической работе вам предстоит подготовить виртуальные машины, а в следующей — создать балансировщик и поэкспериментировать с ним.
Создавать ВМ через консоль управления облаком вы уже умеете. Вот только для реальных проектов часто приходится разворачивать далеко не одну-две ВМ. В этом случае работать через консоль управления долго и неудобно, да и вероятность ошибиться, выполняя много однотипных операций вручную, растёт. Решить эту проблему можно с помощью автоматизации, а один из самых простых способов это сделать — использовать интерфейс командной строки (CLI, Command Line Interface).
Подробно этот инструмент вы будете изучать в курсе «DevOps и автоматизация», а сейчас давайте посмотрим, как его применяют, на примере создания ВМ.
Начнём?
Установите на свой компьютер утилиту yc, которая представляет собой интерфейс командной строки Yandex Cloud CLI, и настройте её (создайте профиль) по инструкции в документации.
Создайте файл startup.sh со следующим содержимым:
#!/bin/bash
apt-get update
apt-get install -y nginx
service nginx start
sed -i -- "s/nginx/Yandex Cloud - ${HOSTNAME}/" /var/www/html/index.nginx-debian.html
EOF 
Это скрипт, который будет запускаться на разворачиваемых ВМ. Он обновляет список пакетов с софтом, устанавливает и запускает NGINX, а затем изменяет текст информационной страницы работающего веб-сервера.
Чтобы создать ВМ с помощью Yandex Cloud CLI, нужно запустить на своем компьютере командную оболочку (например, bash в Ubuntu) и выполнить в ней команду:
yc compute instance create \
--name demo-1 \
--hostname demo-1 \
--metadata-from-file user-data=startup.sh \
--create-boot-disk image-folder-id=standard-images,image-family=ubuntu-2004-lts \
--zone ru-central1-a \
--network-interface subnet-name=default-ru-central1-a,nat-ip-version=ipv4 
Но прежде чем это делать, давайте разберёмся, что же в ней написано.
Большинство команд Yandex Cloud CLI построено по одному и тому же принципу: 
вначале идёт название утилиты yc;
потом указывается сервис Yandex Cloud, к которому мы обращаемся (в нашем случае это Compute Cloud, поэтому используем слово compute);
потом ресурс, с которым мы будем работать (в нашем случае это виртуальная машина — instance);
а затем действие, которое мы собираемся выполнить с этим ресурсом (в нашем случае создать — create);
после этого идут параметры и/или флаги.
В этом примере в качестве параметров мы используем:
--name demo-1 — задаём виртуальной машине имя demo-1;
--hostname demo-1 — задаём имя хоста (тоже demo-1);
Имя ВМ и имя хоста — разные вещи, хотя они часто совпадают. Первое нужно, прежде всего, для того, чтобы вы могли легко отличать свои ВМ друг от друга. При создании ВМ через консоль управления задаётся именно оно. А имя хоста используется для присвоения ВМ внутреннего FQDN (полностью определённого имени домена) — её полного адреса в облачной сети. Подробнее об этом вы можете прочитать в документации.
--metadata-from-file user-data=startup.sh — указываем, что при создании ВМ нужно взять метаданные из скрипта startup.sh;
--create-boot-disk image-folder-id=standard-images,image-family=ubuntu-2004-lts — указываем, что загрузочный диск ВМ нужно создать из стандартного образа с OC Ubuntu 20.04;
--zone ru-central1-a — определяем зону доступности, в которой будет создана ВМ;
--network-interface subnet-name=default-ru-central1-a,nat-ip-version=ipv4 — указываем, в какой подсети для ВМ будет создан сетевой интерфейс с IPv4-адресом.
В этом примере мы создадим ВМ в сети с именем default. Но если вы хотите создать ВМ в другой сети (например, skynet), то имя подсети, естественно, будет другим — skynet-ru-central1-a.     
Помните, что имена, идентификаторы и адреса ваших ресурсов во многих случаях будут отличаться от тех, что приводятся в примерах команд! Поэтому, копируя команды, подставляйте в них свои значения. В дальнейшем мы будем показывать, что именно заменить, с помощью <плейсхолдеров>. 
Выглядит это так.
Фрагмент команды с плейсхолдером:
--network-interface subnet-name=<имя_подсети>,nat-ip-version=ipv4
После подстановки своего значения (skynet-ru-central1-a):
--network-interface subnet-name=skynet-ru-central1-a,nat-ip-version=ipv4
И ещё несколько моментов, прежде чем вы вернетесь к практике.
В Windows из-за особенностей синтаксиса командных оболочек cmd и PowerShell некоторые команды Yandex Cloud CLI могут выполняться некорректно. Рекомендуем использовать альтернативные варианты: стороннюю командную оболочку (например, Git BASH), подсистемы Windows для Linux (WSL) или терминал на машине с ОС Linux / macOS.
Чтобы команды лучше читались, их разбивают на несколько строк с помощью символа \. Благодаря ему командная строка понимает, что вы вводите одну, а не несколько разных команд. В командных оболочках cmd и PowerShell вместо \ используются символы ^ и ` соответственно. Учитывайте это, если работаете в одной из них.
Подробная информация о Yandex Cloud CLI, включая справочник команд, приведена в документации.
Создайте ВМ demo-1, запустив приведённую в шаге 3 команду. Если она выполнена успешно, то в командной оболочке будет выведена информация о параметрах развёрнутой ВМ.
image
Самостоятельно измените и ещё раз запустите команду, чтобы создать другую виртуальную машину с именем ВМ и хоста demo-2.
Проверьте, что обе ВМ успешно созданы. Это можно сделать, заглянув в консоль управления.
image
А можно и с помощью командной строки. Попробуйте вывести список ВМ в каталоге, выполнив команду yc compute instance list. У вас должен получиться примерно такой результат:
+----------------------+--------+---------------+---------+----------------+-------------+
|     ID     | NAME |  ZONE ID  | STATUS | EXTERNAL IP | INTERNAL IP |
+----------------------+--------+---------------+---------+----------------+-------------+
| fhmasegntju0m722ao8e | demo-1 | ru-central1-a | RUNNING | 158.160.55.229 | 10.128.0.4 |
| fhmf61pvajnm9g3r22q7 | demo-2 | ru-central1-a | RUNNING | 158.160.47.2 | 10.128.0.26 |
+----------------------+--------+---------------+---------+----------------+-------------+ 
Статус Running в соответствующем столбце показывает, что ВМ запущены и работают.
Введите публичные IP-адреса ВМ в браузере, чтобы убедиться, что информационные страницы обоих веб-серверов доступны из интернета. Иногда эти страницы открываются не сразу — приходится немного подождать, пока на ВМ обновится список пакетов и установится NGINX.
Итак, нужные ВМ созданы. В следующей практической работе вы добавите их в целевую группу, создадите сетевой балансировщик и изучите, как он работает.
Task:
Создание балансировщика.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Итак, у вас есть виртуальные машины. Можно сразу создать и балансировщик, и целевую группу, но мы поступим иначе: сначала создадим целевую группу, затем подключим её к балансировщику.
В консоли управления откройте раздел Network Load Balancer, на вкладке Целевые группы нажмите кнопку Создать целевую группу. На открывшейся странице введите имя целевой группы (например demo-web), выберите обе ВМ, созданные на предыдущем уроке, и нажмите кнопку Создать.
Остаётся создать балансировщик. Для этого сначала создайте обработчик и настройте проверку состояния ресурсов в целевой группе:
На вкладке Балансировщики нажмите кнопку Создать сетевой балансировщик.
Заполните имя балансировщика (например lb-demo-web) и нажмите кнопку Добавить обработчик.
В открывшемся окне введите имя обработчика (например demo-web-listener). В полях Порт и Целевой порт укажите 80и нажмите кнопку Добавить.
После создания обработчика нажмите кнопку Добавить целевую группу. Укажите имя проверки состояния (например hc-demo-web), тип проверки (HTTP), порт (80), интервал отправки проверок состояния в секундах, порог работоспособности и порог неработоспособности. Оставьте указанный по умолчанию путь для проверок, используйте значения по умолчанию и для других параметров. Нажмите кнопку Применить, а затем кнопку Создать.
После создания балансировщика проверьте состояние ресурсов: в консоли управления откройте страницу балансировщика и убедитесь, что его статус — Active. Значит, балансировщик готов передавать трафик целевым ресурсам.
Перейдите на страницу балансировщика и посмотрите на блок Целевые группы. У запущенных ВМ, готовых принимать трафик, будет статус Healthy.
Введите внешний IP-адрес балансировщика в адресную строку браузера — и балансировщик перенаправит вас на одну из машин целевой группы. Обратите внимание на имя ВМ, указанное во второй строке веб-страницы.
image
Чтобы протестировать отказоустойчивость, в консоли управления перейдите в раздел Compute Cloud и остановите эту ВМ.
image
Вернитесь на страницу балансировщика и убедитесь, что статус остановленной ВМ изменился на Unhealthy. Это означает, что целевой ресурс группы не прошёл проверку состояния и не готов принимать трафик.
image
Обновите страницу с IP-адресом балансировщика, и вы увидите, что трафик перенаправлен на другую ВМ (изменилось имя ВМ, указанное во второй строке веб-страницы). 
image
После завершения работы не забудьте удалить использованные ресурсы: две ВМ и балансировщик.
Task:
Создание группы виртуальных машин.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Иногда вам требуется не автоматическое масштабирование, а автоматическое восстановление ВМ. Например, если вы отлаживаете работу веб-сервиса, который периодически падает. Для этого подойдут группы ВМ фиксированного размера. Давайте создадим и настроим такую группу.
В консоли управления откройте раздел Compute Cloud, перейдите на вкладку Группы виртуальных машин и нажмите кнопку Создать группу.
  Откроется страница Создание группы виртуальных машин.
В блоке Базовые параметры введите имя и описание группы ВМ. Создайте новый сервисный аккаунт. Чтобы иметь возможность создавать, обновлять и удалять ВМ в группе, назначьте сервисному аккаунту роль editor. По умолчанию все операции в группе ВМ выполняются от имени сервисного аккаунта.
ВМ группы могут находиться в разных зонах и регионах. В блоке Распределение выберите две зоны доступности, чтобы обеспечить доступность сервиса, если в одной из них случится сбой.
В блоке Шаблон виртуальной машины нажмите кнопку Задать.
Шаблон создается так же, как и сама ВМ. В блоке Базовые параметры введите описание шаблона конфигурации, затем в блоке Выбор образа/загрузочного диска на вкладке Операционные системы выберите Ubuntu.
В блоках Диски и Вычислительные ресурсы для загрузочного диска оставьте значения по умолчанию. В блоке Сетевые настройки выберите существующую сеть и подсети или создайте новые. В блоке Доступ выберите существующий или создайте новый сервисный аккаунт, укажите логин, вставьте в поле SSH-ключ содержимое файла с публичным ключом, доступ к серийной консоли не разрешайте.
Сохраните параметры и вы вернётесь на страницу Создание группы виртуальных машин.
В блоке В процессе создания и обновления разрешено установите политику развертывания:
Добавлять выше целевого значения (на сколько ВМ можно превышать размер группы) — 2.
Уменьшать относительно целевого значения (на сколько ВМ можно уменьшать размер группы) — 1.
Одновременно создавать (сколько ВМ можно сразу создавать в группе) — 2.
Время запуска (сколько времени должно пройти, прежде чем будут пройдены все проверки состояния и ВМ начнет получать нагрузку) — 2 минуты.
Одновременно останавливать (сколько ВМ можно сразу удалять) — 1.
Останавливать машины по стратегии — Принудительная. При принудительной стратегии Instance Groups самостоятельно выбирает, какие ВМ остановить.
В блоке Масштабирование выберите фиксированный тип, Размер (количество ВМ) — 3.
В блоке Интеграция с Load Balancer оставьте опцию Создать целевую группу выключенной. Не включайте пока проверку состояний, которая позволяет Instance Groups получать сведения о состоянии ВМ.
Нажмите кнопку Создать и вернитесь на страницу Группы виртуальных машин. В правом нижнем углу появится сообщение «Группа виртуальных машин создаётся». Одновременно можно создавать не более двух ВМ. Поэтому сначала будут созданы две ВМ, потом — третья.
После того как вы создали группу, протестируйте включение и выключение всех машин сразу. Обратите внимание: в соответствии с настройками сервис инициирует запуск не более двух машин одновременно. Третья ВМ будет оставаться остановленной. Как только первая будет запущена, один слот на запуск освободится, поэтому сразу будет инициирован запуск третьей и последней ВМ.
Task:
Автоматическое масштабирование под нагрузкой.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Давайте разберёмся, как обеспечить доступность сервиса под высокой нагрузкой. Вы уже научились создавать группы ВМ. Теперь создадим автоматически масштабируемую группу ВМ. 
В консоли управления откройте раздел Compute Cloud. Перейдите на вкладку Группы виртуальных машин и нажмите кнопку Создать группу. Задайте имя группе ВМ.
image
Создайте сервисный аккаунт. Чтобы иметь возможность создавать, обновлять и удалять ВМ в группе, назначьте сервисному аккаунту роль editor. По умолчанию все операции в группе ВМ выполняются от имени сервисного аккаунта.
В блоке Распределение выберите только одну зону доступности.
В блоке Шаблон виртуальной машины нажмите кнопку Задать. В открывшемся окне выберите:
ОС: Ubuntu 20.04.
Размер загрузочного диска: 50 ГБ.
Тип загрузочного диска: SSD.
Остальные параметры — по умолчанию. Не забудьте добавить публичный SSH-ключ. Он понадобится нам на следующем практическом занятии.
image
В блоке В процессе создания и обновления разрешено оставьте параметры по умолчанию.
Перейдите к блоку Масштабирование и выберите тип Автоматический.
image
Задайте параметры масштабирования: 
Тип автомасштабирования — зональное. При зональном автомасштабировании количество ВМ регулируется отдельно в каждой зоне доступности, указанной в настройках группы.
Минимальное количество ВМ в зоне — 2. Сервис Instance Groups не будет удалять ВМ в зоне доступности, если их там всего две.
Максимальный размер группы — 4. Instance Groups не будет создавать ВМ, если их уже четыре. В этот раз размер загрузочного диска ВМ — 50 ГБ, поэтому с учётом квот на суммарный объём SSD-дисков в одном облаке смогут запуститься четыре ВМ.
Промежуток измерения загрузки (это период усреднения: время, за которое следует усреднять замеры нагрузки для каждой ВМ в группе) — 60 секунд.
Время на разогрев ВМ — 3 минуты. В течение этого времени ВМ не учитывается в измерении средней нагрузки на группу. Фактически данное время мы можем определить, измерив, как быстро запускается ВМ.
Период стабилизации — 5 минут. Отсчитывается с момента, когда Compute Cloud принял последнее решение о том, что количество ВМ в группе нужно увеличить.
Начальный размер группы — 4. Это количество ВМ, которое следует создать вместе с группой.
В блоке Метрики укажите:
Тип — CPU.
Целевой уровень загрузки CPU, % — 80. Instance Groups будет управлять размером группы так, чтобы поддерживать указанную нагрузку CPU.
image
Нажмите кнопку Создать. Сервис начнет создавать ВМ. После создания статус группы изменится на Active. Обратите внимание, как меняются Состояния ВМ.
Creating instance — ВМ создаётся и запускается.
image
     Awaiting warmup duration — ВМ начинает принимать сетевой трафик. В этом статусе ВМ находится в течение периода прогрева, указанного в настройках автоматического масштабирования. Значения метрик ВМ в этом статусе заменяются средними значениями ВМ из той же зоны доступности.
image
Running actual — ВМ запущена, на неё подается сетевой трафик, пользовательские приложения работают.
image
  Группа ВМ готова принимать рабочую нагрузку.
Task:
Воссоздание виртуальных машин в группе.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Давайте сымитируем рост нагрузки на ВМ и посмотрим, как сервис на это отреагирует. 
В консоли управления перейдите на страницу Группы виртуальных машин. В другом окне браузера откройте вкладку Мониторинг на странице группы ВМ, которую вы создали в предыдущей практической работе.
Если эта группа была только что создана, подождите несколько минут, пока количество ВМ в ней не уменьшится до двух (это число определяется параметром Минимальное количество ВМ в зоне). Зайдите на каждую из двух ВМ и установите на них приложение для стресс-тестирования:
sudo apt-get install stress 
После этого для каждой ВМ запустите установленное приложение:
stress -c 2 
Аргумент -c означает, что при тестировании будет нагружаться процессор, а число после аргумента задаёт количество ядер процессора, которые будут нагружаться. Чтобы эксперимент удался, укажите количество ядер, которое вы выбрали в шаблоне ВМ.
На вкладке со страницей мониторинга на графике Average CPU utilization in ru-central1-a следите за тем, как усреднённое значение нагрузки будет постепенно расти.
image
Как только усреднённое значение нагрузки превысит порог, сервис Instance Groups начнёт прогревать две дополнительные ВМ и вводить их в строй. Это будет видно на странице Группы виртуальных машин.
image
Поскольку стресс-тест не остановлен, сервис завершает запуск двух ВМ.
image
Через некоторое время усреднённое значение нагрузки процессоров в группе упадёт до 50%, поскольку первая половина ВМ загружена полностью, а вторая не загружена вовсе.
image
Остановите работу стресс-теста на первой ВМ. В командной строке используйте сочетание клавиш Ctrl + C.
image
Через некоторое время усреднённое значение достигнет 25%, тогда Instance Groups удалит лишнюю ВМ:
image
Остановите второй стресс-тест. Через некоторое время после того, как усредненное значение достигнет нуля, Instance Groups удалит вторую дополнительную ВМ.
image
При минимальной нагрузке остаются работать две машины:
image
Вот так при растущей нагрузке группа ВМ автоматически масштабируется, чтобы обеспечить доступность ресурса.
Task:
Потренируемся работать с объектным хранилищем на практике. Представьте, что вы создаёте облачную систему хранения рентгеновских снимков для крупной клиники.
Рентгеновские снимки — это неструктурированные данные, которые нельзя изменять, нужно надежно хранить и легко находить.
Загруженные файлы будут скачивать нечасто. Также важно предоставлять доступ к файлам другим клиникам (это пригодится, если пациента переводят или врачу надо посоветоваться с коллегами). Объектное хранилище — подходящее решение задачи.
# Администрирование баз данных.
Decision:
Выберите на стартовой странице консоли управления сервис Object Storage.
Давайте создадим бакет для рентгеновских снимков.
Нажмите кнопку Создать бакет. Откроется окно с основными параметрами:
Имя. Придумайте его с учетом правил. Обратите внимание, что дать бакету имя hospital не получится. Имена бакетов во всем Yandex Object Storage уникальны — назвать два бакета одинаково нельзя даже в разных облаках. Помните об этом, если будете создавать бакеты автоматически.
Макс. размер. У вас есть два варианта: Выбрать опцию Без ограничения. Размер бакета будет увеличиваться, сколько бы объектов в него ни помещали. Указать максимальный размер. Это убережёт вас от финансовых потерь, если что-то пойдёт не так и в бакет загрузится слишком много объектов.
Другие опции. Далее для всех типов операций оставьте ограниченный доступ (публичный позволяет выполнять операции всем пользователям интернета), выберите стандартный класс хранилища и нажмите кнопку Создать бакет.
На странице объектного хранилища появился пустой бакет. Мы приготовили два рентгеновских снимка: image01.dat и image02.dat. Файлы можно загрузить в бакет с помощью: консоли управления; приложений; S3-совместимого HTTP API; HTML-форм на сайте.
Разберём два способа: ручную загрузку через консоль управления и автоматическую с помощью утилиты S3cmd.
Для загрузки файла через консоль управления выберите созданный бакет и в открывшемся окне нажмите кнопку Загрузить объекты.
Выберем файл image01.dat. В появившейся форме нажмите кнопку Загрузить — и вы увидите, что файл оказался в хранилище.
Загрузите второй файл с помощью утилиты S3cmd — консольного клиента для Linux и MacOS, предназначенного для работы с S3-совместимым HTTP API. Для работы в Windows используйте один из вариантов: установите другой консольный клиент для объектных хранилищ, например AWS CLI; установите подсистему Linux на Windows с помощью утилиты WSL (Windows Subsystem for Linux) и работайте с S3cmd в ней; создайте в облаке виртуальную машину с Ubuntu и работайте с S3cmd в ней. Для загрузки файла-примера в виртуальную машину воспользуйтесь командой:
tuser@kvmubuntu:~$ wget "https://disk.yandex.ru/i/2UlugGkurhcxWw" -O image02.dat
Установите S3cmd (в Ubuntu, например, это делается с помощью команды sudo apt-get install s3cmd). Теперь настройте S3cmd для работы с Yandex Object Storage:
tuser@kvmubuntu:~$ s3cmd --configure
Инструкции о настройке клиента вы найдете в документации.
После ввода параметров утилита попытается установить соединение с объектным хранилищем и в случае успеха покажет такое сообщение: Success. Your access key and secret key worked fine :-)
Загрузим в бакет второй файл (image02.dat) и затем получим список хранящихся в бакете объектов:
s3cmd put <путь к второму файлу>/image02.dat s3://<имя бакета>
s3cmd ls s3://<имя бакета>
Вернемся в консоль управления.
Мы видим, что класс хранилища у обоих объектов — стандартное. Напомним: стандартное хранилище подходит для данных, к которым обращаются часто, а тариф за размещение данных в нем примерно в два раза выше, чем в холодном хранилище.
Спустя несколько недель после того, как рентгеновский снимок сделан, к нему будут редко обращаться (если вообще будут), потому что пациент, скорее всего, выздоровеет.
Чтобы оптимизировать затраты на хранение данных, настроим жизненный цикл объектов в бакете. Создадим правило, согласно которому через 30 дней после загрузки объектов в бакет класс их хранилища будет автоматически меняться со стандартного на холодное.
Перейдите на вкладку Жизненный цикл и нажмите кнопку Настроить. Задайте произвольное описание. В поле Префикс укажите Все объекты. Выберите тип операции Transition. В качестве Условия срабатывания правила задайте Точную дату или Количество дней. В первом случае правило сработает в 00:00 установленной даты. Во втором — через указанное количество дней после загрузки объекта в бакет.
Если понадобится настроить автоматическое удаление объектов, выберите тип операции Expiration. Нажмите кнопку Сохранить.
Представим теперь, что объекты в хранилище — это оцифрованные рентгеновские снимки пациента Петрова. Первый из них (image01.dat) сделали несколько месяцев назад в ходе профосмотра, а второй (image02.dat) — вчера, после того как Петров обратился к врачу с жалобой на недомогание. В обоих случаях на снимках не увидели патологий.
Опишите с помощью пользовательских метаданных эти снимки, и позже вы быстро найдете их среди множества объектов в бакете.
С помощью утилиты S3cmd задайте для загруженных объектов метаданные с фамилией пациента (x-amz-meta-patient:petrov) и с результатами обследования (x-amz-meta-status:ok):
s3cmd modify --add-header=x-amz-meta-patient:petrov --add-header=x-amz-meta-status:ok s3://hospital/image01.dat s3://hospital/image02.dat
Выведите на экран информацию об этих объектах, чтобы проверить, что получилось:
s3cmd info s3://hospital/image01.dat s3://hospital/image02.dat
В результате вы должны увидеть информацию об объектах в бакете.
s3://hospital/image01.dat (object):
File size: 33
Last mod: Thu, 04 Mar 2021 22:05:31 GMT
MIME type: application/x-www-form-urlencoded
Storage: STANDARD
MD5 sum: 6f6d5a1cb79839e523582ed8810a42fd
SSE: none
Policy: none
CORS: none
x-amz-meta-patient: petrov
x-amz-meta-status: ok
s3://hospital/image02.dat (object):
File size: 16
Last mod: Thu, 04 Mar 2021 23:11:27 GMT
MIME type: text/plain
Storage: STANDARD
MD5 sum: 0366a1d19e584ce79d5c05ddedc69310
SSE: none
Policy: none
CORS: none
x-amz-meta-patient: petrov
x-amz-meta-status: ok
Предположим, Петров чувствует себя хуже. Судя по анализам, он действительно болен. Лечащий врач решает проконсультироваться с более опытной коллегой Ивановой из профильной клиники. Объекты в бакете недоступны для внешних пользователей, поскольку при его создании мы ограничили доступ. Чтобы Иванова увидела рентгеновский снимок Петрова, отправим ей временную ссылку на объект image02.dat.
Для этого в консоли управления кликните на объект и в открывшемся окне информации об объекте нажмите кнопку Получить ссылку. Укажите время жизни ссылки в часах или днях.
Можно поделиться ссылкой или использовать ее в любом сервисе для доступа к файлу.
После консультации Иванова поставила Петрову правильный диагноз: вирусная пневмония (код J12 по Международной классификации болезней). Вам осталось исправить метаданные объекта image02.dat. Замените значение метаданных с результатами обследования с ok на J12 самостоятельно.
Decision:
tuser@kvmubuntu:~$ wget "https://disk.yandex.ru/i/2UlugGkurhcxWw" -O image02.dat
tuser@kvmubuntu:~$ wget "https://disk.yandex.ru/i/qpl0T4u-nWPgiw" -O image01.dat
tuser@kvmubuntu:~$ ls *.dat
image01.dat image02.dat
tuser@kvmubuntu:~$ sudo apt-get install s3cmd
tuser@kvmubuntu:~$ s3cmd --configure
tuser@kvmubuntu:~$ s3cmd ls
2023-12-06 02:37 s3://klinika138
tuser@kvmubuntu:~$ s3cmd put /YOUR-DIR/image02.dat s3://klinika138
tuser@kvmubuntu:~$ s3cmd modify \
--add-header=x-amz-meta-patient:petrov \
--add-header=x-amz-meta-status:ok \
s3://klinika138/image01.dat \
s3://klinika138/image02.dat
tuser@kvmubuntu:~$ s3cmd info \
s3://klinika138/image01.dat \
s3://klinika138/image02.dat
Task:
Представьте, что вам нужно выбрать оптимальный хостинг для сайта клиники. Главные критерии: отказоустойчивый, недорогой и простой в обслуживании.
Один из вариантов решения такой задачи — использовать объектное хранилище. Вы можете, не настраивая никаких серверов, просто загрузить HTML-файлы, скрипты, стили и другие файлы в хранилище. Пользователи будут открывать в браузере ваш сайт, а по сути — скачивать файлы прямо из бакета.
Важно понимать, что этот вариант подойдет только для полностью статических сайтов. Иными словами, сайт должен быть сделан с помощью клиентских технологий (HTML, CSS и JavaScript) и не требовать запуска чего-либо на стороне веб-сервера.
Предположим, что сайт нашей клиники как раз такой — полностью статический. Опубликуйте его с помощью объектного хранилища. Прежде всего для него нужно создать бакет.
# Администрирование баз данных.
Decision:
Обратите внимание на несколько особенностей: Если вы планируете использовать собственный домен (например www.example.com), то присвойте бакету точно такое же имя. Откройте публичный доступ на чтение объектов. Это позволит пользователям интернета скачивать объекты из бакета и просматривать сайт в браузере.
Задайте необходимые настройки и нажмите кнопку Создать бакет.
Теперь загрузите в бакет файлы сайта (например, этот и этот) любым удобным способом.
Чтобы настроить хостинг, перейдите на страницу бакета в консоли управления. Выберите вкладку Веб-сайт на левой панели и включите опцию Хостинг.
Укажите файл с главной страницей сайта (как правило, это index.html), а поле со страницей ошибки можно не заполнять.
Сохраните настройки, и сайт станет доступен по адресам:
http(s)://<имя_бакета>.website.yandexcloud.net
http(s)://website.yandexcloud.net/<имя_бакета>
По умолчанию сайт будет доступен только по протоколу HTTP. Для поддержки HTTPS нужно загрузить в объектное хранилище TLS-сертификат. Вам предстоит это сделать в одной из практических работ курса «Безопасность».
Если у вас есть собственный домен и вы хотите опубликовать сайт на нём, то настройте CNAME-запись у DNS-провайдера или на своем DNS-сервере. Например, для домена www.example.com CNAME-запись выглядела бы так: www.example.com CNAME www.example.com.website.yandexcloud.net
В этом случае можно использовать домены не ниже третьего уровня (то есть использовать домен example.com не получится, только www.example.com). Это связано с особенностями обработки CNAME-записей на DNS-хостингах.
Decision:
tuser@kvmubuntu:~$ wget "https://disk.yandex.ru/d/KcpMuYBwKjIa6Q" -O index.html
tuser@kvmubuntu:~$ wget "https://disk.yandex.ru/i/uTai62_esPSaEw" -O doctor.png
tuser@kvmubuntu:~$ s3cmd put /YOUR-DIR/doctor.png s3://www.aibloit.healthcare138
tuser@kvmubuntu:~$ s3cmd put /YOUR-DIR/index.html s3://www.aibloit.healthcare138
Task:
Object Storage — удобный и полезный инструмент для хранения данных в облаке. Но для решения практических задач важно не просто хранить данные, но и иметь возможность их изменять и выполнять с ними различные операции (сортировать, группировать, делать выборки и так далее). Для этого используются базы данных. В этой и следующих темах вы научитесь работать с несколькими управляемыми БД. И начнем мы с одной из самых популярных — MySQL.
На этом уроке вы создадите и настроите кластер управляемой БД MySQL, подключитесь к нему, перенесёте данные в облако, познакомитесь с возможностями резервного копирования и мониторинга. Эти навыки пригодятся вам и в других сервисах управляемых БД, поскольку принципы работы в них очень похожи.
Предположим, вы решили добавить в разрабатываемый вами мессенджер новую функциональность. Вы написали микросервис, который позволяет оценивать сообщения в групповых чатах и хранит оценки в БД MySQL. Давайте поместим эту БД в Yandex Cloud.
# Администрирование баз данных.
Decision:
Прежде всего понадобится создать кластер: набор виртуальных машин (ВМ, или хостов), на которых будет развёрнута БД. Это обязательный первый шаг при использовании любого сервиса управляемых БД.
Войдите в консоль управления Yandex Cloud и выберите каталог для кластера. Вверху справа нажмите кнопку Создать ресурс и выберите из выпадающего списка Кластер MySQL.
Откроется страница с основными настройками кластера. Рассмотрим их подробнее.
Базовые параметры. Имя кластера может включать только цифры, прописные и строчные латинские буквы, дефисы.
Поле Описание заполнять необязательно. Оно полезно, если вам нужно создать несколько кластеров для разных целей, чтобы в них было проще ориентироваться.
О том, какое бывает Окружение кластера и чем различаются PRESTABLE и PRODUCTION, мы говорили на одном из предыдущих уроков. Поскольку микросервис только разрабатывается, выберите окружение PRESTABLE.
Версия. В качестве сервера MySQL в Yandex Cloud используется Percona Server версии 5.7 или 8.0. У этих реализаций сервера улучшенная производительность на многоядерных машинах. Если для вас критична стабильность работы микросервиса, выбирайте проверенную временем 5.7. Для нашей задачи подойдёт 8.0: в ней много новых функций, но она ещё не полностью обкатана.
Класс хостов. Следующий шаг — выбор класса хостов, или шаблона ВМ. Хосты кластера будут развёрнуты на базе ВМ Compute Cloud с использованием этого шаблона.
Платформа определяет тип физического процессора (Intel Broadwell или Intel Cascade Lake), а также конфигурации числа ядер виртуального процессора (vCPU) и размера оперативной памяти.
Если тип процессора для вас неважен, выбирайте более современную платформу Intel Cascade Lake. Она предоставляет широкий выбор конфигураций вычислительных ресурсов.
Также на конфигурации влияет тип ВМ, на которой будет развёрнута БД.
Standard — это обычные ВМ с 4 ГБ RAM на ядро vCPU. Это оптимальный баланс между количеством запущенных процессов, быстродействием и потребляемой оперативной памятью.
Memory-optimized — машины с вдвое увеличенным объёмом RAM на каждое ядро. Выбирайте их для высоконагруженных сервисов с повышенными требованиями к кешу.
Burstable — машины, для которых гарантируется использование лишь доли ядра vCPU (5, 20 или 50%) с вероятностью временного повышения вплоть до 100%. Они стоят дешевле и подходят для задач, где не нужен постоянный уровень производительности, т. е. для тестирования или разработки.
Выберем для микросервиса следующий класс хоста: платформа — Intel Cascade Lake; тип — standard; конфигурация вычислительных ресурсов — s2.micro (два ядра vCPU, 8 ГБ RAM).
Хранилище данных. Хранилище БД может быть сетевым или локальным. В первом случае данные находятся на виртуальных дисках в инфраструктуре Yandex Cloud. Локальное хранилище — это диски, которые физически размещаются в серверах хостов БД.
При создании кластера можно выбирать между следующими типами хранилища:
- Стандартное сетевое (network-hdd) — это наиболее экономичный вариант. Выбирайте его, если к скорости записи и чтения нет особых требований.
- Быстрое сетевое (network-ssd) стоит примерно в четыре раза дороже, но при размере хранилища от 100 ГБ работает быстрее стандартного в десять и более раз (чем больше размер, тем заметнее разница в скорости).
- Сетевое на нереплицируемых SSD-дисках (network-ssd-nonreplicated) — использует сетевые SSD-диски с повышенной производительностью, реализованной за счет устранения избыточности. Объём такого хранилища можно увеличивать только с шагом 93 ГБ.
- Быстрое локальное (local-ssd) — самое быстрое и дорогое. Если локальный диск откажет, все сохранённые на нём данные будут потеряны. Чтобы этого избежать, при выборе локального хранилища сервис автоматически создаст отказоустойчивый кластер минимум из трёх хостов.
При создании кластера внимательно выбирайте тип хранилища. Размер хранилища можно будет позже изменить, а тип — нет.
Выберите для кластера стандартное сетевое хранилище network-hdd размером 50 ГБ.
База данных. В этом разделе настроек задаются атрибуты базы: Имя БД, уникальное в рамках кластера, Имя пользователя (владельца БД) и Пароль пользователя.
Сеть. Здесь можно выбрать облачную сеть для кластера и группы безопасности для его сетевого трафика.
Оставьте сеть по умолчанию (default) или выберите сеть, которую создали на предыдущем курсе. Кластер будет доступен для всех ВМ, которые подключены к вашей облачной сети.
Параметры хостов. В этом блоке можно добавить количество хостов, которые будут созданы вместе с кластером, и изменить их параметры. Дополнительные хосты могут понадобиться, например, для репликации БД или снижения нагрузки на хост-мастер.
Для наших целей достаточно кластера из одного хоста. Нажмите значок редактирования параметров хоста и в открывшемся окне выберите опцию Публичный доступ. Это означает, что к хосту можно будет подключиться из интернета, а не только из облачной сети. Остальные параметры оставьте без изменений.
Дополнительные настройки.Здесь можно: указать время Начала резервного копирования и Окна обслуживания. Это пригодится, если вы хотите, чтобы резервное копирование и техобслуживание хостов кластера не совпадали с периодами пиковых нагрузок на БД; разрешить Доступ из DataLens, если вы планируете анализировать в DataLens данные из базы. Подробнее о DataLens вы узнаете на одном из следующих занятий; разрешить Доступ из консоли управления, чтобы выполнять SQL-запросы к БД из консоли управления Yandex Cloud. Отметьте этот пункт: доступ из консоли понадобится нам на следующих практических работах; разрешить Доступ из Data Transfer, чтобы разрешить доступ к кластеру из сервиса Yandex Data Transfer в Serverless-режиме; разрешить Сбор статистики, чтобы воспользоваться инструментом Диагностика производительности в кластере; установить Защиту от удаления, чтобы защитить кластер от непреднамеренного удаления пользователем.
В этом блоке также можно задать настройки БД (например используемую сервером MySQL кодировку при работе с данными и обмене информацией с клиентами). По умолчанию при создании кластера сервис выбирает оптимальные настройки. Изменяйте их, если уверены, что это необходимо.
Настройка завершена. Осталось только нажать кнопку Создать кластер.
Создание кластера займёт несколько минут. Когда он будет готов к работе, его статус на панели Managed Service for MySQL сменится с Creating на Running, а состояние — на Alive.
Статус показывает, что происходит с кластером: Creating — создаётся; Running — работает; Error — не отвечает, возникла проблема; Updating — обновляется; Stopped — остановлен; Unknown — статус неизвестен (так может быть, например, когда кластер не виден из интернета).
Состояние — это показатель доступности кластера: Alive — все хосты кластера работают; Degraded — часть хостов (один или больше) не работает; Dead — все хосты не работают.
Task:
Доступ из консоли управления. В кластере, который вы создали, уже есть БД. Она пока пустая. Поскольку при создании кластера вы выбрали в настройках пункт Доступ из консоли управления, в консоли управления Yandex Cloud появилась вкладка с интерфейсом для выполнения SQL-запросов к БД.
Давайте зайдём туда и создадим в БД таблицу для нашего микросервиса.
# Администрирование баз данных.
Decision:
На странице Managed Service for MySQL выберите строку с созданным вами кластером. В панели консоли управления перейдите на вкладку SQL. Вам будет предложено выбрать БД для SQL-запросов и имя пользователя, а также ввести пароль. Все эти атрибуты вы задавали при создании кластера.
Нажмите кнопку Подключиться. Откроется структура БД (сейчас там написано, что данных нет) и окно ввода для SQL-запросов.
Теперь создадим таблицу. Введите в окне ввода следующий запрос и нажмите кнопку Выполнить.
CREATE TABLE IF NOT EXISTS ratings (
rating_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
message_id INT NOT NULL,
rating INT NOT NULL
) ENGINE=INNODB;
Обратите внимание, что в качестве движка в сервисе управляемых БД MySQL используется только InnoDB.
В таблицу можно добавить данные с помощью команды INSERT.
INSERT INTO ratings (user_id,message_id,rating) VALUES (44,368,4);
Чтобы отобразить обновлённую структуру БД, нажмите на имя БД и выберите таблицу ratings.
Наведите указатель на заголовок столбца, чтобы увидеть тип данных в нём.
SQL-запросы через консоль управления Yandex Cloud — нетипичный способ работы с БД. Используйте его для небольших, разовых задач, когда быстрее и проще открыть подключение в браузере. Этот способ не очень удобен: текст запроса и результат его выполнения доступны, только пока вы не закрыли или не перезагрузили страницу в браузере. Конечно, если запрос успешно запущен, то сервис обработает его независимо от состояния консоли управления.
В консоли выводятся только первые 1000 строк результата запроса, даже если данных больше. Чтобы увидеть строку, введите её номер в поле Номер первой строки.
Подключение к кластеру
В основном вы будете работать с БД из приложений или из командной строки. Однако для этого нужно подключиться к хосту, на котором развёрнута БД.
Есть два варианта подключения. Если публичный доступ к хосту открыт, подключитесь к нему через интернет с помощью защищённого SSL-соединения. Если публичного доступа нет, подключитесь к хосту с виртуальной машины, созданной в той же виртуальной сети. SSL-соединение можно не использовать, но тогда трафик между виртуальной машиной и БД шифроваться не будет.
Давайте подключимся к БД через интернет и создадим в ней ещё одну таблицу. Для выполнения этого задания вы можете использовать виртуальную машину с Ubuntu.
Для создания таблицы сделаем в текстовом редакторе файл createTables.sql с командами. Например, такой:
CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT,
nickname VARCHAR(128) NOT NULL,
avatar VARCHAR(255),
mail VARCHAR(255),
PRIMARY KEY (user_id)
) ENGINE=INNODB;
Чтобы выполнить этот запрос в БД, подключимся к хосту. Для этого понадобится SSL-сертификат. Команды для его получения в Ubuntu:
mkdir ~/.mysql
wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.mysql/root.crt
chmod 0600 ~/.mysql/root.crt
Чтобы получить команды для подключения к БД, в консоли управления перейдите на страницу кластера, на вкладке Обзор нажмите кнопку Подключиться. В результате их выполнения в директории /home/<домашняя_директория>/.mysql/ сохранится SSL-сертификат root.crt.
Установите утилиту mysql-client, если на вашем компьютере или виртуальной машине её нет.
sudo apt update
sudo apt install -y mysql-client
Чтобы подключиться к БД, введите команду mysql. Для запуска нашего скрипта она выглядит следующим образом:
mysql --host=<адрес хоста> \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=<имя пользователя> \
--password \
<имя_базы_данных> < createTables.sql
Сервис помогает заполнить параметры в команде. Чтобы посмотреть пример команды с адресом хоста, именами пользователя и БД, в консоли управления перейдите на страницу кластера, на вкладке Обзор нажмите кнопку Подключиться.
После запуска команды введите пароль к БД, после чего в ней будет создана таблица users.
Если при создании кластера вы не включили публичный доступ, то к БД можно подключиться с виртуальной машины из той же облачной сети без использования шифрования. \\Следовательно, в этом случае в команде для подключения опускается параметр --ssl-ca, а --ssl-mode передаётся со значением DISABLED:
mysql --host=адрес_хоста \
--port=3306 \
--ssl-mode=DISABLED \
--user=<имя пользователя> \
--password \
<имя_базы_данных> < createTables.sql
Естественно, подключаться к БД можно не только из командной оболочки, но и из приложений. Нажмите уже знакомую вам кнопку Подключиться и посмотрите примеры кода для Python, PHP, Java, Node.js, Go, Ruby или настроек для драйвера ODBC.
Если вы хотите перенести БД в облако, то понадобится создать дамп и восстановить его в нужном кластере. Дамп — это копия БД или её части, представляющая собой текстовый файл с командами SQL (например, CREATE TABLE или INSERT). Его создают с помощью утилиты mysqldump.
Давайте попробуем перенести данные в кластер с помощью дампа. Для этого воспользуемся тестовой БД с данными о сотрудниках компании (имя, дата рождения, дата найма, место работы, зарплата и т. д.). Размер БД — около 167 Мб.
Скачайте из репозитория и сохраните на компьютере файлы с расширениями .sql и .dump. В файле employees.sql содержатся SQL команды, необходимые для создания таблиц и добавления в них данных из dump-файлов. Для переноса тестовой БД в облако понадобится запустить этот файл. Но, прежде чем приступить к переносу БД, откройте этот файл и удалите или закомментируйте (допишите в начало строки --) в нём строку 110. В этой строке расположена команда FLUSH LOGS, которая закрывает и снова открывает файлы журналов, а они в этой тестовой БД отсутствуют.
Создайте базу данных employees через консоль управления. Для этого на странице кластера перейдите на вкладку Базы данных и нажмите кнопку Добавить.
Добавьте пользователю, например user1, разрешение на доступ к БД employees. Для этого на странице кластера перейдите на вкладку Пользователи, напротив пользователя user1 нажмите кнопку ··· и выберите Настроить. Во всплывающем окне нажмите Добавить базу данных, выберите employees, добавьте роль ALL_PRIVILEGES и нажмите Сохранить.
Затем в командной строке перейдите в папку сохраненными файлами .sql и .dump и восстановите данные из дампа с помощью команды:
mysql --host=<адрес хоста> \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=<имя_пользователя> \
--password \
employees < ~/employees.sql
После того как данные скопируются, ваш кластер и БД будут готовы к работе. Подключитесь к БД в консоли управления и убедитесь, что данные перенесены.
Decision:
tuser@kvmubuntu:~$ mkdir ~/.mysql
tuser@kvmubuntu:~$ wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.mysql/root.crt
tuser@kvmubuntu:~$ chmod 0600 ~/.mysql/root.crt
tuser@kvmubuntu:~$ sudo apt update
tuser@kvmubuntu:~$ sudo apt install mysql-client
tuser@kvmubuntu:~$ vim createTables.sql
tuser@kvmubuntu:~$ cat createTables.sql
CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT,
nickname VARCHAR(128) NOT NULL,
avatar VARCHAR(255),
mail VARCHAR(255),
PRIMARY KEY (user_id)
) ENGINE=INNODB;
tuser@kvmubuntu:~$ mysql --host=rc1a-642tdtv6ope6gk7u.mdb.yandexcloud.net \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=tuser \
--password \
tdb < createTables.sql
tuser@kvmubuntu:~$ wget https://github.com/datacharmer/test_db/archive/refs/heads/master.zip
tuser@kvmubuntu:~$ unzip master.zip
tuser@kvmubuntu:~$ ls test_db-master/
Changelog   employees.sql load_dept_emp.dump load_salaries1.dump load_titles.dump sakila test_employees_md5.sql
employees_partitioned_5.1.sql images   load_dept_manager.dump load_salaries2.dump objects.sql show_elapsed.sql test_employees_sha.sql
employees_partitioned.sql load_departments.dump load_employees.dump load_salaries3.dump README.md sql_test.sh test_versions.sh
tuser@kvmubuntu:~$ cat test_db-master/employees.sql | grep flush
flush /*!50503 binary */ logs;
tuser@kvmubuntu:~$ vim test_db-master/employees.sql
tuser@kvmubuntu:~$ cat test_db-master/employees.sql | grep flush
tuser@kvmubuntu:~$ cd test_db-master/
tuser@kvmubuntu:~$ mysql --host=rc1a-642tdtv6ope6gk7u.mdb.yandexcloud.net \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=tuser1 \
--password \
employees < employees.sql
tuser@kvmubuntu:~$ mysql --host=rc1a-642tdtv6ope6gk7u.mdb.yandexcloud.net \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=tuser1 \
--password \
employees
mysql> show tables;
Task:
В этой практической работе вы создадите кластер еще одной управляемой БД, на этот раз PostgreSQL, подключитесь к ней и загрузите в нее данные.
# Администрирование баз данных.
Decision:
Создание кластера управляемой базы данных PostgreSQL аналогично созданию кластера базы данных MySQL.
Перейдите в сервис управляемых баз данных PostgreSQL и нажмите кнопку Создать кластер.
В появившемся окне настроек задайте необходимые параметры.
- Имя кластера и его описание. Выберите уникальное в облаке имя кластера. Описание опционально, поэтому можно оставить это поле пустым.
- В поле Окружение выберите PRODUCTION.
- Выберите версию PostgreSQL и класс хоста.
- Выберите размер и тип сетевого хранилища.
- Задайте атрибуты базы данных.
- Выберите из списка сеть, в которой будут находиться хосты кластера (для подключения потребуются публичные хосты).
- В блоке Хосты добавьте ещё два хоста в других зонах доступности для обеспечения отказоустойчивости кластера. База автоматически реплицируется.
- В блоке Дополнительные настройки задайте время начала резервного копирования и включите доступ из консоли управления.
- Нажмите кнопку Создать кластер.
Как и в случае с MySQL, к хостам кластера Managed Service for PostgreSQL можно подключиться двумя способами.
Через интернет. Если вы настроили публичный доступ для нужного хоста, то подключиться к нему можно с помощью SSL-соединения.
С виртуальных машин Yandex Cloud. Они должны быть расположены в той же облачной сети. Если к хосту нет публичного доступа, для подключения с таких виртуальных машин SSL-соединение использовать необязательно. Обратите внимание, что если публичный доступ в вашем кластере настроен только для некоторых хостов, автоматическая смена мастера может привести к тому, что вы не сможете подключиться к мастеру из интернета.
Установите клиент для подключения к БД PostgreSQL. Команда установки в Ubuntu: sudo apt update && sudo apt install -y postgresql-client
Скачайте сертификат для подключения к БД PostgreSQL:
mkdir -p ~/.postgresql
wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.postgresql/root.crt
chmod 0600 ~/.postgresql/root.crt
Пример команды для подключения можно посмотреть в консоли управления, нажав на кнопку Подключиться на странице кластера. Подключение с SSL происходит при помощи следующей команды:
psql "host=<FQDN_хоста> \
port=6432 \
sslmode=verify-full \
dbname=<имя базы данных> \
user=<имя пользователя базы данных> \
target_session_attrs=read-write"
Загрузка данных в базу данных из CSV. Одним из способов добавления данных в базу является их загрузка из csv-файла.
Предположим, вы используете БД для организации работы транспортной службы интернет-магазина. Вам нужно добавить в базу таблицу, содержащую данные о расстояниях между складом и пунктами самовывоза, а также о стандартном времени доставки товаров со склада в эти пункты. Создадим csv-файл, например DTM.csv, который содержит такие данные (100 - код склада, 101-109 - коды пунктов, Time - стандартное время доставки в минутах, Distance - расстояние в километрах):
"depot","store","time","distance"
"100","101",31,12
"100","102",38,17
"100","103",56,33
"100","104",70,60
"100","105",41,25
"100","106",21,8
"100","107",33,14
"100","108",62,42
"100","109",45,29
Важные моменты при миграции из CSV:
- Названия колонок в файле и в таблице необязательно совпадают.
- Файл содержит заголовок, который не нужно импортировать.
- Первые 2 колонки конвертируем из строк (string) в целые числа (int).
PostgreSQL позволяет импортировать данные из файла несколькими способами:
- Командой copy.
- Через функции pl/pgsql.
- Средствами другого языка, например Python.
Воспользуемся первым способом. Сначала нам понадобится создать таблицу, в которую будет осуществлена миграция данных. Подключитесь к БД согласно инструкциям выше. Выполните следующую команду:
CREATE TABLE dtm (
id serial PRIMARY KEY,
depot int NOT NULL,
store int NOT NULL,
time int NOT NULL,
distance int NOT NULL
);
Загрузите данные: \copy dtm(depot,store,time,distance) from '/<путь к файлу>/DTM.csv' DELIMITERS ',' CSV HEADER;
В этой команде мы учли те моменты, о которых говорили вначале:
- dtm (depot, store, time, distance) маппинг колонок связывает колонки в файле с колонками в таблице, их имена могут не совпадать
- CSV HEADER показывает, что заголовок импортировать не нужно
- Колонки в таблице уже имеют правильные типы данных, конвертация будет выполнена автоматически.
В консоли управления на странице кластера перейдите на вкладку SQL. Введите пароль пользователя БД и нажмите кнопку Подключиться. Выберите таблицу dtm, чтобы убедиться, что добавление данных выполнено правильно.
Decision:
tuser@kvmubuntu:~$ sudo apt update && sudo apt install postgresql-client
tuser@kvmubuntu:~$ mkdir -p ~/.postgresql
tuser@kvmubuntu:~$ wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.postgresql/root.crt
tuser@kvmubuntu:~$ chmod 0600 ~/.postgresql/root.crt
tuser@kvmubuntu:~$ vim DTM.csv
tuser@kvmubuntu:~$ cat DTM.csv
"deport","store","time","distance"
"100","101",31,12
"100","102",38,17
"100","103",56,33
"100","104",70,60
"100","105",41,25
"100","106",21,8
"100","107",33,14
"100","108",62,42
"100","109",45,29
tuser@kvmubuntu:~$ psql "host=rc1a-w3usdays081v0itf.mdb.yandexcloud.net,rc1c-qga7rd1sqe5jm8io.mdb.yandexcloud.net,rc1d-l1vzj1210qj68s8n.mdb.yandexcloud.net \
port=6432 \
sslmode=verify-full \
dbname=YOUR-DB \
user=YOUR-USERNAME \
target_session_attrs=read-write"
YOUR-DB=> CREATE TABLE dtm (
YOUR-DB(> id serial PRIMARY KEY,
YOUR-DB(> depot int NOT NULL,
YOUR-DB(> store int NOT NULL,
YOUR-DB(> time int NOT NULL,
YOUR-DB(> distance int NOT NULL
YOUR-DB(> );
YOUR-DB=> \copy dtm(depot,store,time,distance) from '/home/test/DTM.csv' DELIMITERS ',' CSV HEADER;
YOUR-DB=> exit
Task:
На этом уроке вы создадите кластер MongoDB, подключитесь к нему и загрузите в него данные.
Раньше вы работали только с реляционными БД, но использование кластера MongoDB принципиально не отличается от работы с кластером MySQL или PostgreSQL, так что многое будет вам знакомо.
# Администрирование баз данных.
Decision:
Выберите в консоли управления Yandex Cloud каталог для кластера БД. На дашборде каталога откройте раздел Managed Service for MongoDB. В открывшемся окне нажмите кнопку Создать кластер.
Установите основные настройки кластера. Для этого урока создайте кластер с минимальной конфигурацией: тип хоста burstable, класс b2.nano, стандартное сетевое хранилище размером 10 ГБ. Откройте публичный доступ к хосту и задайте пароль пользователя БД. Остальные значения оставьте по умолчанию.
В сервисе управляемых БД MongoDB к хостам можно подключаться через интернет или с виртуальных машин в той же сети. Порт для подключения — 27018.
Для подключения через интернет хосты кластера должны находиться в публичном доступе. Подключаться можно только через зашифрованное соединение.
Обратите внимание: если публичный доступ настроен только для некоторых хостов в кластере, то при автоматической смене основной реплики она может оказаться недоступной из интернета.
Если к хосту нет публичного доступа и вы подключаетесь к нему с виртуальных машин Yandex Cloud, то зашифрованное соединение необязательно.
Подключитесь к созданной БД из интернета. Используйте SSL-сертификат, который вы подготовили на одной из предыдущих практических работ, или команду (для Ubuntu):
sudo mkdir -p /usr/local/share/ca-certificates/Yandex && \
sudo wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
Если всё пройдет успешно — вы получите сообщение операционной системы о том, что сертификат сохранён.
Установите утилиту Mongo Shell:
sudo apt install mongodb-clients
Подключитесь к БД с помощью команды mongo. Чтобы получить строку подключения, на основной странице сервиса в консоли управления выберите кластер, на вкладке Обзор нажмите кнопку Подключиться.
Сервис сформирует пример строки подключения для кластера. Там же вы можете посмотреть примеры кода на Python, PHP, Java, Node.js, Go для подключения из приложений.
Подключитесь к кластеру из командной строки.
mongo --norc \
--ssl \
--sslCAFile /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt \
--host '<FQDN хоста MongoDB>:27018' \
-u <имя пользователя БД> \
-p <пароль пользователя БД> \
<имя БД>
Создадим в БД коллекцию users. Предположим, в ней содержится информация о пользователях вашего приложения.
db.createCollection("users")
Загрузим в коллекцию тестовые данные с помощью методов добавления одного документа db.insertOne(...) и сразу нескольких db.insertMany(...).
Сначала добавим один документ (данные одного пользователя).
db.users.insertOne({firstName: "Adam", lastName: "Smith", age: 37, email: "adam.smith@test.com"});
Дополним коллекцию данными еще двух пользователей.
db.users.insertMany( [
{firstName: "Viktoria", lastName: "Holmes", age: 73, email: "viktoria.holmes@test.com", phone: "737772727"},
{firstName: "Tina", lastName: "Anders", age: 29, email: "tina.anders@test.com", children: [{firstName: "Sam", lastName: "Anders"},{firstName: "Anna", lastName: "Anders"}]}
] );
Обратите внимание, что документы в коллекции users содержат разный набор данных. С помощью MongoDB мы можем работать с данными, структура которых частично не совпадает.
Теперь посмотрим на содержимое коллекции с помощью команды db.users.find(). Результат показывает, что все данные успешно добавлены:
Проверим, есть ли среди пользователей те, кому больше 37 лет. Сделаем запрос к БД с помощью метода find.
db.users.find({age: {$gt: 37}});
Decision:
tuser@kvmubuntu:~$ sudo mkdir -p /usr/local/share/ca-certificates/Yandex && \
sudo wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
tuser@kvmubuntu:~$ wget https://downloads.mongodb.com/linux/mongodb-linux-x86_64-enterprise-ubuntu2004-6.0.2.tgz
tuser@kvmubuntu:~$ tar -zxvf mongodb-linux-x86_64-enterprise-ubuntu2004-6.0.2.tgz
tuser@kvmubuntu:~$ sudo ln -s /path/to/the/mongodb-directory/bin/* /usr/local/bin/
tuser@kvmubuntu:~$ sudo apt install mongodb-clients
tuser@kvmubuntu:~$ mongo --norc \
--ssl \
--sslCAFile /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt \
--host 'rs01/rc1b-b7xwau9lvu3hdt0w.mdb.yandexcloud.net:27018' \
-u YOUR-USERNAME \
-p YOUR-PASSWORD \
YOUR-DB
rs01:PRIMARY> db.createCollection("users")
rs01:PRIMARY> db.users.insertOne({firstName: "Adam", lastName: "Smith", age: 37, email: "adam.smith@test.com"});
rs01:PRIMARY> db.users.insertMany( [
{firstName: "Viktoria", lastName: "Holmes", age: 73, email: "viktoria.holmes@test.com", phone: "737772727"},
{firstName: "Tina", lastName: "Anders", age: 29, email: "tina.anders@test.com", children: [{firstName: "Sam", lastName: "Anders"},{firstName: "Anna", lastName: "Anders"}]}
] );
rs01:PRIMARY> db.users.find({age: {$gt: 37}});
Task:
В этой практической работе вы создадите кластер ClickHouse. Вы уже знаете, как создавать кластеры и выставлять их основные настройки в сервисах платформы данных. Но у БД ClickHouse есть свои особенности.
Когда вы создадите кластер из двух или более хостов, сервис дополнительно создаст ещё один кластер из трёх хостов, где развернёт Apache ZooKeeper. Это служба для распределенных систем, которая управляет конфигурацией, репликацией и распределением запросов по хостам БД. Без неё кластер ClickHouse работать не будет. К ZooKeeper у пользователей доступа нет, однако его хосты учитываются при расчёте квоты ресурсов облака и стоимости сервиса.
ZooKeeper синхронизирует шарды (т. е. хосты) ClickHouse. В отличие от классических реляционных БД, у ClickHouse нет главного узла (мастера), через который добавляются данные. В ClickHouse данные можно и записывать, и читать с любого узла.
# Администрирование баз данных.
Decision:
Перейдите в каталог, где нужно создать кластер БД, выберите Managed Service for ClickHouse и нажмите кнопку Создать кластер.
Для практической работы нам понадобится кластер с минимальной конфигурацией: тип хоста burstable, класс b2.nano и стандартное сетевое хранилище размером 10 ГБ.
Задайте настройки: введите имена для кластера и БД, а также имя и пароль пользователя. Откройте публичный доступ к хосту.
Обратите внимание: в отличие от сервисов, которые мы уже рассматривали, здесь в разделе База данных можно включить опции управления пользователями и БД с помощью SQL-запросов.
Кроме того, в дополнительных настройках можно включить доступ к БД из консоли управления, сервисов DataLens, Яндекс Метрики и AppMetrica, а также возможность использовать бессерверные вычисления (подробно о них мы расскажем на курсе «Serverless»). С помощью DataLens, например, вы визуализируете результаты поисковых запросов в виде графиков, диаграмм и дашбордов, а подключение AppMetrica позволит импортировать данные из этого сервиса в кластер.
Отметьте пункт Доступ из DataLens: он понадобится вам на одном из следующих уроков. Нажмите кнопку Создать кластер.
К хостам кластера ClickHouse можно подключаться через интернет или с виртуальных машин в той же виртуальной сети. Если к хостам БД открыт публичный доступ, то для подключения к ним используется шифрованное соединение.
Подключайтесь к кластеру с помощью HTTP-протокола или более низкоуровневого Native TCP-протокола. В большинстве случаев рекомендуется взаимодействовать с ClickHouse не напрямую, а с помощью инструмента или библиотеки. Официально поддерживаются консольный клиент, драйверы JDBC и ODBC, клиентская библиотека для C++. Также можно использовать библиотеки сторонних разработчиков для Python, PHP, Go, Ruby и т. д.
Примеры строк подключения приводятся в документации и консоли управления на вкладке Обзор страницы кластера.
С БД удобно работать в приложении с графическим интерфейсом. Один из вариантов — универсальный клиент DBeaver. Другие варианты вы найдёте в полном списке клиентов.
Подробная информация о настройке подключения приведена в документации. Чтобы создать подключение к ClickHouse в DBeaver, помимо обычных параметров (адреса хоста, порта, имени БД, логина и пароля) задайте на вкладке Свойства драйвера настройки свойств драйвера JDBC. Укажите следующие параметры: ssl = true; sslmode = strict; sslrootcert = <путь к SSL-сертификату>. Как получить SSL-сертификат, вы уже узнали на предыдущих уроках.
При подключении DBeaver покажет номер версии ClickHouse и пинг до хоста.
В двух следующих практических работах мы используем кластер для аналитической работы с датасетами и для создания БД ClickHouse.
Decision:
tuser@kvmubuntu:~$ wget https://dbeaver.io/files/dbeaver-ce_latest_amd64.deb
tuser@kvmubuntu:~$ sudo dpkg -i dbeaver-ce_latest_amd64.deb
tuser@kvmubuntu:~$ dbeaver-ce &
tuser@kvmubuntu:~$ mkdir -p ~/.clickhouse-client
tuser@kvmubuntu:~$ sudo wget "https://storage.yandexcloud.net/mdb/clickhouse-client.conf.example" -O ~/.clickhouse-client/config.xml
tuser@kvmubuntu:~$ sudo apt-get install -y apt-transport-https ca-certificates dirmngr
tuser@kvmubuntu:~$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754
tuser@kvmubuntu:~$ echo "deb https://packages.clickhouse.com/deb stable main" | sudo tee \
/etc/apt/sources.list.d/clickhouse.list
tuser@kvmubuntu:~$ sudo apt-get update
tuser@kvmubuntu:~$ sudo apt-get install -y clickhouse-server clickhouse-client
tuser@kvmubuntu:~$ clickhouse-client --host rc1a-mg8yquor7pspcwkc.mdb.yandexcloud.net \
   --secure \
   --user YOUR-USERNAME \
   --database YOUR-DB \
   --port 9440 \
   --ask-password
Task:
В интернете выложено множество датасетов — структурированных наборов данных, связанных общей темой. Например в репозитории проекта Our World in Data находится около тысячи разнообразных датасетов: от численности населения государств до сведений об употреблении алкоголя в США с 1850 года.
Датасеты часто выкладывают в виде CSV- или TSV-файлов. В них значения разделены запятой (comma separated values, CSV) или табуляцией (tab separated values, TSV).
Сохраняйте датасеты в объектное хранилище и анализируйте данные с помощью ClickHouse. При этом не требуется создавать БД и копировать в неё данные из датасета. Отправляйте запросы к ClickHouse — а ClickHouse сходит за данными напрямую в объектное хранилище.
# Написание Sql запросов.
Decision:
В качестве примера возьмем датасет с историей метеонаблюдений за 10 лет и попробуем развеять мифы о разнице погоды в Москве и Санкт-Петербурге. Датасет содержит примерно 50 тысяч записей, он выложен в объектном хранилище Yandex Cloud и доступен всем.
Воспользуемся кластером БД, который мы создали на предыдущем уроке. Откройте его в консоли управления. Запросы к датасету будем делать через SQL-консоль. На панели слева выберите вкладку SQL и введите пароль пользователя. В правом поле открывшейся консоли мы и станем вводить SQL-запросы.
Как вы думаете, где зарегистрирована самая низкая температура? Наверняка в Санкт-Петербурге! Давайте проверим.
Выполните запрос:
SELECT
City,
LocalDate,
TempC
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
ORDER BY TempC ASC
LIMIT 1
Всё-таки наши интуитивные представления не всегда верны и могут опровергаться данными.
А что насчет самой высокой температуры, скорости ветра и влажности? Проверьте сами, изменив поля в запросе (средняя скорость ветра за 10 минут — WindSpeed10MinAvg, относительная влажность — RelHumidity; сортировка по возрастанию — ASC, по убыванию — DESC). Увеличив количество выводимых данных, вы получите более точное представление (измените параметр LIMIT c 1 до 10).
Но это были крайние значения. Давайте проверим, насколько в этих городах отличается климат в целом. Узнаем, например, разницу среднегодовых температур.
SELECT
Year,
msk.t - spb.t
FROM
(
SELECT
toYear(LocalDate) AS Year,
avg(TempC) AS t
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
WHERE City = 'Moscow'
GROUP BY Year
ORDER BY Year ASC
) AS msk
INNER JOIN
(
SELECT
toYear(LocalDate) AS Year,
avg(TempC) AS t
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
WHERE City = 'Saint-Petersburg'
GROUP BY Year
ORDER BY Year ASC
) AS spb ON msk.Year = spb.Year
Измените поля в запросе, чтобы проверить разницу относительной влажности.
Давайте теперь рассчитаем, где раньше начинается лето. Будем считать началом лета день, начиная с которого температура поднималась выше +15 °С хотя бы пять раз в течение 10-дневного периода (864 тысячи секунд).
SELECT
City,
toYear(LocalDate) AS year,
MIN(LocalDate)
FROM
(
SELECT
City,
LocalDate,
windowFunnel(864000)(LocalDateTime, TempC >= 15, TempC >= 15, TempC >= 15, TempC >= 15, TempC >= 15) AS warmdays
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
GROUP BY
City,
LocalDate
)
WHERE warmdays = 5
GROUP BY
year,
City
ORDER BY
year ASC,
City ASC
Task:
Предположим, вы работаете в метеорологической службе и постоянно изучаете датасеты с погодными данными.
Сбор данных о погоде автоматизирован: на территории области расположены несколько десятков пунктов наблюдения с датчиками.
Информация о температуре, давлении, влажности и скорости ветра раз в полчаса передаётся с датчиков на центральный сервер.
Приложение на сервере обрабатывает данные, переводит их в нужный формат и записывает в файл. Каждый файл содержит данные за три часа наблюдений.
Для прогноза нужно учитывать всю историю наблюдений за последние несколько лет, то есть все файлы потребуется собрать в одну БД.
Давайте потренируемся добавлять данные из файлов в БД ClickHouse.
На предыдущих уроках мы создали кластер, на котором развёрнута БД, и научились подключаться к нему.
Продолжим работать с этой БД, а в качестве добавляемого файла возьмем уже известный вам датасет с данными о погоде в Москве и Санкт-Петербурге.
# Написание Sql запросов.
Decision:
Сохраните файл на компьютере.
Прежде чем добавлять файл в БД, создадим в ней таблицу, куда будут вставляться данные. Перейдите в SQL-консоль кластера и выполните команду:
CREATE TABLE <имя вашей БД>.Weather
( LocalDateTime DateTime,
LocalDate Date,
Month Int8,
Day Int8,
TempC Float32,
Pressure Float32,
RelHumidity Int32,
WindSpeed10MinAvg Int32,
VisibilityKm Float32,
City String
) ENGINE=MergeTree
ORDER BY LocalDateTime;
В результате будет создана пустая таблица с полями и типами данных, соответствующими полям и типам данных в нашем файле (датасете).
Вставим данные в таблицу с помощью клиента командной строки clickhouse-client. Команды для его установки (для Ubuntu):
sudo apt update && sudo apt install --yes apt-transport-https ca-certificates dirmngr && \
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E0C56BD4 && \
echo "deb https://repo.clickhouse.com/deb/stable/ main/" | sudo tee \
/etc/apt/sources.list.d/clickhouse.list
sudo apt update && sudo apt install --yes clickhouse-client
mkdir --parents ~/.clickhouse-client && \
wget "https://storage.yandexcloud.net/mdb/clickhouse-client.conf.example" \
--output-document ~/.clickhouse-client/config.xml
Подробности о том, как установить клиент и работать с ним, вы найдёте в документации ClickHouse.
Подключитесь к кластеру. Пример строки подключения посмотрите в консоли управления.
Добавим файл с данными в БД с помощью команды:
cat weather_data.tsv | clickhouse-client \
--host <адрес вашей БД> \
--secure \
--user user1 \
--database db1 \
--port 9440 \
-q "INSERT INTO db1.Weather FORMAT TabSeparated" \
--ask-password
Переключившись в SQL-консоль, вы увидите, что данные появились в таблице.
Данные в БД можно загружать и другими способами: из приложений или клиентов с графическим интерфейсом (например DBeaver). В этом случае подключение к БД и передача данных будут идти по HTTP-протоколу через порт 8443.
Теперь вы можете анализировать 10-летний срез данных о погоде в Москве и Санкт-Петербурге непосредственно в ClickHouse, без обращений к внешним источникам. Попробуйте, например, выяснить, какой день был самым ветреным в этих городах.
После практической работы остановите кластер, но не удаляйте его. Кластер ещё понадобится, когда мы будем рассматривать сервис визуализации и анализа данных Yandex DataLens.
Decision:
tuser@kvmubuntu:~$ wget https://storage.yandexcloud.net/arhipov/weather_data.tsv
tuser@kvmubuntu:~$ cat weather_data.tsv | clickhouse-client \
--host YOUR-IP \
--secure \
--user YOUR-USERNAME \
--database YOUR-DB \
--port 9440 \
-q "INSERT INTO YOUR-DB.Weather FORMAT TabSeparated" \
--ask-password
Task:
В этой практической работе вы создадите БД YDB в dedicated режиме, научитесь подключаться к ней и добавлять данные из тестового приложения.
Также вы подключитесь к БД и запустите тестовое приложение, чтобы создать в ней несколько таблиц с данными о популярных сериалах.
# Администрирование баз данных.
Decision:
На стартовой странице консоли управления перейдите в каталог, в котором будете создавать БД, выберите в списке сервисов База данных YDB и нажмите кнопку Создать ресурс.
В открывшемся окне выберите тип БД dedicated. Появившийся интерфейс создания новой БД практически идентичен уже знакомым вам интерфейсам создания кластеров управляемых БД.
Выберите для вашей БД имя, назначьте необходимые вычислительные ресурсы (для этой и следующих практических работ достаточно одного хоста конфигурации medium), тип и количество групп хранения (достаточно одной группы).
Группа хранения – это массив независимых дисковых накопителей, объединённых по сети в единый логический элемент. В YDB такой массив состоит из 9 дисков, расположенных по три в каждой из трёх зон доступности. Такая конфигурация обеспечивает устойчивость при одновременном отказе одной из зон и отказе диска в другой зоне. Стандартный размер группы хранения — 100 ГБ.
Выберите облачную сеть и подсети для работы с БД. Вы можете оставить сеть по умолчанию или выбрать ту, которую создали на предыдущем курсе. БД будет доступна для всех виртуальных машин, которые подключены к той же облачной сети.
Также выберите опцию присвоения публичного IP-адреса, чтобы иметь возможность подключаться к БД из интернета.Нажмите кнопку Создать базу данных. Создание БД занимает несколько минут. Когда статус БД изменится с Provisioning на Running, она готова к работе. Кликнув на созданную БД в консоли управления, вы перейдёте на вкладку Обзор.
В разделе Соединение на этой странице приведена информация, которая вам понадобится для подключения к БД:
- Эндпоинт — точка подключения с указанием протокола, представляющая собой в данном случае адрес, на который посылаются сообщения;
- Размещение базы данных — полный путь к БД.
- Примеры подключений из командной строки и приложений вы можете посмотреть, нажав на кнопку Подключиться.
Подключение к базе данных и запуск тестового приложения.
Для того, чтобы выполнить эту задачу, вам понадобится сервисный аккаунт с ролями viewer и editor. Перейдите в дашборд каталога и выберите вкладку Сервисные аккаунты. Создайте сервисный аккаунт, назначив для него указанные роли. Сохраните идентификатор этого аккаунта.
Вы можете запускать тестовое приложение со своего компьютера или с виртуальной машины в Yandex Cloud. В данном примере используется OC Ubuntu и приложение на Python.
Если при создании БД вы не присвоили ей публичный IP-адрес, то подключиться к ней вы сможете только с виртуальной машины, расположенной в той же облачной сети.
Для запуска приложения нужно склонировать на свою машину репозиторий YDB Python SDK, из которого оно будет вызываться, а также установить библиотеки ydb, iso8601 и yandexcloud. Воспользуйтесь для этого следующими командами:
git clone https://github.com/yandex-cloud/ydb-python-sdk.git
sudo pip3 install iso8601 ydb yandexcloud
Создайте авторизованный ключ для вашего сервисного аккаунта и сохраните его в файл с помощью интерфейса командной строки Yandex Cloud.
mkdir ~/.ydb
yc iam key create \
--folder-id <идентификатор каталога> \
--service-account-name <имя сервисного аккаунта> \
--output ~/.ydb/sa_name.json
Получите SSL-сертификат:
wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" \
-O ~/.ydb/CA.pem
Установите переменную окружения YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS и переменную окружения с SSL-сертификатом.
export YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS=~/.ydb/sa_name.json
export YDB_SSL_ROOT_CERTIFICATES_FILE=~/.ydb/CA.pem
Запустите тестовое приложение basic_example_v1 из репозитория ydb-python-sdk, указав в качестве параметров подключения значения протокола, эндпоинта и полного пути к БД.
cd ./ydb-python-sdk/examples/basic_example_v1
python3 __main__.py \
-e <Эндпоинт> \
-d <Размещение базы данных>
Результат выполнения приложения должен выглядеть так:
> describe table: series
column, name: series_id , Uint64
column, name: title , Utf8
column, name: series_info , Utf8
column, name: release_date , Uint64
> select_simple_transaction:
series, id: 1 , title: IT Crowd , release date: b'2006-02-03'
> bulk upsert: episodes
> select_prepared_transaction:
episode title: To Build a Better Beta , air date: b'2016-06-05'
> select_prepared_transaction:
episode title: Bachman's Earnings Over-Ride , air date: b'2016-06-12'
> explicit TCL call
> select_prepared_transaction:
episode title: TBD , air date: b'2022-08-24'
Вернитесь в консоль управления Yandex Cloud, чтобы посмотреть на результаты работы приложения. Переключитесь на вкладку Навигация.
В вашей БД созданы три таблицы: episodes, seasons и series с информацией о двух популярных сериалах IT Crowd и Silicon Valley. Кликнув по названию таблицы, вы увидите содержащиеся в ней данные. А если подвести к названию таблицы курсор и кликнуть на значок «информация» справа, то внизу появится дополнительное окно с вкладками Обзор, Схема и Партиции.
Кнопка Создать на панели Навигация служит для создания директорий и таблиц. С её помощью можно создать новую таблицу, не прибегая к командам YQL.
Decision:
tuser@kvmubuntu:~$ git clone https://github.com/yandex-cloud/ydb-python-sdk.git
tuser@kvmubuntu:~$ mkdir ~/.ydb
tuser@kvmubuntu:~$ yc iam key create \
--service-account-name sa-ydb \
--output ~/.ydb/authorized_key.json
tuser@kvmubuntu:~$ export YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS=~/.ydb/authorized_key.json
tuser@kvmubuntu:~$ cd ydb-python-sdk/examples/basic_example_v1
tuser@kvmubuntu:~$ python3 __main__.py \
-e <Эндпоинт> \
-d <Размещение базы данных>
Task:
В этом уроке вы освоите базовый набор операций для работы с данными с использованием YQL и консоли управления Yandex.Cloud.
# Написание Sql запросов.
Decision:
Чтобы начать, войдите в раздел Навигация консоли управления и откройте редактор SQL, нажав на кнопку SQL-запрос.
На прошлом уроке мы уже создали в нашей БД три таблицы, содержащие информацию о сериалах IT Crowd и Silicon Valley.
Добавим в БД еще одну таблицу с рейтингами эпизодов сериала IT Crowd на IMDb.com.
YQL является диалектом SQL, поэтому многие инструкции в этих языках идентичны.
Для создания таблицы вам понадобится сделать запрос к БД, содержащий инструкцию CREATE TABLE. Например, если бы мы хотели создать таблицу seasons (она уже есть в вашей БД), то SQL запрос выглядел бы следующим образом:
CREATE TABLE seasons
(
series_id Uint64,
season_id Uint64,
first_aired Date,
last_aired Date,
title Utf8,
PRIMARY KEY (series_id, season_id)
);
Обратите внимание, что в пределах директории YDB имена таблиц должны быть уникальны. Первичный ключ (PRIMARY KEY) — это столбец или комбинация столбцов, однозначно идентифицирующих каждую строку в таблице. Он может содержать только неповторяющиеся значения. Для таблицы YDB указание первичного ключа обязательно, при этом он может быть только один.
Первичный ключ по сути является первичным индексом, который помогает СУБД быстрее обнаруживать отдельные записи в таблице и сокращает время выполнения запросов. Также в таблицу можно добавить один или несколько вторичных индексов. Они служат той же цели, но в отличие от первичного индекса могут содержать повторяющиеся значения. Добавить вторичные индексы можно в любой момент, когда возникнет необходимость, и это не вызовет деградацию производительности БД. Чтобы при создании таблицы добавить в нее вторичный индекс, используется такая конструкция:
INDEX <имя индекса> GLOBAL ON (<имя столбца1>, <имя столбца2>, ...)
Вторичный индекс можно добавить и в уже существующую таблицу. Работа БД при этом не прерывается. В отличие от предыдущего случая в существующую таблицу можно добавлять только один вторичный индекс за раз. Делается это с помощью следующей команды:
ALTER TABLE <имя таблицы> ADD INDEX <имя индекса> GLOBAL ON (<имя столбца>);
Добавим в эту таблицу данные. Для вставки данных в YDB помимо обычной SQL инструкции INSERT также используются инструкции REPLACE и UPSERT.
При выполнении INSERT перед операцией записи выполняется операция чтения данных. Это позволяет убедиться, что уникальность первичного ключа будет соблюдена. При выполнении инструкций REPLACE и UPSERT осуществляется слепая запись.
Инструкции REPLACE и UPSERT используются для добавления новой или изменения существующей строки по заданному значению первичного ключа. При операциях записи и изменения данных использование этих инструкций эффективнее.
Если при выполнении этих инструкций строка с указанным значением первичного ключа не существует, то она будет создана. Если же такая строка существует, то значения ее столбцов будут заменены на новые. Отличие между REPLACE и UPSERT заключается в том, что первая из этих инструкций устанавливает значения столбцов, не участвующих в операции, в значения по умолчанию, а вторая такие значения не меняет.
Одним запросом REPLACE, UPSERT или INSERT можно вставить в таблицу несколько строк.
Например, если бы мы хотели добавить в таблицу series те данные, которые в ней сейчас содержатся, то SQL запрос выглядел бы так:
REPLACE INTO series (series_id, title, release_date, series_info)
VALUES
(
1,
"IT Crowd",
Date("2006-02-03"),
"The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry."),
(
2,
"Silicon Valley",
Date("2014-04-06"),
"Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley."
);
C помощью SQL запросов можно добавлять и удалять не только строки таблицы, но и столбцы. Для этого используется команда ALTER TABLE и фразы ADD COLUMN и DROP COLUMN.
Например, если вы хотите добавить в таблицу ratings столбец viewed с данными о том, какие эпизоды сериала вы уже посмотрели, то это можно сделать с помощью следующей команды.
ALTER TABLE ratings ADD COLUMN viewed Bool;
Теперь потренируемся извлекать данные из БД. Для этого используется команда SELECT. В простейшем случае ее синтаксис выглядит так:
SELECT <имя столбца1>, <имя столбца2>, ...
FROM <имя таблицы>;
Например, чтобы выбрать всю информацию из таблицы seasons, нужно сделать следующий запрос к БД.
SELECT * FROM seasons;
Если нужно выбрать из таблицы только те строки, которые удовлетворяют определенному условию, в запросе используют секцию WHERE. В этой секции должно находиться выражение, возвращающее логический результат. Обычно оно состоит из логических операций and, or, not и операций сравнения.
Например, выбрать из таблицы episodes только первые эпизоды всех сезонов можно так:
SELECT * FROM episodes
WHERE episode_id = 1;
Запрос SELECT извлекает строки без определенного порядка. Чтобы отсортировать полученные данные нужным образом, в этот запрос включают секцию ORDER BY. В ней указывается список столбцов, которые будут определять порядок сортировки результатов запроса.
Для получения обобщённых сведений о содержащихся в таблице данных — например, о числе строк в таблице или среднем значении какого-либо выражения — в запрос SELECT включают агрегатные функции и секцию GROUP BY. Эта секция используется для агрегации внутри каждого ключа. Ключом является значение одной или более колонок, указанных в GROUP BY.
Примеры агрегатных функций:
COUNT(*) — вычисляет число строк в таблице.
MAX(expr) — находит максимум выражения expr по всем строкам.
SUM(expr) — суммирует выражение expr по всем строкам. Тип выражения должен быть числовым.
AVG(expr) — находит среднее значение выражения expr по всем строкам. Тип выражения должен быть числовым или интервалом.
SOME(expr) — возвращает одно произвольное значение выражения по всем строкам.
Результаты выполнения агрегатной функции выводятся в отдельном столбце. Чтобы задать этому столбцу имя, используют оператор AS. Конструкция может выглядеть, например, так:
SELECT
<имя столбца1>,
MAX(<имя столбца2>) AS max_value
...;
В реляционной БД таблицы логически связаны друг с другом. С помощью объединений (JOIN) можно получить данные из нескольких связанных друг с другом таблиц и представить их в виде одной результирующей таблицы.
Столбцы, по которым выполняется объединение, можно указать одним из двух способов.
- После ключевого слова USING, например table1 AS a JOIN table2 AS b USING (foo). Это более короткий способ записи, удобный для простых случаев. Имена столбцов, по которым происходит объединение таблиц, должны быть одинаковы.
- После ключевого слова ON (например, a JOIN b ON a.foo = b.bar). Этот способ позволяет использовать разные имена столбцов и указывать дополнительные условия по аналогии с WHERE.
Поскольку такие запросы затрагивают столбцы разных таблиц, имена столбцов должны содержать и имя таблицы (то есть, например, не просто series_id, а seasons.series_id).
В YDB доступны следующие логические типы объединений:
INNER (используется по умолчанию) — строки попадают в результат, только если значение ключевых колонок присутствует в обеих таблицах;
FULL, LEFT и RIGHT — при отсутствии значения в обеих или в одной из таблиц включает строку в результат, но оставляет пустыми (NULL) колонки, соответствующие противоположной таблице.
LEFT/RIGHT SEMI — одна сторона выступает как белый список (whitelist) ключей, её значения недоступны. В результат включаются столбцы только из одной таблицы, декартового произведения не возникает;
LEFT/RIGHT ONLY — вычитание множеств по ключам (blacklist). Практически эквивалентно добавлению условия IS NULL на ключ противоположной стороны в обычном LEFT/RIGHT, но, как и в SEMI, нет доступа к значениям;
CROSS — декартово произведение двух таблиц целиком без указания ключевых колонок, секция с ON/USING явно не пишется;
EXCLUSION — обе стороны минус пересечение.
Простой пример запроса с объединением таблиц приведен ниже.
SELECT
sa.title AS season_title,
sr.title AS series_title,
sr.series_id, sa.season_id
FROM seasons AS sa
INNER JOIN series AS sr ON sa.series_id = sr.series_id
WHERE sa.season_id = 1
ORDER BY sr.series_id;
Этот запрос извлекает из таблиц series и seasons сведения о первых сезонах всех сериалов и выводит объединённые данные в результирующей таблице.
Task:
создайте таблицу ratings, в которой будут содержаться рейтинги всех эпизодов сериала IT Crowd, со столбцами season_id (Uint64), episodes_id (Uint64), title (Utf8), air_date (Date) и imdb_rating (Uint64) и вторичным индексом rating_index по полю imdb_rating.
# Написание Sql запросов.
Decision:
CREATE TABLE ratings (
season_id Uint64,
episodes_id Uint64,
title Utf8,
air_date Date,
imdb_rating Uint64,
PRIMARY KEY (season_id, episodes_id),
INDEX rating_index GLOBAL ON (imdb_rating)
);
Task:
добавьте в таблицу ratings данные из этого файла.
# Написание Sql запросов.
Decision:
REPLACE INTO ratings (season_id, episodes_id, title, air_date, imdb_rating) VALUES
(1, 1, "Yesterday's Jam", Date("2006-02-03"), 76),
(1, 2, "Calamity Jen", Date("2006-02-03"), 82),
(1, 3, "Fifty-Fifty", Date("2006-02-10"), 79),
(1, 4, "The Red Door", Date("2006-02-17"), 80),
(1, 5, "The Haunting of Bill Crouse", Date("2006-02-24"), 85),
(1, 6, "Aunt Irma Visits", Date("2006-03-03"), 81),
(2, 1, "The Work Outing", Date("2006-08-24"), 95),
(2, 2, "Return of the Golden Child", Date("2007-08-31"), 82),
(2, 3, "Moss and the German", Date("2007-09-07"), 82),
(2, 4, "The Dinner Party", Date("2007-09-14"), 87),
(2, 5, "Smoke and Mirrors", Date("2007-09-21"), 78),
(2, 6, "Men Without Women", Date("2007-09-28"), 76),
(3, 1, "From Hell", Date("2008-11-21"), 78),
(3, 2, "Are We Not Men?", Date("2008-11-28"), 85),
(3, 3, "Tramps Like Us", Date("2008-12-05"), 82),
(3, 4, "The Speech", Date("2008-12-12"), 90),
(3, 5, "Friendface", Date("2008-12-19"), 85),
(3, 6, "Calendar Geeks", Date("2008-12-26"), 78),
(4, 1, "Jen The Fredo", Date("2010-06-25"), 80),
(4, 2, "The Final Countdown", Date("2010-07-02"), 84),
(4, 3, "Something Happened", Date("2010-07-09"), 75),
(4, 4, "Italian For Beginners", Date("2010-07-16"), 82),
(4, 5, "Bad Boys", Date("2010-07-23"), 84),
(4, 6, "Reynholm vs Reynholm", Date("2010-07-30"), 76);
Task:
Вы решили, что столбец с датой выхода эпизодов в таблице ratings не нужен, поскольку эта информация уже содержится в другой таблице. Удалите столбец air_date из таблицы ratings.
# Написание Sql запросов.
Decision:
ALTER TABLE ratings DROP COLUMN air_date;
Task:
получите список самых популярных (с рейтингом не менее 85) эпизодов сериала IT Crowd. При поиске используйте созданный ранее вторичный индекс rating_index. Чтобы упорядочить результаты по убыванию рейтинга используйте конструкцию ORDER BY … DESC.
# Написание Sql запросов.
Decision:
SELECT
season_id,
episodes_id,
title,
imdb_rating
FROM ratings VIEW rating_index
WHERE
imdb_rating >= 85
ORDER BY
imdb_rating DESC;
Task:
Напишите SQL запрос к таблице episodes, который выводит данные о числе эпизодов каждого сериала.
Вам понадобится вычислить число строк для каждого значения столбца series_id и сгруппировать результаты по series_id.
# Написание Sql запросов.
Decision:
SELECT
series_id,
COUNT(*) AS total_episodes
FROM episodes
GROUP BY
series_id
ORDER BY
series_id;
Task:
Напишите SQL запрос, с помощью которого можно сравнить популярность сезонов сериала IT Crowd.
Вам понадобится вычислить средний рейтинг эпизодов для каждого сезона и сгруппировать результаты по столбцу season_id.
# Написание Sql запросов.
Decision:
SELECT
season_id,
AVG (imdb_rating) AS avg_rating
FROM ratings
GROUP BY season_id
ORDER BY avg_rating DESC;
Task:
напишите запрос, который выводит таблицу, содержащую название сериала IT Crowd и названия всех его эпизодов (то есть, каждая строка итоговой таблице должна содержать название сериала и название отдельного эпизода).
# Написание Sql запросов.
Decision:
SELECT
sr.title AS series_title,
ep.title AS episode_title,
ep.season_id, 
ep.episode_id
FROM
series AS sr
INNER JOIN
episodes AS ep
ON sr.series_id = ep.series_id
WHERE sr.series_id = 1
ORDER BY
ep.season_id, 
ep.episode_id;
Task:
На этом уроке вы создадите и настроите кластер Hadoop с помощью сервиса Yandex Data Proc.
Hadoop предназначается для работы с большими данными, поэтому создание кластера потребует от вас больше усилий, чем на предыдущих практических работах (но гораздо меньше, чем если бы вы делали это самостоятельно).
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Для хранения зависимостей заданий нашего кластера и результатов их выполнения нужно предварительно создать бакет в объектном хранилище. О том, как это сделать, мы рассказывали на одном из предыдущих занятий.
Также создайте сервисный аккаунт для доступа к кластеру. Обратите внимание: можно использовать только аккаунт с ролью mdb.dataproc.agent. Для автоматического масштабирования кластера сервисному аккаунту также понадобятся роли editor и dataproc.agent.
Откройте каталог, где будете создавать кластер, и выберите сервис Data Proc.
В открывшемся окне нажмите кнопку Создать кластер.
Задайте для кластера имя и выберите версию образа — 1.4. В образ включена одна из версий Hadoop и дополнительные компоненты. Некоторые вы можете устанавливать по выбору. Кроме того, в каждую версию образа входит Conda (менеджер окружений для Python) и набор инструментов машинного обучения (scikit-learn, TensorFlow, CatBoost, LightGBM и XGBoost).
Обратите внимание на то, что некоторые из сервисов обязательны, чтобы использовать другие. На следующем уроке нам понадобится сервис HIVE. Выберите его, и рядом с MAPREDUCE и YARN вы увидите напоминания о том, что они нужны для HIVE.
Вставьте в поле публичный ключ публичную часть SSH-ключа. Как сгенерировать и использовать SSH-ключи, мы рассказывали в одной из практических работ о виртуальных машинах.
Выберите созданный сервисный аккаунт для доступа к кластеру.
Выберите зону доступности для кластера. Все подкластеры будут находиться в этой
Если нужно, задайте свойства Hadoop и его компонентов. Доступные свойства перечислены в документации.
Выберите бакет в объектном хранилище, где будут храниться зависимости заданий и результаты их выполнения.
Выберите или создайте сеть для кластера. Включите опцию NAT в интернет для подсетей, в которых размещается кластер.
Если нужно, создайте группу безопасности. Правила для неё вы добавите позже в сервисе Virtual Private Cloud.
Включите опцию UI Proxy, чтобы получить доступ к веб-интерфейсам компонентов Data Proc. У некоторых компонентов (например Hadoop, Spark, YARN и Zeppelin) есть пользовательские веб-интерфейсы, доступные на мастер-узле кластера. С помощью этих интерфейсов вы можете:
- отслеживать ресурсы кластера и управлять ими (YARN Resource Manager, HDFS NameNode);
- просматривать статус и отлаживать задания (Spark History, JobHistory);
- проводить эксперименты, совместно работать или выполнять отдельные операции (Zeppelin).
Настройка подкластеров. В состав кластера входит один главный подкластер (Мастер) с управляющим хостом, а также подкластеры для хранения данных (Data) или вычислений (Compute).
В подкластерах Data можно разворачивать компоненты для хранения данных, а в подкластерах Compute — для обработки данных. Хранилище в подкластере Compute предназначено только для временного хранения обрабатываемых файлов.
Для каждого подкластера можно задать число и класс хостов, размер и тип хранилища, а также подсеть той сети, в которой расположен кластер. Кроме того, для подкластеров Compute можно настроить автоматическое масштабирование. Это позволит выполнять задания на обработку данных быстрее без дополнительных усилий с вашей стороны.
Создадим подкластер Compute с одним хостом.
В блоке Добавить подкластер нажмите кнопку Добавить.
В поле Роли выберите COMPUTENODE. В блоке Масштабирование включите опцию Автоматическое масштабирование.
Все открывшиеся настройки знакомы вам из практических работ по созданию виртуальных машин.
Автоматическое масштабирование подкластеров обработки данных поддерживается в кластерах Yandex Data Proc версии 1.2 и выше. Чтобы оно работало, в кластере с установленным Spark или Hive должен быть также установлен сервис YARN.
Yandex Data Proc автоматически масштабирует кластер, используя для этого системные метрики нагрузки на кластер. Когда их значение выходит из установленного диапазона, запускается масштабирование. Если значение метрики превысит порог, в подкластер добавятся хосты. Если опустится ниже порога, начнётся декомиссия (высвобождение ненужных ресурсов), а избыточные хосты удалятся.
По умолчанию для масштабирования используется внутренняя метрика YARN (yarn.cluster.containersPending). Она показывает, сколько единиц ресурсов нужно заданиям в очереди. Выбирайте эту опцию Масштабирование по умолчанию, если в кластере выполняется много относительно небольших заданий.
Другой вариант — масштабирование на основе метрики загрузки процессора (vCPU). Чтобы использовать его, отключите опцию Масштабирование по умолчанию и укажите целевой уровень загрузки vCPU.
Настроив подкластеры, нажмите кнопку Создать кластер.
Сервис запустит создание кластера. После того как статус кластера изменится на Running, вы сможете подключиться к любому активному подкластеру с помощью указанного в настройках SSH-ключа.
Task:
На этом уроке вы научитесь подключаться к кластеру Hadoop и работать с ним на примере выполнения запросов с помощью Hive.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Подключимся к управляющему хосту главного подкластера. Поскольку хостам кластера Hadoop не назначается публичный IP-адрес, для подключения к ним нужна виртуальная машина, расположенная в той же сети Yandex Cloud.
Выберите машину, которую создавали раньше, или создайте новую. Подключитесь к ней по SSH. Вы уже делали это, когда изучали виртуальные машины.
Подключитесь с этой машины к хосту главного подкластера также с помощью SSH. Для этого на машине должна быть закрытая часть SSH-ключа, открытую часть которого вы указали при создании кластера Data Proc. Вы можете скопировать ключ на виртуальную машину или подключаться к ней с запущенным SSH-агентом.
Скопировать ключ можно с помощью утилиты nano. На виртуальной машине выполните команду:
sudo nano ~/.ssh/<имя ключа>
В открывшийся редактор скопируйте содержимое закрытой части SSH-ключа с вашей локальной машины.
Запустите SSH-агент:
eval `ssh-agent -s`
Добавьте ключ в список доступных агенту:
ssh-add ~/.ssh/<имя ключа>
Узнайте внутренний FQDN хоста главного подкластера. Для этого в консоли управления на странице кластера перейдите на вкладку Хосты и выберите хост с ролью MASTERNODE.
Откройте SSH-соединение с хостом Data Proc для пользователя root, например:
ssh root@<FQDN хоста>
Пошаговые инструкции по различным способам подключения к кластеру Data Proc приведены в документации.
Проверим, что команды Hadoop выполняются, например:
hadoop version
Результат выполнения этой команды выглядит так:
Запуск заданий Apache Hive
Как мы уже говорили ранее, Hive — это платформа для хранения данных и управления ими в экосистеме Hadoop. Она используется для доступа к большим датасетам, сохранённым в распределённом хранилище.
Hive позволяет работать с данными различного формата (csv, tsv, Parquet, ORC, Avro и другими), подключаться к БД и взаимодействовать с ней с помощью SQL-подобного языка запросов. Hive используется преимущественно для работы с данными в HDFS, HBase, S3-совместимых хранилищах и реляционных СУБД.
Запрос на действия с данными в Hive называется заданием. Задания можно запускать на управляющем хосте с помощью командной оболочки CLI Hive, а также с помощью CLI Yandex Cloud.
Для запуска Hive CLI выполните команду hive на управляющем хосте.
Проверьте, всё ли работает: выполните, например, команду select 1; — корректный результат выглядит так:
Теперь создайте внешнюю таблицу (external table) в формате Parquet, содержащую открытые данные о списке перелётов между городами США в 2018 году. Для этого с помощью Hive CLI выполните запрос:
hive> CREATE EXTERNAL TABLE flights (Year bigint, Month bigint, FlightDate string, Flight_Number_Reporting_Airline bigint, OriginAirportID bigint, DestAirportID bigint) STORED AS PARQUET LOCATION 's3a://yc-mdb-examples/dataproc/example01/set01';
Проверим список таблиц, выполнив команду show tables. Результат должен выглядеть так:
Запросим число перелётов с разбивкой по месяцам:
hive> SELECT Month, COUNT(*) FROM flights GROUP BY Month;
Пример результата такого запроса:
Безусловно, на одном примере сложно показать возможности сервиса Data Proc. Если вас интересует работа с большими данными в облаке, посмотрите доклады сотрудников Yandex Cloud об управлении кластерами Hadoop и заданиями в Data Proc на YouTube-канале Yandex Cloud.
Decision:
tuser@kvmubuntu:~$ cat YOUR-KEY
tuser@kvmubuntu:~$ eval `ssh-agent -s`
tuser@kvmubuntu:~$ ssh-add YOUR-KEY
tuser@kvmubuntu:~$ ssh root@<FQDN хоста>
tuser@kvmubuntu:~$ hadoop version
tuser@kvmubuntu:~$ hive
hive> select 1;
hive> CREATE EXTERNAL TABLE flights (Year bigint, Month bigint, FlightDate string, Flight_Number_Reporting_Airline bigint, OriginAirportID bigint, DestAirportID bigint) STORED AS PARQUET LOCATION 's3a://yc-mdb-examples/dataproc/example01/set01';
hive> show tables;
hive> SELECT Month, COUNT(*) FROM flights GROUP BY Month;
Task:
На этом уроке вы научитесь создавать чарты и дашборды. Мы пройдём по всей цепочке сущностей DataLens начиная с источника данных.
Изучая ClickHouse, мы анализировали данные о погоде с помощью SQL-запросов.
Давайте посмотрим на примере того же самого набора данных, как с помощью DataLens быстро и наглядно показать отличия климата в Москве и Санкт-Петербурге.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Источник данных.ClickHouse и DataLens интегрированы друг с другом, поэтому подключение DataLens к ClickHouse можно настроить всего за пару кликов.
В консоли управления запустите кластер ClickHouse, в котором развёрнута БД с таблицей Weather, созданной вами ранее. Перейдите на страницу кластера, на панели слева выберите DataLens.
Подключение. Нажмите кнопку Создать подключение. В открывшемся диалоговом окне вы увидите, что кластер ClickHouse, из которого мы возьмём данные для анализа, имя хоста и имя пользователя БД уже указаны.
Вам осталось только дать имя подключению в пустом поле вверху, ввести пароль к БД, нажать кнопку Проверить подключение и убедиться, что всё в порядке, а потом — кнопку Создать.
Датасет. После того как подключение будет создано, DataLens выведет на панели слева таблицы из БД и предложит создать датасет. Наш датасет будет состоять из одной таблицы: db1.Weather. Перетащите её на центральную панель, и внизу откроется предпросмотр данных.
Нажмите кнопку Сохранить и задайте имя датасета.
Подготовим данные. Это важная часть аналитической работы, и её не стоит пропускать. Прежде всего укажем имена полей на русском языке. Перейдите на вкладку Поля и переименуйте их:
- LocalDateTime - Дата и время
- LocalDate - Дата
- Month - Месяц
- Day - День
- TempC - Температура
- Pressure - Давление
- RelHumidity - Влажность
- Тип WindSpeed10MinAvg - Скорость ветра
- VisibilityKm - Видимость
- City - Город
Поля Дата и время, Дата, Месяц, День, Город будут полями-измерениями, а Температура, Давление, Влажность, Скорость ветра, Видимость — полями-показателями. Зададим для показателей тип агрегации Среднее.
Чарты. Приступим к созданию первого чарта. Нажмите кнопку Создать чарт. Выберите тип чарта Линейная диаграмма и перетащите Дата в раздел X , а Температура — в раздел Y.
На этом примере видно, что средства визуализации иногда помогают быстро проверить качество датасета: есть ли в нём пропущенные или странные, выбивающиеся из общей тенденции данные.
В нашем случае можно сделать вывод о том, что в датасете не хватает данных примерно с середины 2015-го по середину 2016-го.
Разделим показатели температуры для двух городов. Для этого перетащим Город в раздел Цвета. Кроме того, округлим значения поля Дата до месяцев, чтобы лучше увидеть, как различаются данные для Москвы и Санкт-Петербурга. Для этого слева от поля Дата нажмите зелёный значок календаря и в разделе Группировка выберите округление по месяцам.
Из этого графика уже можно делать выводы. В целом температура в Москве выше, чем в Санкт-Петербурге. Летом примерно на 5 градусов, зимой — на 1−2 градуса.
Сохраните чарт, чтобы затем использовать его для дашборда.
Чтобы окончательно разобраться с температурой, построим ещё один чарт — Столбчатую диаграмму — и сравним среднегодовую температуру. Выберите тип диаграммы. Добавьте поле Город в раздел X, чтобы разделить отображение значений температуры. Также для поля Дата выберите группировку по годам.
Кроме того, для чарта понадобится задать фильтр по датам. Поскольку мы сравниваем среднегодовые значения, неполные данные за 2009 и 2019 годы отбросим. В разделе Фильтры нажмите + и выберите поле Дата.
Для этого чарта мы возьмём только данные из диапазона с начала 2010-го по конец 2018-го. Нажмите кнопку Применить фильтр и сохраните чарт.
Сделайте сами два таких же чарта с данными о скорости ветра: линейную диаграмму со среднемесячными значениями скорости ветра в городах и столбчатую диаграмму со среднегодовыми значениями.
Теперь у нас достаточно чартов для информативного дашборда.
Дашборд. На панели слева выберите Дашборды и нажмите кнопку Создать дашборд. Введите название дашборда и нажмите Создать.
Если в каталоге это первый дашборд — он откроется сразу после создания. Если в каталоге есть другие дашборды, вы увидите список. В этом случае выберите из списка только что созданный дашборд.
Теперь добавим созданные нами чарты на дашборд. Нажмите Добавить и в выпадающем списке выберите Чарт. Поочерёдно выбирайте из списка и добавляйте чарты.
В результате на дашборде появятся четыре виджета с чартами. Меняйте размеры и положение виджетов для лучшей визуализации.
Осталось лишь несколько последних штрихов. В том же пункте меню Добавить создадим пару заголовков и селектор по датам. В правом верхнем углу каждого виджета нажмите значок шестерёнки, чтобы изменить названия. Сохраним дашборд.
То, какие чарты сделать и как их разместить на дашборде, бывает понятно не сразу. Рассмотрите несколько вариантов, когда строите дашборд, чтобы разобраться, какая именно визуализация лучше помогает ответить на вопросы.
В маркетплейсе DataLens вы найдёте ещё один дашборд с погодой. Он хорошо демонстрирует возможности визуализации данных этого сервиса.
Чтобы открыть публичный доступ к дашборду, справа от его названия нажмите ··· и выберите Публичный доступ. Скопируйте ссылку. По ней дашборд будет доступен всем, с любых устройств и без аутентификации.
Task:
Права доступа и роли для сервисного аккаунта.
В этом уроке вы научитесь работать с сервисными аккаунтами и назначать для них роли и права доступа к объектам.
В качестве объекта будет выступать созданный в сервисе KMS ключ шифрования.
Предположим, что перед вами стоит задача использовать сервисный аккаунт для ротации ключей.
Чтобы решить эту задачу, понадобится выполнить следующие шаги:
- Создать сервисный аккаунт.
- Получить права на управление этим сервисным аккаунтом.
- Создать статические ключи доступа и привязать их к сервисному аккаунту, чтобы он мог пройти авторизацию в сервисе KMS.
- Создать в сервисе KMS ключ шифрования и назначить сервисному аккаунту роль kms.admin для управления этим ключом.
- И, наконец, ротировать ключ, то есть создать его новую версию с такими же параметрами, из-под сервисного аккаунта.
- Нужно заметить, что через консоль управления сервисному аккаунту можно назначить роль только на каталог, в котором он был создан. Роли на все остальные ресурсы назначаются с помощью CLI или API. Поэтому для выполнения этой практической работы вам понадобится вспомнить, как пользоваться утилитой yc, чему вы уже научились в курсе «DevOps и автоматизация».
ШАГ 1
Для начала создадим сервисный аккаунт. В консоли управления войдите в каталог облака, в котором вы будете выполнять эту практическую работу, и перейдите на вкладку Сервисные аккаунты. Нажмите кнопку Создать сервисный аккаунт.
В открывшемся окне задайте для нового сервисного аккаунта имя и, при желании, добавьте описание. Здесь аккаунту также можно добавить роли на каталог, в котором он создаётся.
Оставьте поле с ролями пустым и нажмите Создать.
ШАГ 2
Настройте для вашего аккаунта на Яндексе доступ на авторизацию под созданным сервисным аккаунтом.
Для начала убедитесь, что вы авторизованы в аккаунте с ролью admin. Чтобы это проверить, выполните команду
yc iam role list
Вы увидите список ролей вашего аккаунта. Примерно такой (роль admin должна в нем присутствовать):
Узнайте идентификатор своего аккаунта. Он понадобится, чтобы добавить вашему аккаунту роль editor на созданный сервисный аккаунт (сервисный аккаунт тоже является ресурсом, и для работы с ним нужна соответствующая роль). Воспользуйтесь для этого командой:
yc iam user-account get <имя_вашего_аккаунта>
Кроме того, нужно узнать идентификатор созданного сервисного аккаунта. Это можно сделать в разделе Сервисные аккаунты консоли управления. Выбрав нужный аккаунт в списке, вы перейдёте на страницу с детальной информацией о нем.
Теперь предоставьте вашему пользовательскому аккаунту права на управление созданным сервисным аккаунтом. Для этого нужно выполнить команду
yc iam service-account add-access-binding <ID_сервисного_аккаунта> \
--role editor --subject userAccount:<ID_пользовательского_аккаунта>
ШАГ 3
Настройте аутентификацию под сервисным аккаунтом с вашей машины.
Сначала нужно создать статические ключи доступа и сохранить их в json-файле (например key.json).
Воспользуйтесь для этого командой
yc --folder-name <имя_каталога> iam key create \
--service-account-name <имя_сервисного_аккаунта> --output key.json
После выполнения команды вы получите идентификатор созданной ключевой пары. Используя статические ключи доступа, можно получить IAM-токен для авторизации в сервисах.
Теперь нужно создать профиль, от имени которого будут выполняться операции из-под сервисного аккаунта. Для этого придумайте имя этого профиля (например yc-lab23-profile) и выполните команду:
yc config profile create <имя_профиля>
Привяжите к этому профилю ранее созданный статический ключ доступа с помощью команды:
yc config set service-account-key key.json
Чтобы убедиться, что всё сделано правильно, выведите информацию об авторизации и ключах доступа
yc config list
Вы должны получить примерно такой результат:
ШАГ 4
Теперь нужно создать ключ шифрования, ротацией которого вы будете управлять с помощью сервисного аккаунта. Для этого перейдите в дашборд каталога в консоли управления, нажмите кнопку Создать ресурс и выберите Ключ шифрования.
В открывшемся окне задайте для ключа имя и нажмите кнопку Создать. Новый ключ появится в списке в открывшемся разделе Ключи.
Чтобы назначить сервисному аккаунту роль для какого-либо ресурса, нужно знать идентификатор этого ресурса. Нажмите строку с созданным ключом, чтобы перейти на страницу детальной информации о нём, и скопируйте ID ключа.
Перейдем к назначению сервисному аккаунту роли kms.admin для управления созданным ключом шифрования. Перед этим нужно сначала вернуться в профиль вашего аккаунта.
yc config profile activate <имя_профиля>
Выполните команду:
yc --folder-name <имя_каталога> kms symmetric-key \
add-access-binding <ID_ключа_шифрования> --role kms.admin \
--subject serviceAccount:<ID_сервисного_аккаунта>
Теперь с помощью сервисного аккаунта вы можете управлять этим ключом шифрования.
ШАГ 5
В CLI переключитесь обратно в профиль сервисного аккаунта:
yc config profile activate <имя_профиля_сервисного_аккаунта>
Ротируйте ключ шифрования:
yc kms symmetric-key rotate <ID_ключа>
После выполнения команды перейдите на страницу детальной информации о ключе и откройте вкладку Операции.
Вы увидите, что операция по ротации ключа выполнена под вашим сервисным аккаунтом. Значит, всё получилось и задача решена!
Decision:
tuser@kvmubuntu:~$ yc iam service-account create --name <имя_сервисного_аккаунта>
tuser@kvmubuntu:~$ yc iam service-account create --name security-labs \
--description "Service account for Security course labs"
tuser@kvmubuntu:~$ yc iam role list
tuser@kvmubuntu:~$ yc iam user-account get <ваш_логин>
tuser@kvmubuntu:~$ yc iam service-account list
tuser@kvmubuntu:~$ yc iam service-account get <имя_сервисного_аккаунта>
tuser@kvmubuntu:~$ yc iam service-account add-access-binding <id_сервисного_аккаунта> \
--role editor \
--subject userAccount:<id_пользовательского_аккаунта>
tuser@kvmubuntu:~$ vim key.json
tuser@kvmubuntu:~$ cat key.json
{
"id": "YOUR-ID1",
"service_account_id": "YOUR-ID2",
"created_at": "2023-12-09T01:19:35.625144946Z",
"key_algorithm": "RSA_2048",
"public_key": "-----BEGIN PUBLIC KEY-----\nYOUR-KEY1\n-----END PUBLIC KEY-----\n",
"private_key": "PLEASE DO NOT REMOVE THIS LINE! Yandex.Cloud SA Key ID <YOUR-ID1>\n-----BEGIN PRIVATE KEY-----\nYOUR-KEY2\n-----END PRIVATE KEY-----\n"
}
tuser@kvmubuntu:~$ yc iam key create \
--service-account-name <имя_сервисного_аккаунта> \
--output key.json
tuser@kvmubuntu:~$ yc config profile create <имя_профиля_сервисного_аккаунта>
tuser@kvmubuntu:~$ yc config set service-account-key key.json
tuser@kvmubuntu:~$ yc config set folder-id <идентификатор_рабочего_каталога>
tuser@kvmubuntu:~$ yc config list
tuser@kvmubuntu:~$ yc config profile list
tuser@kvmubuntu:~$ yc config profile activate <имя_основного_профиля>
tuser@kvmubuntu:~$ yc kms symmetric-key add-access-binding \
--id <id_ключа_шифрования> \
--role kms.admin \
--subject serviceAccount:<id_сервисного_аккаунта>
tuser@kvmubuntu:~$ yc config profile activate <имя_профиля_сервисного_аккаунта>
tuser@kvmubuntu:~$ yc kms symmetric-key rotate --id <id_ключа_шифрования>
Task:
На этой практической работе мы установим утилиту yc, познакомимся с режимом подсказок --help и выполним несколько простых команд.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Установка CLI
Первым делом скачайте и установите CLI (если вы ещё не сделали этого в предыдущих курсах).
Теперь настройте программу для работы с вашим аккаунтом и облаком. Для этого запустите командную строку и введите команду:
yc init 
В консоли появится предложение перейти по ссылке, чтобы программа получила доступ к аккаунту и облаку. Перейдите по ссылке, согласитесь с условиями, затем скопируйте токен и вставьте его в окно командной строки.
Срок жизни токена — один год. Через год необходимо получить новый токен и повторить аутентификацию.
Если кто-то узнал ваш токен, отзовите его и запросите новый.
Продолжайте установку: выберите облако, каталог и зону доступности по умолчанию. Следуйте указаниям системы и завершите настройку.
Готово!
При установке автоматически создается профиль default, в него записываются выбранные настройки.
Список профилей можно посмотреть командой:
yc config profile list 
Флаг --help
На прошлом уроке вы узнали о флагах, которые используются при запуске команд. Пожалуй, самый важный и частый флаг — --help (сокращенно -h), поэтому поговорим о нём подробнее.
Команд для управления ресурсами облака много. Вы запомните некоторые, но помнить всё невозможно. Пользуйтесь флагом --help, когда пишете команду. В этом случае она не выполняется, а в консоль выводится информация о ресурсах, которыми управляет команда, и параметрах запуска.
Помните, на предыдущем уроке мы говорили о типичной структуре большинства команд CLI (сервис — ресурс — действие — флаги)?
Важная особенность флага --help заключается в том, что его можно применять для каждого уровня этой структуры и писать команду постепенно.
В этой практической работе нам понадобится ВМ в облаке. Если у вас нет ВМ — создайте её в консоли управления, как мы это делали в предыдущих курсах.
Допустим, вы хотите перезапустить эту ВМ, но не помните полный синтаксис команды.
Сначала вызовите описание сервиса Yandex Compute Cloud:
yc compute --help 
Вы увидите синтаксис команды (раздел Usage), список ресурсов, которыми можно управлять (Groups), а также действий (Commands) и флагов:
Usage:
yc compute <group|command> 
Groups:
instance       Manage virtual machine instances
disk         Manage disks
disk-type       Show available disk types
image         Manage images
snapshot       Manage snapshots
zone         Show availability zones
instance-group     Manage instance groups
placement-group    Manage placement groups
host-type       Show available host types
host-group       Manage host groups
disk-placement-group Manage disk placement groups
filesystem       Manage filesystems 
Commands:
connect-to-serial-port Connect to serial port 
Global Flags:
   --profile string   Set the custom configuration file.
   --debug        Debug logging.
   --debug-grpc     Debug gRPC logging. Very verbose, used for debugging connection problems.
   --no-user-output   Disable printing user intended output to stderr.
   --retry int      Enable gRPC retries. By default, retries are enabled with maximum 5 attempts.
               Pass 0 to disable retries. Pass any negative value for infinite retries.
               Even infinite retries are capped with 2 minutes timeout.
   --cloud-id string   Set the ID of the cloud to use.
   --folder-id string   Set the ID of the folder to use.
   --folder-name string Set the name of the folder to use (will be resolved to id).
   --endpoint string   Set the Cloud API endpoint (host:port).
   --token string     Set the OAuth token to use.
   --format string    Set the output format: text (default), yaml, json, json-rest.
-h, --help         Display help for the command. 
Вам нужна ВМ — ресурс instance. Теперь узнайте, как получить список активных ВМ и какая команда отвечает за перезапуск:
yc compute instance --help 
Вы увидите список возможных действий (команд) над ВМ:
Manage virtual machine instances 
Usage:
yc compute instance <command> 
Aliases:
instances 
Commands:
get           Show information about the specified virtual machine instance
list           List virtual machine instances
create         Create a virtual machine instance
create-with-container  Create a virtual machine instance running Docker container
update         Update the specified virtual machine instance
update-container     Update the specified virtual machine instance running Docker container
add-metadata       Add or update metadata for the specified virtual machine instance
remove-metadata     Remove keys from metadata for the specified virtual machine instance
add-labels       Add labels to specified virtual machine instance
remove-labels      Remove labels from specified virtual machine instance
delete         Delete the specified virtual machine instance
get-serial-port-output Return the serial port output of the specified virtual machine instance
stop           Stop the specified virtual machine instance
start          Start the specified virtual machine instance
restart         Restart the specified virtual machine instance
attach-disk       Attach existing disk to the the specified virtual machine instance
attach-new-disk     Attach new disk to the the specified virtual machine instance
detach-disk       Detach disk from the the specified virtual machine instance
attach-filesystem    Attach existing filesystem to the specified virtual machine instance
detach-filesystem    Detach filesystem from the specified virtual machine instance
update-network-interface Update the specified network interface of virtual machine instance
add-one-to-one-nat   Add one-to-one NAT to the the specified network interface of virtual machine instance
remove-one-to-one-nat  Remove one-to-one NAT to the the specified network interface of virtual machine instance
list-operations     List operations for the specified instance 
Global Flags:
   --profile string   Set the custom configuration file.
   --debug        Debug logging.
   --debug-grpc     Debug gRPC logging. Very verbose, used for debugging connection problems.
   --no-user-output   Disable printing user intended output to stderr.
   --retry int      Enable gRPC retries. By default, retries are enabled with maximum 5 attempts.
               Pass 0 to disable retries. Pass any negative value for infinite retries.
               Even infinite retries are capped with 2 minutes timeout.
   --cloud-id string   Set the ID of the cloud to use.
   --folder-id string   Set the ID of the folder to use.
   --folder-name string Set the name of the folder to use (will be resolved to id).
   --endpoint string   Set the Cloud API endpoint (host:port).
   --token string     Set the OAuth token to use.
   --format string    Set the output format: text (default), yaml, json, json-rest.
-h, --help         Display help for the command. 
Сначала получите список ВМ, это команда list:
yc compute instance list 
Результатом выполнения команды будет список машин в том каталоге, где вы работаете, с именами и идентификаторами:
+----------------------+-------------+---------------+---------+----------------+-------------+
|     ID     |  NAME   |  ZONE ID  | STATUS | EXTERNAL IP | INTERNAL IP |
+----------------------+-------------+---------------+---------+----------------+-------------+
| fhm2p20bifmg3k3voda7 | my-instance | ru-central1-a | RUNNING | ХХХ.ХХХ.ХХХ.ХХ | ХХ.ХХХ.Х.ХХ |
+----------------------+-------------+---------------+---------+----------------+-------------+ 
На шаге 2 вы нашли команду перезапуска — restart, теперь можно посмотреть её синтаксис:
yc compute instance restart --help 
Результат выполнения будет таким: 
Restart the specified virtual machine instance 
Usage:
yc compute instance restart <INSTANCE-NAME>|<INSTANCE-ID> [Global Flags...] 
Flags:
   --id string   Instance id.
   --name string Instance name.
   --async     Display information about the operation in progress, without waiting for the operation to complete. 
Global Flags:
   --profile string   Set the custom configuration file.
   --debug        Debug logging.
   --debug-grpc     Debug gRPC logging. Very verbose, used for debugging connection problems.
   --no-user-output   Disable printing user intended output to stderr.
   --retry int      Enable gRPC retries. By default, retries are enabled with maximum 5 attempts.
               Pass 0 to disable retries. Pass any negative value for infinite retries.
               Even infinite retries are capped with 2 minutes timeout.
   --cloud-id string   Set the ID of the cloud to use.
   --folder-id string   Set the ID of the folder to use.
   --folder-name string Set the name of the folder to use (will be resolved to id).
   --token string     Set the OAuth token to use.
   --format string    Set the output format: text (default), yaml, json, json-rest.
-h, --help         Display help for the command. 
Наконец, вы получили полный синтаксис команды перезапуска. Примените её к ВМ:
yc compute instance restart --name <имя_ВМ> 
По такому принципу вы можете сформировать в консоли любую команду.
Итак, подведём итоги. Чтобы уточнить синтаксис или порядок действий при работе с CLI, выберите один из трёх путей:
найдите нужное действие на странице сервиса Yandex Cloud в документации и там переключитесь на вкладку CLI;
найти команду в справочнике о yc;
воспользуйтесь флагом --help, шаг за шагом уточняя возможности и синтаксис команды.
Синхронный и асинхронный режимы работы
Некоторые команды выполняются очень быстро. Например, создание каталога или просмотр настроек профиля. Такие команды можно выполнять подряд, одну за другой — без задержек.
Если при выполнении команды ресурс изменяет состояние, создается операция. Примеры операций — перезапуск ВМ после обновления или резервное копирование базы данных.
Если каждая следующая команда ждёт завершения предыдущей операции, такой режим выполнения называется синхронным. Когда какая-то операция в синхронном режиме выполняется долго, CLI выводит в консоли точки и время с начала операции, чтобы показать, что процесс не завис:
...1s...6s...12s...17s 
Операция может выполняться довольно долго, а ожидание — затормозить процесс. В таких случаях важно оценить, нужен ли результат операции для выполнения следующих команд. Если нет — можно не ждать её завершения и сразу же переходить к следующей команде. Этот режим называется асинхронным.
Чтобы выполнить команду асинхронно, используйте флаг --async.
Перезапустите одну из ранее созданных ВМ в асинхронном режиме (в команде укажите имя этой ВМ):
yc compute instance restart --name <имя_ВМ> --async 
В ответ на асинхронный вызов CLI выводит идентификатор операции (в поле id) и информацию о ней:
id: fhm5k7iq03rm2s7enhdk
description: Restart instance
created_at: "2021-03-27T08:32:47.562595036Z"
created_by: aje9cb7k03512mrugcee
modified_at: "2021-03-27T08:32:47.562595036Z"
metadata:
'@type': type.googleapis.com/yandex.cloud.compute.v1.RestartInstanceMetadata
instance_id: fhm2p20bifmg3k3voda7 
С помощью идентификатора операции вы можете проверить результаты её выполнения:
yc operation get <идентификатор_операции> 
Когда операция завершится, вы увидите результат:
id: fhm5k7iq03rm2s7enhdk
...
done: true
... 
Task:
Создание ВМ — одна из самых сложных команд CLI, потому что в ней очень много параметров. Давайте потренируемся работать с ней. Но сначала подготовим окружение.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Мы будем работать в каталоге по умолчанию. Создадим в нём сеть, в ней — три подсети, а затем по ВМ в каждой подсети.
Создайте сеть my-network в Virtual Private Cloud. Эта команда относится к группе vpc.
Проверьте синтаксис команды
yc vpc network create --name my-network 
Выполнение операции займёт какое-то время. В итоге сеть появится в каталоге, который вы выбрали в предыдущей практической работе.
Теперь создадим три подсети в разных зонах доступности (ru-central1-a, ru-central1-b, ru-central1-d). Чтобы проще было выполнять задания дальше, назовите их my-subnet-1, my-subnet-2 и my-subnet-3. А пространства IP-адресов для подсетей укажите, соответственно, как 192.168.1.0/24, 192.168.2.0/24 и 192.168.3.0/24.
Не забудьте указать, что вы создаёте подсети в новой сети, созданной на предыдущем шаге. Иначе они появятся в сети по умолчанию, которая есть в каждом каталоге.
Вот так выглядит команда создания подсети в зоне доступности ru-central1-a:
yc vpc subnet create \
--name my-subnet-1 \
--zone ru-central1-a \
--range 192.168.1.0/24 \
--network-name my-network 
Где:
name — имя подсети.
zone — зона доступности.
range — адресное пространство подсети.
network-name — имя сети, в которой создаётся подсеть.
Создайте две другие подсети сами.
Проверьте синтаксис команд
yc vpc subnet create \
--name my-subnet-2 \
--zone ru-central1-b \
--range 192.168.2.0/24 \
--network-name my-network
yc vpc subnet create \
--name my-subnet-3 \
--zone ru-central1-d \
--range 192.168.3.0/24 \
--network-name my-network 
Осталось создать три ВМ в нужных зонах доступности и привязать к ним подсети.
Пусть машины работают под управлением ОС Ubuntu 20.04 LTS, имеют диски объёмом 30 Гб, 4 Гб оперативной памяти и два виртуальных процессорных ядра.
ВМ могут создаваться долго, поэтому запускайте команды в асинхронном режиме.
Пример команды для первой ВМ:
yc compute instance create \
--name my-instance-1 \
--hostname my-instance-1 \
--zone ru-central1-a \
--create-boot-disk image-family=ubuntu-2004-lts,size=30,type=network-nvme \
--image-folder-id standard-images \
--memory 4 --cores 2 --core-fraction 100 \
--network-interface subnet-name=my-subnet-1,nat-ip-version=ipv4 \
--async 
    Посмотрите внимательно на все параметры. Подумайте, какой из них за что отвечает.
    Создайте две другие машины сами.
Проверьте синтаксис команд
yc compute instance create \
--name my-instance-2 \
--hostname my-instance-2 \
--zone ru-central1-b \
--create-boot-disk image-family=ubuntu-2004-lts,size=30,type=network-nvme \
--image-folder-id standard-images \
--memory 4 --cores 2 --core-fraction 100 \
--network-interface subnet-name=my-subnet-2,nat-ip-version=ipv4 \
--async
yc compute instance create \
--name my-instance-3 \
--hostname my-instance-3 \
--zone ru-central1-d \
--create-boot-disk image-family=ubuntu-2004-lts,size=30,type=network-nvme \
--image-folder-id standard-images \
--memory 4 --cores 2 --core-fraction 100 \
--network-interface subnet-name=my-subnet-3,nat-ip-version=ipv4 \
--async 
После выполнения каждой команды благодаря флагу --async вы получите идентификатор операции и ее описание в виде:
id: c9q9v4bsn1hs9api4b13
description: Create instance
created_at: "2021-03-01T03:23:00.079888Z"
created_by: aje8s4vd4pp7cduq2o4k
modified_at: "2022-07-29T09:14:53.567744154Z"
metadata:
'@type': type.googleapis.com/yandex.cloud.compute.v1.CreateInstanceMetadata
instance_id: fhmepiaciq5l9slqid3k 
Проследите за статусом одной из операций, используя ее идентификатор.
Проверьте синтаксис команды

yc operation get <идентификатор_операции> 
Дождитесь, пока операция завершится. Используйте для этого команду wait.
Проверьте синтаксис команды
yc operation wait <идентификатор_операции> 
Убедитесь, что ВМ созданы. Для этого выведите их список.
yc compute instance list 
По умолчанию список выдается в виде таблицы:
+----------------------+---------------+---------------+---------+---------------+--------------+
|     ID     |   NAME   |  ZONE ID  | STATUS | EXTERNAL IP | INTERNAL IP |
+----------------------+---------------+---------------+---------+---------------+--------------+
| ef34r4fs8dsva3qtsivs | my-instance-3 | ru-central1-d | RUNNING | 51.250.44.130 | 192.168.3.34 |
| epdj7u79isrolup3vfo8 | my-instance-2 | ru-central1-b | RUNNING | 158.160.6.249 | 192.168.2.28 |
| fhmepiaciq5l9slqid3k | my-instance-1 | ru-central1-a | RUNNING | 62.84.126.39 | 192.168.1.30 |
+----------------------+---------------+---------------+---------+---------------+--------------+ 
Список можно вывести в формате YAML или JSON (эта возможность пригодится вам на следующих уроках):
yc compute instance list --format json 
Список в формате JSON содержит больше информации, чем таблица.
Посмотрите результат
[
{
  "id": "ef3rutmaas72bsujcja7",
  "folder_id": "b1gfdbij3ijgopgqv9m9",
  "created_at": "2021-06-21T12:41:10Z",
  "name": "my-instance-3",
  "zone_id": "ru-central1-d",
  "platform_id": "standard-v2",
  "resources": {
   "memory": "4294967296",
   "cores": "2",
   "core_fraction": "100"
  },
  "status": "RUNNING",
  "metadata_options": {
   "gce_http_endpoint": "ENABLED",
   "aws_v1_http_endpoint": "ENABLED",
   "gce_http_token": "ENABLED",
   "aws_v1_http_token": "ENABLED"
  },
  "boot_disk": {
   "mode": "READ_WRITE",
   "device_name": "ef3v2lor1u4pfn3ce1al",
   "auto_delete": true,
   "disk_id": "ef3v2lor1u4pfn3ce1al"
  },
  "network_interfaces": [
   {
    "index": "0",
    "mac_address": "d0:0d:1b:f7:6c:a5",
    "subnet_id": "b0c4h992tbuodl5hudpu",
    "primary_v4_address": {
     "address": "10.128.0.32",
     "one_to_one_nat": {
      "address": "178.154.212.5",
      "ip_version": "IPV4"
     }
    }
   }
  ],
  "fqdn": "my-instance-3.ru-central1.internal",
  "scheduling_policy": {},
  "network_settings": {
   "type": "STANDARD"
  },
  "placement_policy": {}
},
{
  "id": "epd928ffks7m8ssc4i3k",
  "folder_id": "b1gfdbij3ijgopgqv9m9",
  "created_at": "2021-06-21T12:40:35Z",
  "name": "my-instance-2",
  "zone_id": "ru-central1-b",
  "platform_id": "standard-v2",
  "resources": {
   "memory": "4294967296",
   "cores": "2",
   "core_fraction": "100"
  },
  "status": "RUNNING",
  "metadata_options": {
   "gce_http_endpoint": "ENABLED",
   "aws_v1_http_endpoint": "ENABLED",
   "gce_http_token": "ENABLED",
   "aws_v1_http_token": "ENABLED"
  },
  "boot_disk": {
   "mode": "READ_WRITE",
   "device_name": "epddf7t0ljn9i1jp2pbs",
   "auto_delete": true,
   "disk_id": "epddf7t0ljn9i1jp2pbs"
  },
  "network_interfaces": [
   {
    "index": "0",
    "mac_address": "d0:0d:91:21:ef:a7",
    "subnet_id": "e2l1fgq2fbhnp6b929t7",
    "primary_v4_address": {
     "address": "10.129.0.9",
     "one_to_one_nat": {
      "address": "84.201.176.134",
      "ip_version": "IPV4"
     }
    }
   }
  ],
  "fqdn": "my-instance-2.ru-central1.internal",
  "scheduling_policy": {},
  "network_settings": {
   "type": "STANDARD"
  },
  "placement_policy": {}
},
{
  "id": "fhm1op9id0dc6bubfags",
  "folder_id": "b1gfdbij3ijgopgqv9m9",
  "created_at": "2021-06-21T12:39:43Z",
  "name": "my-instance-1",
  "zone_id": "ru-central1-a",
  "platform_id": "standard-v2",
  "resources": {
   "memory": "4294967296",
   "cores": "2",
   "core_fraction": "100"
  },
  "status": "RUNNING",
  "metadata_options": {
   "gce_http_endpoint": "ENABLED",
   "aws_v1_http_endpoint": "ENABLED",
   "gce_http_token": "ENABLED",
   "aws_v1_http_token": "ENABLED"
  },
  "boot_disk": {
   "mode": "READ_WRITE",
   "device_name": "fhmms7r7ia4uteikv1to",
   "auto_delete": true,
   "disk_id": "fhmms7r7ia4uteikv1to"
  },
  "network_interfaces": [
   {
    "index": "0",
    "mac_address": "d0:0d:1c:65:32:68",
    "subnet_id": "e9bcvlanhbum9ggdvkh2",
    "primary_v4_address": {
     "address": "10.130.0.6",
     "one_to_one_nat": {
      "address": "178.154.225.167",
      "ip_version": "IPV4"
     }
    }
   }
  ],
  "fqdn": "my-instance-1.ru-central1.internal",
  "scheduling_policy": {},
  "network_settings": {
   "type": "STANDARD"
  },
  "placement_policy": {}
}

Чтобы избежать ненужных расходов, удалите три созданные ВМ – в следующих практических работах они вам не понадобятся.
yc compute instance delete my-instance-1 my-instance-2 my-instance-3
Task:
В этой практической работе вы создадите, обновите и удалите группу ВМ.
Вы уже убедились, что создать даже одну ВМ через yc непросто: нужно установить много разных параметров. Создание группы ВМ требует ещё больше параметров. Чтобы не указывать их все в командной строке, конфигурацию описывают в файле, который используют при создании группы. Такой файл называется спецификацией. Использование спецификаций — это первый шаг в освоении подхода Infrastructure as Code (IaC), который мы будем применять на следующих уроках.
Спецификации пишутся в разных форматах. Для группы ВМ используется язык YAML. Если вы не знакомы с ним — ничего страшного. В документации есть шаблоны спецификаций, и на первых порах вам будет достаточно лишь немного их изменять. Ниже мы разберём, как составлять спецификации.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Часть 1. Создание Instance Group
Для разворачивания группы ВМ потребуется сеть. Если сети ещё нет, создайте её. 
Посмотрите информацию об имеющихся сетях. 
yc vpc network list 
Сохраните идентификатор сети, он понадобиться нам в дальнейшем.
По умолчанию все операции в Instance Groups выполняются от имени сервисного аккаунта c ролью editor на каталог. Если сервисного аккаунта нет, то тоже создайте его и назначьте эту роль.
Посмотрите информацию об имеющихся сервисных аккаунтах.
yc iam service-account list 
Сохраните идентификатор сервисного аккаунта, он понадобится нам в дальнейшем.
Для создания группы необходимо подготовить её спецификацию. Создайте в любом текстовом редакторе файл с расширением yaml, например specification.yaml.
Обратите внимание: в формате YAML важны отступы слева. Даже если текст правильный, но отступы не соблюдены, при выполнении спецификации возникнут ошибки.
Сначала внесите информацию о группе. Пусть группа называется my-group. Укажите идентификатор сервисного аккаунта, от имени которого будете работать (см. шаг 2).
Идентификаторы ресурсов уникальны. Копируя команды из текста урока, не забывайте подставлять свои идентификаторы.
  name: my-group
  service_account_id: <идентификатор_сервисного_аккаунта>   
Наша группа будет содержать три одинаковые ВМ. Машины создадим из публичного образа Ubuntu 18.04 LTS (возьмём не последнюю версию, чтобы потренироваться обновлять ВМ). Узнайте идентификатор образа с помощью команды:
yc compute image list --folder-id standard-images 
В столбце FAMILY найдите ubuntu-1804-lts, в столбце ID будет указан нужный идентификатор.
Опишите в спецификации ВМ. Это раздел instance_template.
Пусть каждая машина использует платформу Intel Broadwell (посмотрите поддерживаемые платформы в документации Yandex Compute Cloud), имеет 2 Гб оперативной памяти и два процессорных ядра.
instance_template:
   platform_id: standard-v2
   resources_spec:
     memory: 2g
     cores: 2 
Добавьте описание загрузочного диска. Он будет использоваться на чтение и запись (режим READ_WRITE). Укажите идентификатор образа, который получили на шаге 5. Выделите сетевой HDD объёмом 32 Гб. 
   boot_disk_spec:
    mode: READ_WRITE
    disk_spec:
      image_id: <идентификатор_образа>
      type_id: network-hdd
      size: 32g
Теперь опишите сеть: идентификатор сети из каталога по умолчанию (см. шаг 1). Задайте публичный IP-адрес, чтобы к ВМ можно было обращаться извне.
   network_interface_specs:
     - network_id: <идентификатор_сети>
     primary_v4_address_spec: { one_to_one_nat_spec: { ip_version: IPV4 }} 
В политике планирования укажите, что машина не прерываемая.
   scheduling_policy:
     preemptible: false 
В политике развертывания (раздел deploy policy) укажите, что в каждый момент времени может быть неработоспособной только одна машина, не больше. Запретите увеличивать число ВМ, т. е. создавать больше трех машин одновременно. Мы чуть подробнее разберём эти настройки, когда будем обновлять ВМ в группе.
   deploy_policy:
     max_unavailable: 1
     max_expansion: 0 
Мы создаем группу фиксированного размера из трёх ВМ. Укажите это в политике масштабирования (раздел scale_policy):
  scale_policy:
    fixed_scale:
    size: 3 
Наконец, в политике распределения машин по зонам (раздел allocation_policy) укажите, что будет использоваться зона ru-central1-a. Мы делаем это для простоты. Лучше распределять ВМ группы по разным зонам доступности — это позволит пережить краткие сбои или выход зоны из строя.
  allocation_policy:
    zones:
      - zone_id: ru-central1-a 
Для балансировщика нагрузки (раздел load_balancer_spec) укажите целевую группу, к которой он будет привязан (это мы рассмотрим чуть ниже).
  load_balancer_spec:
    target_group_spec:
    name: my-target-group 
Нашей спецификации уже достаточно, чтобы создать группу ВМ. Но на эти машины не будет установлено никакого ПО, только операционная система из публичного образа. Если не менять конфигурацию, то после создания ВМ вам придётся устанавливать программы вручную.
Чтобы сэкономить время и сократить число ошибок, давайте максимально автоматизируем создание ВМ, включая установку ПО. Для этого добавим в конфигурацию машины секцию, где будут вызываться команды установки программ. В этой же секции можно описать создание пользователей, но мы этого делать не будем, так как заходить на ВМ не планируем.
Установим на машины веб-сервер NGINX и на веб-странице index.nginx-debian.html, которая создается по умолчанию и выводит приветственное сообщение «Welcome to nginx», заменим слово nginx идентификатором активной ВМ и версией ОС. Поскольку мы подключим балансировщик нагрузки, идентификатор активной ВМ будет различаться для разных пользователей. Это и позволит нам убедиться в том, что балансировщик работает.
Для установки ПО используйте cloud-init — пакет, выполняющий команды на ВМ при первом запуске. Вы узнали о нём из курса о ВМ. Команды опишите в блоке конфигурации #cloud-config. Примеры команд смотрите в документации cloud-init.
Содержимое #cloud-config описывается в разделе instance_template в секции metadata:
  metadata:
   user-data: |-
    #cloud-config
     package_update: true
     runcmd:
      - [apt-get, install, -y, nginx ]
      - [/bin/bash, -c, 'source /etc/lsb-release; sed -i "s/Welcome to nginx/It is $(hostname) on $DISTRIB_DESCRIPTION/" /var/www/html/index.nginx-debian.html'] 
Спецификация готова. Вот ее полный текст. Помните, что в формате YAML важно соблюдать отступы слева.
name: my-group
service_account_id: ajeu495h1s9tn1rorulb 
instance_template:
  platform_id: standard-v1
  resources_spec:
    memory: 2g
    cores: 2
  boot_disk_spec:
    mode: READ_WRITE
    disk_spec:
      image_id: fd8fosbegvnhj5haiuoq 
      type_id: network-hdd
      size: 32g
  network_interface_specs:
    - network_id: enpnr4onfs6ihtoao32u
     primary_v4_address_spec: { one_to_one_nat_spec: { ip_version: IPV4 }}
  scheduling_policy:
    preemptible: false
  metadata:
   user-data: |-
    #cloud-config
     package_update: true
     runcmd:
      - [ apt-get, install, -y, nginx ]
      - [/bin/bash, -c, 'source /etc/lsb-release; sed -i "s/Welcome to nginx/It is $(hostname) on $DISTRIB_DESCRIPTION/" /var/www/html/index.nginx-debian.html'] 
deploy_policy:
  max_unavailable: 1
  max_expansion: 0
scale_policy:
  fixed_scale:
    size: 3
allocation_policy:
  zones:
    - zone_id: ru-central1-a 
load_balancer_spec:
  target_group_spec:
    name: my-target-group 
Теперь создайте группу ВМ по подготовленной спецификации. Уточните синтаксис команды сами:
yc compute instance-group --help 
Проверить синтаксис команды
yc compute instance-group create --file <путь_к_файлу_specification.yaml> 
Для тренировки можете вызвать эту команду в асинхронном режиме, а затем проверить её статус и дождаться завершения.
Убедитесь, что группа создана, в веб-консоли или выведя список групп с помощью yc. 
yc compute instance-group list 
В списке вы должны увидеть свою группу машин my-group:
+----------------------+------------+------+
|     ID     |  NAME  | SIZE |
+----------------------+------------+------+
| amc65sbgfqeqf00m02sc | my-group |  3 |
+----------------------+------------+------+ 
Часть 2. Балансировщик
Создайте балансировщик my-load-balancer. Посмотрите, какие параметры должны быть у соответствующей команды:
yc load-balancer network-load-balancer create --help 
В выводе справки обратите внимание, что при создании балансировщика можно сразу создать и обработчик входящего трафика (параметр --listener).
Формат параметра --listener достаточно хитрый: в нём можно указать сразу несколько подпараметров через запятую:
...
--listener name=my-listener,external-ip-version=ipv4,port=80
... 
Помимо имени обработчика, здесь указывается версия IP-протокола и порт, на котором балансировщик будет принимать трафик.
Проверить синтаксис команды
yc load-balancer network-load-balancer create \
--region-id ru-central1 \
--name my-load-balancer \
--listener name=my-listener,external-ip-version=ipv4,port=80 
Затем подключите к балансировщику целевую группу (команда attach-target-group). Вам понадобится идентификатор целевой группы. Чтобы узнать его, запросите с помощью yc список доступных целевых групп и выберите ту, которую вы указали в спецификации specification.yaml. 
Проверить синтаксис команды
yc load-balancer target-group list 
Целевая группа также подключается с помощью нескольких подпараметров, которые соответствуют настройкам в консоли управления (их вы изучали на первом курсе). Для целевой группы укажите такие параметры:
target-group-id — идентификатор группы;
healthcheck-name, healthcheck-interval, healthcheck-timeout, healthcheck-unhealthythreshold, healthcheck-healthythreshold, healthcheck-http-port — параметры проверки состояния (см. документацию). Эти параметры аналогичны тем, что задаются в консоли управления при создании балансировщика. Вы изучали их в первом курсе.
Укажите 80-й порт, на котором запущен NGINX.
Проверить синтаксис команды
yc load-balancer network-load-balancer attach-target-group my-load-balancer \
--target-group target-group-id=<идентификатор целевой группы>,healthcheck-name=test-health-check,healthcheck-interval=2s,healthcheck-timeout=1s,healthcheck-unhealthythreshold=2,healthcheck-healthythreshold=2,healthcheck-http-port=80 
Можно не выполнять две команды (создание балансировщика и подключение целевой группы) по очереди, а одной командой create создать балансировщик с привязанной целевой группой.
Убедитесь, что балансировщик создан, а целевая группа подключена через консоль управления или с помощью yc.
Часть 3. Доступ к машинам группы
Проверьте состояние машин группы. Для этого запросите список машин и дождитесь статуса HEALTHY.
yc load-balancer network-load-balancer target-states my-load-balancer \
  --target-group-id <идентификатор_целевой_группы> 
Теперь откройте в браузере страницу балансировщика. IP-адрес балансировщика вы можете узнать с помощью консоли управления или yc.
На странице вы увидите приветственное сообщение и в нём идентификатор одной из машин.
image
Часть 4. Обновление Instance Group
При создании на ВМ группы была установлена ОС Ubuntu 18.04 LTS. Теперь обновите её до Ubuntu 20.04 LTS (ubuntu-2004-lts в столбце FAMILY). Ещё раз посмотрите список доступных образов (см. часть 1) и в файле спецификации specification.yaml измените параметр image_id.
...
boot_disk_spec:
mode: READ_WRITE
disk_spec:
   image_id: <идентификатор_образа>
   type_id: network-hdd
   size: 32g
... 
Теперь запустите обновление группы с изменённым файлом спецификации.
Проверить синтаксис команды
yc compute instance-group update \
--id <instance_group_id> \
--file <путь_к_файлу_specification.yaml> 
Группа будет обновляться постепенно: когда одна машина из группы удаляется, ей на замену создаётся новая. Общее число машин в группе не увеличится. Именно такую политику обновления мы задали в файле спецификации (см. часть 1):
...
deploy_policy:
  max_unavailable: 1
  max_expansion: 0
... 
Есть и другой режим обновления: сначала в группу добавляется ВМ с новой конфигурацией, а затем отключается старая машина. Это повторяется, пока не обновятся все машины. Такому режиму соответствовала бы другая конфигурация:
...
deploy_policy:
  max_unavailable: 0
  max_expansion: 1
... 
Убедитесь, что машины обновились. На приветственной странице должна выводиться новая версия ОС.
image
Часть 5. Удаление машины из группы
На приветственной странице балансировщика посмотрите имя активной машины и попробуйте удалить ее. Убедитесь, что приветственная страница остаётся доступна всё время: балансировщик переключит трафик на другую машину группы. А Yandex Cloud тем временем пересоздаст удалённую машину.
Проверить синтаксис команды
yc compute instance delete <имя_ВМ> 
image
Часть 6. Удаление Instance Group
Теперь удалите группу и балансировщик командами yc.
Проверить синтаксис команд
yc compute instance-group delete --name my-group 
yc load-balancer network-load-balancer delete --name my-load-balancer 
Кстати, ключевой параметр --name можно и не писать. Достаточно указать имя группы или балансировщика.
Убедитесь, что группы и балансировщика больше нет, через консоль управления или с помощью yc. 
Task:
В этой практической работе вы установите Packer, подготовите с его помощью образ, а затем создадите из образа виртуальную машину.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Установите Packer. Дистрибутив для вашей платформы можно скачать из зеркала. Он поддерживает все популярные операционные системы — Windows, macOS, Linux и FreeBSD.
Скачать дистрибутив Packer для вашей ОС можно, в том числе, с зеркала Yandex Cloud.
Подготовьте файл в формате HCL со спецификацией образа, например my-ubuntu-nginx.pkr.hcl.
При создании файла опирайтесь на документацию Packer.
В качестве примера можете взять спецификацию из предыдущего урока:
packer {
required_plugins {
  yandex = {
   version = "~> 1"
   source = "github.com/hashicorp/yandex"
  }
}
}
source "yandex" "ubuntu-nginx" {
token       = "<OAuth-токен>"
folder_id     = "<идентификатор_каталога>"
source_image_family = "ubuntu-2004-lts"
ssh_username    = "ubuntu"
use_ipv4_nat    = "true"
image_description = "my custom ubuntu with nginx"
image_family    = "ubuntu-2004-lts"
image_name     = "my-ubuntu-nginx"
subnet_id     = "<идентификатор_подсети>"
disk_type     = "network-ssd"
zone        = "ru-central1-a"
}
build {
sources = ["source.yandex.ubuntu-nginx"]
provisioner "shell" {
  inline = ["sudo apt-get update -y",
       "sudo apt-get install -y nginx",
       "sudo systemctl enable nginx.service"]
}

  Не забудьте подставить в спецификацию идентификаторы своего каталога и подсети (подсеть должна быть в той же зоне доступности, которая указана в параметре zone). Также укажите свой OAuth-токен (или воспользуйтесь переменной окружения YC_TOKEN при сборке образа).
Запустите команду установки плагина для работы с YC
packer init my-ubuntu-nginx.pkr.hcl
Теперь создайте образ ВМ на основе файла спецификации:
packer build <путь_к_файлу_my-ubuntu-nginx.pkr.hcl>
После того как команда отработает, убедитесь, что образ появился в каталоге. Для этого в консоли управления перейдите в сервис Compute Cloud. Найдите образ на вкладке Образы.
image
Перейдите на вкладку Виртуальные машины и начните создавать ВМ.
Раньше для создания загрузочного диска вы выбирали один из публичных образов, например Ubuntu 20.04. Теперь вместо этого переключитесь на вкладку Пользовательские. Нажмите кнопку Выбрать и в открывшемся окне переключитесь на вкладку Образ.
Выберите созданный образ и нажмите Применить.
Из образа создастся загрузочный диск.
image
Завершите создание ВМ.
Убедитесь, что веб-сервер работает — введите IP-адрес ВМ в адресную строку браузера.
Удалите ВМ, в следующих уроках она не понадобится. А вот образ удалять не стоит.
Task:
В этой практической работе вы установите Terraform и подготовите спецификацию, с помощью которой создадите виртуальную машину, а затем управляемую базу данных.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Установите Terraform. Дистрибутив для вашей платформы можно скачать из зеркала. После загрузки добавьте путь к папке, в которой находится исполняемый файл, в переменную PATH.
Настройте провайдер.
Создайте файл спецификации my-config.tf и укажите в нём Yandex Cloud в качестве провайдера.
terraform {
required_providers {
   yandex = {
   source = "yandex-cloud/yandex"
   }
}
}
provider "yandex" {
token = "<OAuth-токен>"
cloud_id = "<идентификатор_облака>"
folder_id = "<идентификатор_каталога>"
zone   = "<зона_доступности_по_умолчанию>"
}
Далее мы будем считать, что в качестве зоны доступности по умолчанию выбрана ru-central1-a.
Добавьте в файл блок, описывающий создание ВМ. Его сложно написать с нуля, поэтому опирайтесь на пример из документации. Чтобы вам было проще опознать в консоли управления объекты, созданные по этой спецификации, указывайте уникальные имена для ВМ, сети и подсети, а не оставляйте имена по умолчанию (default).
Для создания ВМ используйте образ, созданный с помощью Packer в предыдущей практической работе.
Можно использовать переменные в спецификации Terraform и передавать в них разные значения при запуске команд. Например, если сделать переменную для идентификатора образа image-id, тогда с помощью одного и того же файла спецификации вы сможете создавать ВМ с разным наполнением.
Переменные Terraform хранятся в файлах с расширением .tfvars. Создайте файл my-variables.tfvars и укажите в нём идентификатор своего образа Packer (узнайте идентификатор с помощью команды yc compute image list):
image-id = "<идентификатор_образа>"
В файле спецификации my-config.tf объявите эту переменную (ключевое слово variable). Тогда в секции, где описываются настройки ВМ, вы сможете обратиться к переменной как var.image-id:
...
variable "image-id" {
   type = string
}
resource "yandex_compute_instance" "vm-1" {
... 
   boot_disk {
     initialize_params {
       image_id = var.image-id
     }
   }
...
Скорректируйте описание для сети и подсети.
Для сети достаточно указать имя:
resource "yandex_vpc_network" "network-1" {
   name = "from-terraform-network"
}
Для подсети укажите зону доступности и сеть, а также внутренние IP-адреса, уникальные в рамках сети. Используйте адреса из адресного пространства 10.0.0.0/16.
resource "yandex_vpc_subnet" "subnet-1" {
   name     = "from-terraform-subnet"
   zone     = "ru-central1-a"
   network_id   = "${yandex_vpc_network.network-1.id}"
   v4_cidr_blocks = ["10.2.0.0/16"]
}
Проверьте синтаксис спецификации:
variable "image-id" {
type = string
}
resource "yandex_compute_instance" "vm-1" {
name = "from-terraform-vm"
platform_id = "standard-v1"
zone = "ru-central1-a"
resources {
  cores = 2
  memory = 2
}
boot_disk {
  initialize_params {
   image_id = var.image-id
  }
}
network_interface {
  subnet_id = yandex_vpc_subnet.subnet-1.id
  nat   = true
}
metadata = {
  ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
}
}
resource "yandex_vpc_network" "network-1" {
name = "from-terraform-network"
}
resource "yandex_vpc_subnet" "subnet-1" {
name     = "from-terraform-subnet"
zone     = "ru-central1-a"
network_id   = "${yandex_vpc_network.network-1.id}"
v4_cidr_blocks = ["10.2.0.0/16"]
}
output "internal_ip_address_vm_1" {
value = yandex_compute_instance.vm-1.network_interface.0.ip_address
}
output "external_ip_address_vm_1" {
value = yandex_compute_instance.vm-1.network_interface.0.nat_ip_address

Теперь попробуйте применить спецификацию. Перейдите в папку с файлом спецификации и выполните инициализацию.
terraform init
Если всё сделано верно, Terraform покажет сообщение:
...
Terraform has been successfully initialized!
...
Важно: выполняйте команды Terraform в папке, где находится файл спецификации.
Проверьте спецификацию с помощью команды terraform plan.
Terraform использует все файлы .tf из папки, в которой запущена команда. Поэтому название файла спецификации my-config.tf указывать не нужно: его Terraform подхватит и так.
Если файл с переменными называется стандартно (terraform.tfvars), его тоже можно не указывать при запуске команды. А если название файла нестандартное, то его нужно указывать:
terraform plan -var-file=my-variables.tfvars
Terraform выведет план: объекты, которые будут созданы, и т. п.:
...
Terraform will perform the following actions:
...
На самом деле необязательно помещать переменные в файл, их можно просто указывать при запуске команды. Поскольку у вас только одна переменная, это было бы несложно:
terraform plan -var="image-id=<идентификатор_образа>"
Создайте в облаке инфраструктуру по описанной вами спецификации. Выполните команду:
terraform apply -var-file=my-variables.tfvars
Terraform запросит подтверждение:
...
Do you want to perform these actions?
   Terraform will perform the actions described above.
   Only 'yes' will be accepted to approve.
   Enter a value:
В ответ введите yes.
Когда команда будет выполнена, вы увидите сообщение:
Apply complete! Resources: ... added, 0 changed, 0 destroyed.
Outputs:
external_ip_address_vm_1 = "84.201.133.49"
internal_ip_address_vm_1 = "10.2.0.24"
В консоли управления убедитесь, что ВМ создана. Откройте в браузере страницу с указанным IP-адресом и проверьте, доступна ли ВМ.
Как мы говорили на предыдущем уроке, Terraform хранит описание инфраструктуры в стейт-файлах. Посмотрите, как выглядит стейт-файл сейчас:
terraform state list
Вы увидите список объектов:
yandex_compute_instance.vm-1
yandex_vpc_network.network-1
yandex_vpc_subnet.subnet-1
Теперь добавьте в файл спецификации блок, описывающий создание кластера БД PostgreSQL. Подсказки ищите в справочнике ресурсов. Не забудьте заменить в спецификации имя подсети.
Проверьте синтаксис спецификации:
resource "yandex_mdb_postgresql_cluster" "postgres-1" {
name    = "postgres-1"
environment = "PRESTABLE"
network_id = yandex_vpc_network.network-1.id
config {
  version = 12
  resources {
   resource_preset_id = "s2.micro"
   disk_type_id   = "network-ssd"
   disk_size     = 16
  }
  postgresql_config = {
   max_connections         = 395
   enable_parallel_hash       = true
   vacuum_cleanup_index_scale_factor = 0.2
   autovacuum_vacuum_scale_factor  = 0.34
   default_transaction_isolation   = "TRANSACTION_ISOLATION_READ_COMMITTED"
   shared_preload_libraries     = "SHARED_PRELOAD_LIBRARIES_AUTO_EXPLAIN,SHARED_PRELOAD_LIBRARIES_PG_HINT_PLAN"
  }
}
database {
  name = "postgres-1"
  owner = "my-name"
}
user {
  name   = "my-name"
  password = "Test1234"
  conn_limit = 50
  permission {
   database_name = "postgres-1"
  }
  settings = {
   default_transaction_isolation = "read committed"
   log_min_duration_statement  = 5000
  }
}
host {
  zone   = "ru-central1-a"
  subnet_id = yandex_vpc_subnet.subnet-1.id
}
}
Сохраните файл спецификации.
Проверьте синтаксис спецификации:
terraform {
required_providers {
  yandex = {
   source = "yandex-cloud/yandex"
  }
}
}
provider "yandex" {
token = "<OAuth-токен>"
cloud_id = "<идентификатор_облака>"
folder_id = "<идентификатор_каталога>"
zone   = "ru-central1-a"
}
variable "image-id" {
type = string
}
resource "yandex_compute_instance" "vm-1" {
name = "from-terraform-vm"
platform_id = "standard-v1"
zone = "ru-central1-a"
resources {
  cores = 2
  memory = 2
}
boot_disk {
  initialize_params {
   image_id = var.image-id
  }
}
network_interface {
  subnet_id = yandex_vpc_subnet.subnet-1.id
  nat   = true
}
metadata = {
  ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
}
}
resource "yandex_vpc_network" "network-1" {
name = "from-terraform-network"
}
resource "yandex_vpc_subnet" "subnet-1" {
name     = "from-terraform-subnet"
zone     = "ru-central1-a"
network_id   = yandex_vpc_network.network-1.id
v4_cidr_blocks = ["10.2.0.0/16"]
}
resource "yandex_mdb_postgresql_cluster" "postgres-1" {
name    = "postgres-1"
environment = "PRESTABLE"
network_id = yandex_vpc_network.network-1.id
config {
  version = 12
  resources {
   resource_preset_id = "s2.micro"
   disk_type_id   = "network-ssd"
   disk_size     = 16
  }
  postgresql_config = {
   max_connections         = 395
   enable_parallel_hash       = true
   vacuum_cleanup_index_scale_factor = 0.2
   autovacuum_vacuum_scale_factor  = 0.34
   default_transaction_isolation   = "TRANSACTION_ISOLATION_READ_COMMITTED"
   shared_preload_libraries     = "SHARED_PRELOAD_LIBRARIES_AUTO_EXPLAIN,SHARED_PRELOAD_LIBRARIES_PG_HINT_PLAN"
  }
}
database {
  name = "postgres-1"
  owner = "my-name"
}
user {
  name   = "my-name"
  password = "Test1234"
  conn_limit = 50
  permission {
   database_name = "postgres-1"
  }
  settings = {
   default_transaction_isolation = "read committed"
   log_min_duration_statement  = 5000
  }
}
host {
  zone   = "ru-central1-a"
  subnet_id = yandex_vpc_subnet.subnet-1.id
}
}
output "internal_ip_address_vm_1" {
value = yandex_compute_instance.vm-1.network_interface.0.ip_address
}
output "external_ip_address_vm_1" {
value = yandex_compute_instance.vm-1.network_interface.0.nat_ip_address

Теперь примените обновлённую спецификацию. В папке с файлом спецификации выполните команду terraform plan:
terraform plan -var-file=my-variables.tfvars 
Если появляются сообщения об ошибках — исправьте ошибки и снова выполните команду.
Обновите инфраструктуру в соответствии с дополненной спецификацией командой terraform apply:
terraform apply -var-file=my-variables.tfvars 
Поскольку спецификация теперь включает создание БД, команда может выполняться довольно долго (около 10 минут).
В консоли управления откройте раздел Managed Service for PostgreSQL и убедитесь, что кластер postgres-1 создан и имеет статус Alive.
Проверьте, как изменился стейт-файл:
terraform state list 
В списке появился новый объект:
yandex_compute_instance.vm-1
yandex_mdb_postgresql_cluster.postgres-1
yandex_vpc_network.network-1
yandex_vpc_subnet.subnet-1 
Удалите инфраструктуру:
terraform destroy -var-file=my-variables.tfvars 
В конце вы увидите сообщение о выполнении команды:
...
Destroy complete! Resources: 4 destroyed. 
В консоли управления убедитесь, что объекты удалены.
Task:
В этой практической работе вы создадите реестр в Yandex Container Registry, подготовите Docker-образ виртуальной машины и поместите его в реестр, а затем создадите машину из этого образа.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Установите Docker.
Создайте реестр в Yandex Container Registry:
yc container registry create --name my-registry
Обратите внимание, что в выводе есть уникальный идентификатор (id) реестра. Он пригодится вам для следующих команд.
id: crpfpd8jhhldiqah91rc
folder_id: b1gfdbij3ijgopgqv9m9
name: my-registry
status: ACTIVE
created_at: "2021-04-06T00:46:48.150Z"
Аутентифицируйтесь в Yandex Container Registry с помощью Docker Credential helper. Это нужно для того, чтобы внешняя платформа Docker могла от вашего имени отправить образ в ваш приватный реестр в Yandex Cloud.
yc container registry configure-docker
Подготовьте Dockerfile. Можно использовать файл из урока о Docker:
FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y nginx
ENTRYPOINT ["nginx", "-g", "daemon off;"]
По умолчанию Docker использует файл с именем Dockerfile и без расширения.
Перейдите в папку с Dockerfile и соберите образ (не забудьте подставить идентификатор своего реестра):
docker build . -t cr.yandex/<идентификатор_реестра>/ubuntu-nginx:latest
Ключ -t позволяет задать образу имя.
Напоминаем, что в Yandex Container Registry можно загрузить только образы, названные по такому шаблону:
cr.yandex/<ID реестра>/<имя Docker-образа>:<тег>
Загрузите Docker-образ в реестр:
docker push cr.yandex/<идентификатор_реестра>/ubuntu-nginx:latest
В консоли управления перейдите в реестр и предоставьте всем пользователям право использовать хранящиеся образы. Для этого перейдите на вкладку Права доступа, в правом верхнем углу нажмите кнопку Назначить роли. В открывшемся окне нажмите кнопку Выбрать пользователя, на вкладке Группы выберите All users. Нажмите кнопку Добавить роль и последовательно введите viewer и container-registry.images.puller. Нажмите кнопку Сохранить.
image
В консоли управления создайте ВМ с помощью Container Optimized Image.
При создании машины в разделе Выбор образа загрузочного диска переключитесь на вкладку Container Solution и нажмите Настроить. Выберите из реестра созданный образ, остальные настройки оставьте по умолчанию и нажмите Применить.
image
Другие настройки ВМ мы уже разбирали.
Когда новая ВМ получит статус Running, найдите её внешний IP адрес в консоли управления и убедитесь, что по этому адресу отображается приветственная страница NGINX.
Обратите внимание! C помощью Docker-образа вы создали и запустили виртуальную машину с предустановленным, нужным вам ПО. При этом вам даже не потребовалось заходить внутрь ВМ и выполнять установку или настройку ПО вручную. 
Task:
В этой практической работе вы создадите кластер Kubernetes и группу узлов в нём.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Выберите каталог для кластера.
Выберите сервис Managed Service for Kubernetes. Нажмите кнопку Создать кластер. Дальше заполним настройки кластера:
image
Для Kubernetes необходим сервисный аккаунт для ресурсов и узлов.
Сервисный аккаунт для ресурсов — это аккаунт, под которым сервису Kubernetes будут выделяться ресурсы в нашем облаке.
Сервисный аккаунт для узлов необходим уже созданным узлам самого кластера Kubernetes для доступа к другим ресурсам. Например, чтобы получить Docker-образы из Container Registry.
Этим аккаунтам нужны разные права, и поэтому у них бывают разные роли. В общем случае вы можете использовать один и тот же сервисный аккаунт. Выберите аккаунт, который создали на первом курсе, или заведите новый.
Ключ шифрования Yandex Key Management Service позволяет защитить конфиденциальную информацию (пароли, OAuth-токены и SSH-ключи) и повысить безопасность. Это необязательно — кластер запустится и без ключа. Для этой практической работы не создавайте его.
Релизные каналы RAPID, REGULAR и STABLE отличаются процессом обновления и доступными вам версиями Kubernetes. 
RAPID и REGULAR содержат все версии, включая минорные. STABLE — только стабильные версии. RAPID обновляется автоматически, а в REGULAR и STABLE обновление можно отключить. Когда появляется обновление, информация о нём отображается в консоли управления.
Выберите REGULAR.
Внимательно выбирайте релизный канал! Изменить его после создания кластера Kubernetes нельзя.
Конфигурация мастера
Мастер — ведущая нода группы узлов кластера — следит за состоянием Kubernetes и запускает управляющие процессы. Сконфигурируем мастер:
image
Выберите версию Kubernetes. Их набор зависит от релизного канала. Версии мастера и других нод могут не совпадать, но это достаточно тонкая настройка, могут возникнуть проблемы совместимости, которые повлияют на работу всего кластера.
Кластеру может назначаться публичный IP-адрес. Выберите вариант Автоматически. В этом случае IP выбирается из пула свободных IP-адресов. Если вы не используете Cloud Interconnect или VPN для подключения к облаку, то без автоматического назначения IP-адресов вы не сможете подключиться к кластеру: он будет доступен только во внутренней сети вашего облака.
Тип мастера влияет на отказоустойчивость. Зональный работает только в одной зоне доступности, а региональный — в трёх подсетях в каждой зоне доступности.
Выберите зональный тип. В будущем для рабочей среды используйте региональные кластеры, а для разработки и тестирования — более дешёвые зональные.
Выбор типа мастера также влияет на подсети, в которых будет развёрнут кластер. У вас уже есть подсети, созданные по умолчанию для функционирования облака. Выберите их.
Настройки окна обновлений
image
Режимов обновления четыре: Отключено, В любое время, Ежедневно и В выбранные дни. Региональный мастер во время обновления остаётся доступен, зональный — нет.
Группа узлов кластера обновляется с выделением дополнительных ресурсов, так как при обновлении создаются узлы с обновлённой конфигурацией. При обновлении поды с контейнерами будут переезжать с одного узла на другой.
По умолчанию выставлен пункт В любое время. Оставьте его.
Сетевые настройки кластера
Сетевые политики для кластера Kubernetes необязательны. Эта опция включает сетевой контроллер Calico, который позволяет применять тонкие настройки политик доступа для кластера.
Не выбирайте эту опцию.
Во время работы кластера подам с контейнерами и сервисам самого кластера Kubernetes будут автоматически присваиваться внутренние IP-адреса. Чтобы IP-адреса подов и сервисов Kubernetes не пересеклись с другими адресами в вашем облаке, задайте CIDR (Classless Inter-Domain Routing — бесклассовая междоменная маршрутизация). Оставьте адреса пустыми: они будут назначены автоматически.
Маска подсети узлов влияет на количество подов, которые могут запускаться. Если адресов не хватит, под не запустится.
Вы заполнили все настройки, теперь нажмите Создать кластер. Дождитесь, пока статус кластера станет RUNNING, а состояние — HEALTHY. Это может занять около 10 минут.
Создание группы узлов
Зайдите в созданный кластер, перейдите на вкладку Управление узлами и нажмите Создать группу узлов. Группы узлов — это группы виртуальных машин.
Введите имя и описание группы, выберите версию Kubernetes. Выберите Автоматический тип масштабирования и количество узлов от 1 до 5.
В сетевых настройках задайте автоматический IP-адрес и выберите зону доступности (кластер зональный, поэтому зона доступности только одна). Задайте SSH-ключ, чтобы иметь доступ к виртуальным машинам кластера. Настройки обновления идентичны настройкам мастера.
image
Остальные настройки группы, которые мы не упомянули (вычислительные ресурсы, хранилище и т. д.), оставьте по умолчанию.
Нажмите Создать группу узлов и дождитесь, пока операция выполнится.
На следующих уроках мы продолжим работу с Kubernetes: создадим в кластере приложение и откроем ему доступ во внешний мир.
Task:
На прошлом уроке вы создали в консоли управления Yandex Cloud кластер Kubernetes и группу узлов в нём. Теперь с помощью командной строки вы развернете в кластере приложение — веб-сервер NGINX.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Основное средство взаимодействия с кластером — инструмент kubectl. Установите его по инструкции.
В консоли управления войдите в созданный кластер Managed Service for Kubernetes и нажмите кнопку Подключиться. В открывшемся окне скопируйте команду для подключения:
yc managed-kubernetes cluster get-credentials \
--id <идентификатор_кластера> \
--external
Чтобы проверить правильность установки и подключения, посмотрите на конфигурацию:
kubectl config view
Ответ получится примерно таким (IP-адрес сервера и название кластера будут отличаться):
apiVersion: v1
clusters:
- cluster:
   certificate-authority-data: DATA+OMITTED
   server: https://178.154.206.242
name: yc-managed-k8s-cat2oek6hbp7mnhhhr4m
contexts:
...
Создание манифеста
Для описания настроек приложения в кластере создадим файл my-nginx.yaml. Такой файл называется манифестом.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
spec:
replicas: 1
selector:
  matchLabels:
   app: nginx
template:
  metadata:
   labels:
    app: nginx
  spec:
   containers:
   - name: nginx
    image: cr.yandex/<идентификатор_реестра>/ubuntu-nginx:latest 
Рассмотрим, из чего он состоит.
Директива apiVersion определяет, для какой версии Kubernetes написан манифест. От версии к версии обозначение может меняться.
apiVersion: apps/v1
Директива kind описывает механизм использования. Она может принимать значения Deployment, Namespace, Service, Pod, LoadBalancer и т. д. Для развёртывания приложения укажите значение Deployment.
kind: Deployment
Директива metadata определяет метаданные приложения: имя, метки (labels), аннотации.
С помощью Меток можно идентифицировать, группировать объекты, выбирать их подмножества. Добавляйте и изменяйте метки при создании объектов или позднее, в любое время.
Аннотации используют, чтобы добавить собственные метаданные к объектам.
Укажем имя приложения:
metadata:
name: my-nginx-deployment
В основном блоке spec содержится описание объектов Kubernetes.
Директива replicas определяет масштабирование. Для первого запуска укажите, что приложению нужен один под. Позже вы посмотрите, как приложения масштабируются, и сможете увеличить число подов.
Директива selector определяет, какими подами будет управлять контейнер (подробнее о ней можно прочитать в документации). Поды отбираются с помощью метки (label).
Директива template определяет шаблон пода. Метка в шаблоне должна совпадать с меткой селектора — nginx.
В шаблоне содержится ещё одна, собственная директива spec. Она задаёт настройки контейнеров, которые будет развёрнуты на поде. Нам нужен один контейнер. Используйте для него образ, созданный ранее с помощью Docker и помещённый в реестр Yandex Container Registry.
spec: 
matchLabels: 
   app: nginx
replicas: 1
selector: ~
template: 
   metadata: 
   labels: 
     app: nginx
   spec: 
   containers: 
     - name: nginx
     image: "cr.yandex/<идентификатор_реестра>/ubuntu-nginx:latest"
Настройки манифеста для развёртывания приложения есть в документации Kubernetes.
Выполнение манифеста
Для создания или обновления ресурсов в кластере используется команда apply. Файл манифеста указывается после флага -f.
kubectl apply -f <путь_к_файлу_my-nginx.yaml>
Если результат будет успешным, вы увидите сообщение:
deployment.apps/my-nginx-deployment created
Чтобы убедиться, что приложение создано, посмотрите список подов:
kubectl get pods
Дождитесь статуса Running:
NAME                 READY STATUS  RESTARTS AGE
my-nginx-deployment-65b9b678b6-zmfww 1/1   Running 0     5m27s
Теперь получите более подробную информацию, выполнив ту же команду с флагом -o wide:
kubectl get pods -o wide
Вы увидите внутренний IP-адрес, который присвоен поду. Это пригодится, если нужно узнать, где именно развёрнуто приложение.
Чтобы получить максимально подробную информацию о запущенном приложении, используйте команду describe:
kubectl describe deployment/my-nginx-deployment
Масштабирование
Теперь увеличьте количество подов. Вручную это можно сделать двумя способами:
изменить файл манифеста, указав в директиве replicas нужное число подов, и снова выполнить команду apply;
если файла манифеста нет под рукой — использовать команду scale:
kubectl scale --replicas=3 deployment/my-nginx-deployment 
Если всё получится, в выводе команды kubectl get pods вы увидите сообщение:
NAME                 READY STATUS  RESTARTS AGE
my-nginx-deployment-65b9b678b6-6whpp 1/1   Running 0     117s
my-nginx-deployment-65b9b678b6-wtph9 1/1   Running 0     117s
my-nginx-deployment-65b9b678b6-zmfww 1/1   Running 0     14m 
На следующей практической работе мы посмотрим, как обращаться извне к кластеру Kubernetes и развёрнутому в нём приложению.
Кластер как код
Как видите, управление кластерами Kubernetes отлично вписывается в концепцию Infrastructure as Code: вы можете описать конфигурацию кластера в текстовом файле — манифесте. Вы также можете разворачивать кластеры Kubernetes с помощью Terraform.
Task:
Большинство веб-приложений созданы, чтобы взаимодействовать через интернет. Вы развернули в кластере приложение, но у вас пока нет к нему доступа из интернета. Чтобы исправить эту проблему, воспользуемся сервисом LoadBalancer.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
У созданного пода есть внутренний IP-адрес. 
Помните, мы говорили о том, что в кластере есть собственный сервис DNS? Он работает с внутренними IP-адресами объектов кластера, чтобы те могли взаимодействовать.
Однако внутренний IP-адрес может меняться, когда ресурсы группы узлов обновляются. Чтобы обращаться к приложению извне, требуется неизменный публичный IP-адрес — это и будет IP-адрес балансировщика.
Создайте файл-манифест load-balancer.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer
spec:
selector:
   app: nginx
ports:
- port: 80
   targetPort: 80
type: LoadBalancer
Где:
port — порт сетевого балансировщика, на котором будут обслуживаться пользовательские запросы;
targetPort — порт контейнера, на котором доступно приложение;
selector — метка селектора из шаблона подов в манифесте объекта Deployment.
Выполните манифест:
kubectl apply -f <путь_к_файлу_load-balancer.yaml>
Вы увидите сообщение:
service/my-loadbalancer created
В консоли управления откройте раздел Load Balancer. Там должен появиться балансировщик нагрузки с префиксом k8s в имени и уникальным идентификатором кластера Kubernetes.
Скопируйте IP-адрес балансировщика в адресную строку браузера. Вы увидите приветственную страницу NGINX.
Если при создании ресурсов вы получаете ошибку failed to ensure cloud loadbalancer: failed to start cloud lb creation: Permission denied, убедитесь, что вашему сервисному аккаунту хватает прав.
Task:
В этой работе вы увидите, как в Kubernetes® выполняется горизонтальное автомасштабирование.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Создайте манифест load-balancer-hpa.yaml.
   Для начала скопируйте в него настройки спецификаций, которые вы составляли на предыдущих уроках: из my-nginx.yaml (в примере ниже это раздел Deployment) и из load-balancer.yaml (раздел Service).
   Поскольку новый балансировщик должен отслеживать отдельную группу контейнеров, используйте для контейнеров другие метки (labels), например nginx-hpa.
---
### Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-loadbalancer-hpa
labels:
  app: nginx-hpa
spec:
replicas: 1
selector:
  matchLabels:
     app: nginx-hpa
template:
  metadata:
     name: nginx-hpa
     labels:
       app: nginx-hpa
  spec:
     containers:
       - name: nginx-hpa
       image: cr.yandex/<registry ID>/ubuntu-nginx:latest
---
### Service
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer-hpa
spec:
selector:
   app: nginx-hpa
ports:
  - protocol: TCP
   port: 80
   targetPort: 80
type: LoadBalancer 
В разделе Deployment смените образ с Yandex Container Registry на k8s.gcr.io/hpa-example — это специальный тестовый образ из публичного репозитория, создающий высокую нагрузку на процессор. Так вам будет удобно отслеживать работу Horizontal Pod Autoscaler.
   ...
   spec:
     containers:
       - name: nginx-hpa
       image: k8s.gcr.io/hpa-example 
Теперь добавьте в шаблон контейнера настройки requests и limits: мы попросим по умолчанию 256 мебибайтов памяти и 500 милли-CPU (половину ядра), а ограничим контейнер 500 мебибайтами и 1 CPU.
  ...
  spec:
   containers:
   - name: nginx-hpa
     image: k8s.gcr.io/hpa-example
     resources:
     requests:
       memory: "256Mi"
       cpu: "500m"
     limits:
       memory: "500Mi"
       cpu: "1" 
Дополните манифест настройками для Horizontal Pod Autoscaler:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: my-hpa
spec:
scaleTargetRef:
  apiVersion: apps/v1
  kind: Deployment
  name: my-nginx-deployment-hpa
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 20 
В результате должен получиться такой манифест:
---
### Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment-hpa
labels:
  app: nginx-hpa
spec:
replicas: 1
selector:
  matchLabels:
   app: nginx-hpa
template:
  metadata:
   name: nginx-hpa
   labels:
    app: nginx-hpa
  spec:
   containers:
    - name: nginx-hpa
     image: k8s.gcr.io/hpa-example
     resources:
      requests:
       memory: "256Mi"
       cpu: "500m"
      limits:
       memory: "500Mi"
       cpu: "1"
---
### Service
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer-hpa
spec:
selector:
  app: nginx-hpa
ports:
  - protocol: TCP
   port: 80
   targetPort: 80
type: LoadBalancer
---
### HPA
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: my-hpa
spec:
scaleTargetRef:
  apiVersion: apps/v1
  kind: Deployment
  name: my-nginx-deployment-hpa
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 20 
Примените манифест:
kubectl apply -f <путь_к_load-balancer-hpa.yaml> 
Вы увидите три сообщения:
deployment.apps/my-nginx-deployment-hpa created
service/my-loadbalancer-hpa created
horizontalpodautoscaler.autoscaling/my-hpa created 
В консоли управления перейдите в раздел Network Load Balancer. Дождитесь, пока статус my-nginx-deployment-hpa станет Running, после чего посмотрите IP-адрес балансировщика. Убедитесь, что в браузере этот адрес доступен. В терминале сохраните IP-адрес в переменную. Например, так:
LOAD_BALANCER_IP=<IP-адрес балансировщика> 
Запустите в отдельном окне отслеживание интересующих вас компонентов кластера Kubernetes:
while true; do kubectl get pod,svc,hpa,nodes -o wide; sleep 5; done 
Теперь сымитируйте рабочую нагрузку на приложение. Для этого подойдёт утилита wget (установите её с помощью пакетного менеджера или с сайта).
while true; do wget -q -O- http://$LOAD_BALANCER_IP; done 
Вы увидите, что сначала увеличится число подов, а затем добавятся узлы. Число узлов ограничено настройками группы узлов кластера, которые вы задали при создании кластера (в нашем случае максимальное количество узлов — пять). 
Остановите цикл создания нагрузки на приложение (комбинация клавиш Ctrl + C). В окне консоли с отслеживанием компонентов кластера вы увидите, как удаляются узлы и поды без нагрузки.
Task:
Давайте посмотрим, как принципы построения отказоустойчивых систем реализованы в Yandex Cloud. В практических работах этой темы вы проверите четыре основных сценария отказов:
сбой виртуальной машины,
сбой всей зоны доступности,
обновление приложения
сбой приложения.
Вы сымитируете эти отказы и понаблюдаете, как Yandex Cloud обеспечивает доступность приложения и восстанавливает инфраструктуру после сбоев.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Начнем с самого простого сценария — сбоя виртуальной машины.
Создайте группу из трёх ВМ в трёх зонах доступности под балансировщиком нагрузки. Используйте образ с ОС Ubuntu 18.04 (потом мы обновим его на более свежую версию ОС).
Используйте спецификацию specification.yaml из практической работы по CLI Yandex Cloud, но адаптируйте её для того, чтобы на ней можно было проверить разные сценарии сбоев.
Во-первых, будут задействованы все три зоны доступности, поэтому нужно немного исправить блок allocation_policy:
allocation_policy:
  zones:
    - zone_id: ru-central1-a
    - zone_id: ru-central1-b
    - zone_id: ru-central1-d 
Также пропишите подсети для каждой зоны (не забывайте подставлять идентификаторы ваших подсетей):
  network_interface_specs:
    - network_id: <идентификатор_сети>
     subnet_ids: 
      - <идентификатор_подсети_№1>
      - <идентификатор_подсети_№2>
      - <идентификатор_подсети_№3> 
     primary_v4_address_spec: { one_to_one_nat_spec: { ip_version: IPV4 }} 
Во-вторых, в секции #cloud-config укажите пользователя, которого нужно создать для входа в виртуальные машины по SSH (это понадобится позднее, на одной из следующих практических работ):
    users:
     - name: my-user
      groups: sudo
      lock_passwd: true
      sudo: 'ALL=(ALL) NOPASSWD:ALL'
      ssh-authorized-keys:
       - ssh-rsa AAAAB3Nza... 
Обновленный файл спецификации specification.yaml
name: my-group
service_account_id: <идентификатор_сервисного_аккаунта>
instance_template:
  platform_id: standard-v2
  resources_spec:
    memory: 2g
    cores: 2
  boot_disk_spec:
    mode: READ_WRITE
    disk_spec:
      image_id: <идентификатор_образа_Ubuntu_18.04> 
      type_id: network-hdd
      size: 32g
  network_interface_specs:
    - network_id: <идентификатор_сети>
     subnet_ids: 
      - <идентификатор_подсети_№1>
      - <идентификатор_подсети_№2>
      - <идентификатор_подсети_№3> 
     primary_v4_address_spec: { one_to_one_nat_spec: { ip_version: IPV4 }}
  scheduling_policy:
    preemptible: false
  metadata:
   user-data: |-
    #cloud-config
     users:
      - name: my-user
       groups: sudo
       lock_passwd: true
       sudo: 'ALL=(ALL) NOPASSWD:ALL'
       ssh-authorized-keys:
        - <содержимое_публичной_части_SSH-ключа>
     package_update: true
     runcmd:
      - [ apt-get, install, -y, nginx ]
      - [/bin/bash, -c, 'source /etc/lsb-release; sed -i "s/Welcome to nginx/It is $(hostname) on $DISTRIB_DESCRIPTION/" /var/www/html/index.nginx-debian.html']
deploy_policy:
  max_unavailable: 1
  max_expansion: 0
scale_policy:
  fixed_scale:
    size: 3
allocation_policy:
  zones:
    - zone_id: ru-central1-a
    - zone_id: ru-central1-b
    - zone_id: ru-central1-d
load_balancer_spec:
  target_group_spec:
    name: my-target-group 
Создайте группу по новой спецификации:
yc compute instance-group create --file <путь_к_файлу_specification.yaml> 
Если ранее вы удаляли балансировщик нагрузки, создайте его снова и привяжите к целевой группе:
yc load-balancer network-load-balancer create \
--region-id ru-central1 \
--name my-load-balancer \
--listener name=my-listener,external-ip-version=ipv4,port=80 \
--target-group target-group-id=<идентификатор_целевой_группы>,healthcheck-name=test-health-check,healthcheck-interval=2s,healthcheck-timeout=1s,healthcheck-unhealthythreshold=2,healthcheck-healthythreshold=2,healthcheck-http-port=80 
В консоли управления убедитесь, что ресурсы созданы. Проверьте вывод по внешнему IP-адресу балансировщика — должна отображаться приветственная страница с идентификатором одной из виртуальных машин группы.
Начните отслеживать состояние виртуальных машин группы и целевой группы балансировщика:
while true; do \
yc compute instance-group \
--id <идентификатор_группы_ВМ> list-instances; \
yc load-balancer network-load-balancer \
--id <идентификатор_балансировщика> target-states \
--target-group-id <идентификатор_целевой_группы>; \
sleep 5; done 
Информация выводится в виде таблиц:
+----------------------+---------------------------+----------------+-------------+------------------------+----------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |     STATUS     | STATUS MESSAGE |
+----------------------+---------------------------+----------------+-------------+------------------------+----------------+
| ef34nv4tp3ha8gl6p3df | cl1m5ksvljnq5frekghi-uzex | 84.201.148.207 | 10.128.0.42 | RUNNING_ACTUAL [1m54s] |        |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [13m] |        |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [6h]  |        |
+----------------------+---------------------------+----------------+-------------+------------------------+----------------+
+----------------------+-------------+---------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+---------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e2luooifg8ruecr7g6fk | 10.128.0.6 | HEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+---------+
Сбой виртуальной машины может произойти из-за падения физического хоста, на котором она запущена. Иногда виртуальную машину могут удалить случайно, по ошибке. Чтобы сымитировать сбой, удалим одну из виртуальных машин в группе через консоль управления.
Если бы это была единственная машина, на которую поступает трафик, система стала бы недоступна. Но у нас система развернута на нескольких виртуальных машинах, поэтому трафик будет перенаправлен на две оставшиеся. Через несколько секунд будет обнаружена проблема, и виртуальная машина будет выведена из-под балансировки. Об этом говорит статус UNHEALTHY. 
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS    | STATUS MESSAGE |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
| ef330no5frc5de91v77n | cl1m5ksvljnq5frekghi-uzex | 84.201.147.33 | 10.128.0.6 | RUNNING_ACTUAL [15m] |        |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [32m] |        |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [6h] |        |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
+----------------------+-------------+-----------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+-----------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e2luooifg8ruecr7g6fk | 10.128.0.6 | UNHEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+-----------+ 
Далее подсеть перейдет в статус DRAINING — ресурс удаляется, и с него снимается трафик. Балансировщик перестает передавать трафик этому ресурсу.
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS    | STATUS MESSAGE |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
| ef330no5frc5de91v77n | cl1m5ksvljnq5frekghi-uzex | 84.201.147.33 | 10.128.0.6 | CLOSING_TRAFFIC [0s] |        |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [33m] |        |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [6h] |        |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
+----------------------+-------------+----------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+----------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e2luooifg8ruecr7g6fk | 10.128.0.6 | DRAINING |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+----------+
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS    | STATUS MESSAGE |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
| ef330no5frc5de91v77n | cl1m5ksvljnq5frekghi-uzex | 84.201.147.33 | 10.128.0.6 | CLOSING_TRAFFIC [9s] |        |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [33m] |        |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [6h] |        |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
+----------------------+-------------+----------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+----------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+----------+ 
После этого Instance Group начнет пересоздавать удалённую виртуальную машину. Процесс восстановления может занять некоторое время. Понаблюдаем за ним.
Сначала новая виртуальная машина появится в группе в статусе CREATING_INSTANCE.
+----------------------+---------------------------+----------------+-------------+-------------------------+----------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |     STATUS     | STATUS MESSAGE |
+----------------------+---------------------------+----------------+-------------+-------------------------+----------------+
| ef330no5frc5de91v77n | cl1m5ksvljnq5frekghi-uzex | 84.201.147.33 | 10.128.0.6 | CREATING_INSTANCE [-1s] |        |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [33m]  |        |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [6h]   |        |
+----------------------+---------------------------+----------------+-------------+-------------------------+----------------+
+----------------------+-------------+----------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+----------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+----------+
Далее виртуальная машина будет открыта для трафика (статус OPEN_TRAFFIC). Балансировщик начнет процесс включения машины в список доступных машин.
+----------------------+---------------------------+----------------+-------------+-----------------------+--------------------------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS     |     STATUS MESSAGE     |
+----------------------+---------------------------+----------------+-------------+-----------------------+--------------------------------+
| ef374t9ghea78p471gal | cl1m5ksvljnq5frekghi-uzex | 84.252.135.153 | 10.128.0.32 | OPENING_TRAFFIC [-1s] | Adding target(s)       |
|           |             |        |       |           | 10.128.0.32 to target group  |
|           |             |        |       |           | b7rh0bhm9f82dglb2p9r     |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [34m] |                |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [7h] |                |
+----------------------+---------------------------+----------------+-------------+-----------------------+--------------------------------+
+----------------------+-------------+---------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+---------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+---------+ 
+----------------------+---------------------------+----------------+-------------+----------------------+--------------------------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS    |     STATUS MESSAGE     |
+----------------------+---------------------------+----------------+-------------+----------------------+--------------------------------+
| ef374t9ghea78p471gal | cl1m5ksvljnq5frekghi-uzex | 84.252.135.153 | 10.128.0.32 | OPENING_TRAFFIC [1s] | Adding target(s)       |
|           |             |        |       |           | 10.128.0.32 to target group  |
|           |             |        |       |           | b7rh0bhm9f82dglb2p9r     |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [34m] |                |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [7h] |                |
+----------------------+---------------------------+----------------+-------------+----------------------+--------------------------------+
+----------------------+-------------+---------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+---------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e2luooifg8ruecr7g6fk | 10.128.0.32 | INITIAL |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+---------+
+----------------------+---------------------------+----------------+-------------+-----------------------+--------------------------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS     |     STATUS MESSAGE     |
+----------------------+---------------------------+----------------+-------------+-----------------------+--------------------------------+
| ef374t9ghea78p471gal | cl1m5ksvljnq5frekghi-uzex | 84.252.135.153 | 10.128.0.32 | OPENING_TRAFFIC [18s] | Awaiting HEALTHY state for   |
|           |             |        |       |           | target(s) 10.128.0.32. Elapsed |
|           |             |        |       |           | time: 3s.           |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [34m] |                |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [7h] |                |
+----------------------+---------------------------+----------------+-------------+-----------------------+--------------------------------+
+----------------------+-------------+----------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+----------+
| b0c4h992tbuodl5hudpu | 10.128.0.32 | INITIAL |
| b0c4h992tbuodl5hudpu | 10.128.0.37 | INACTIVE |
| b0c4h992tbuodl5hudpu | 10.128.0.9 | HEALTHY |
+----------------------+-------------+----------+
+----------------------+---------------------------+----------------+-------------+-------------------------+--------------------------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |     STATUS     |     STATUS MESSAGE     |
+----------------------+---------------------------+----------------+-------------+-------------------------+--------------------------------+
| ef374t9ghea78p471gal | cl1m5ksvljnq5frekghi-uzex | 84.252.135.153 | 10.128.0.32 | OPENING_TRAFFIC [1m32s] | [NLB unhealthy]; Awaiting   |
|           |             |        |       |             | HEALTHY state for target(s)  |
|           |             |        |       |             | 10.128.0.32. Elapsed time: 1m |
|           |             |        |       |             | 17s.             |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [35m]  |                |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [7h]   |                |
+----------------------+---------------------------+----------------+-------------+-------------------------+--------------------------------+
+----------------------+-------------+---------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+---------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e2luooifg8ruecr7g6fk | 10.128.0.32 | HEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+---------+ 
И в завершение подсеть перейдет в статус HEALTHY, а машина — в статус RUNNING_ACTUAL, и трафик будет снова разделен между тремя машинами.
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
|   INSTANCE ID   |     NAME      | EXTERNAL IP | INTERNAL IP |    STATUS    | STATUS MESSAGE |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
| ef374t9ghea78p471gal | cl1m5ksvljnq5frekghi-uzex | 84.252.135.153 | 10.128.0.32 | RUNNING_ACTUAL [-1s] |        |
| ef3nquhoicdq0ccl0tlq | cl1m5ksvljnq5frekghi-iduv | 84.201.171.248 | 10.128.0.9 | RUNNING_ACTUAL [35m] |        |
| ef3oio9su52imaod7rad | cl1m5ksvljnq5frekghi-ixac | 84.252.132.4 | 10.128.0.37 | RUNNING_ACTUAL [7h] |        |
+----------------------+---------------------------+----------------+-------------+----------------------+----------------+
+----------------------+-------------+---------+
|   SUBNET ID   | ADDRESS | STATUS |
+----------------------+-------------+---------+
| b0c4h992tbuodl5hudpu | 10.128.0.37 | HEALTHY |
| e2luooifg8ruecr7g6fk | 10.128.0.32 | HEALTHY |
| e9bn57jvjnbujnmk3mba | 10.128.0.9 | HEALTHY |
+----------------------+-------------+---------+ 
Восстановление произошло автоматически без ручного вмешательства.
Обратите внимание! Эту группу виртуальных машин мы будем использовать и в трех следующих практических работах, не удаляйте её. Если вы будете делать большой перерыв между практическими работами, вы можете остановить группу (чтобы не расходовать средства на балансе), а затем запустить её снова.
Task:
В этом сценарии рассмотрим ситуацию, когда произошел сбой сразу всей зоны доступности. Такие ситуации возникают крайне редко и могут быть связаны с какими-то масштабными стихийными бедствиями, однако и их стоит предусмотреть.
Посмотрим, как будет решаться проблема неожиданного выхода из строя зоны доступности.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
В нашем примере (см. предыдущий урок) используются все три зоны доступности — ru-central1-a, ru-central1-b и ru-central1-d. В каждой зоне располагается одна ВМ.
Установите такие настройки политики развертывания — пусть группу можно расширять на 1 ВМ и уменьшать на 1 ВМ:
image
Теперь в настройках группы виртуальных машин уберите одну зону доступности, например ru-central1-d. Переключитесь на вкладку Список ВМ и посмотрите, что будет происходить.
Для ВМ, которая располагалась в зоне ru-central1-d, отключается трафик (статус Closing traffic), а затем сама машина удаляется (статус Deleting instance). Одновременно в другой зоне доступности создаётся и запускается новая ВМ. Остальные машины в группе продолжают работать без изменений.
Таким образом, даже при выходе из строя всей зоны доступности группа виртуальных машин продолжит работать и будет способна принимать прежнюю нагрузку.
Task:
В практической работе с CLI вы уже научились обновлять операционную систему для группы виртуальных машин. Любые приложения, установленные на ВМ, обновляются по тем же правилам. Давайте рассмотрим этот процесс ещё раз.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Первый вариант обновления
Если вы работаете в консоли управления, измените шаблон ВМ и выберите образ с ОС Ubuntu 22.04. Убедитесь, что параметры политики развертывания такие: группу нельзя расширять, а уменьшать можно только на одну ВМ.
image
Политика развёртывания группы виртуальных машин (вариант 1)
Если вы работаете в командной строке, в спецификации specification.yaml измените параметр image_id (например с fd8s2gbn4d5k2rcf12d9 на fd8l45jhe4nvt0ih7h2e) и запустите обновление группы:
yc compute instance-group update \
--id <instance_group_id> \
--file <путь_к_файлу_specification.yaml> 
В консоли управления на странице группы ВМ перейдите на вкладку Список ВМ и проследите, как меняются статусы машин.
Сначала вы увидите статус Running outdated. Это означает, что машины работают со старой версией приложения.
Затем одна из машин начинает обновляться: для неё закрывается трафик (статус Closing traffic), она останавливается (статус Stopping instance), обновляется (статус Updating instance), затем трафик снова открывается (статус Opening traffic), и наконец статус меняется на Running actual. Обновление выполнено.
Затем то же самое последовательно выполняется для остальных машин в группе.
Порядок обновления зависит от политики развёртывания. Мы запретили увеличивать размер группы и указали, что одновременно неработоспособной может быть только одна машина. Именно так и произошло обновление: машины по одной выводились из строя, обновлялись и запускались снова.
Второй вариант обновления
Теперь давайте изменим настройки политики развёртывания.
Если вы работаете в консоли управления, измените шаблон ВМ и выберите образ с Ubuntu и NGINX, созданный ранее и помещённый в Container Registry.
Измените параметры развёртывания. Теперь группу можно расширять на одну ВМ, а уменьшать нельзя:
image
Политика развёртывания группы виртуальных машин (вариант 2)
Если вы работаете в командной строке, в спецификации specification.yaml измените параметр image_id (например, снова с fd8ju9iqf6g5bcq77jns на fd8s2gbn4d5k2rcf12d9). Параметры обновления измените так:
deploy_policy:
  max_unavailable: 0
  max_expansion: 1 
Запустите обновление группы.
В консоли управления на странице группы ВМ перейдите на вкладку Список ВМ и проследите, как меняются статусы машин.
Сначала вы увидите статус Running outdated. Затем создаётся новая машина (статус Creating instance), для неё открывается трафик (статус Opening traffic), статус машины меняется на Running actual, при этом одна из «устаревших» ВМ выводится из строя (статусы Closing traffic и Stopping instance).
Затем то же самое последовательно выполняется для остальных машин в группе.
Task:
Последний сценарий, который мы рассмотрим, это сбой приложения. Ситуация, когда сама ВМ работоспособна, но по каким-то причинам произошла ошибка в приложении. Это может быть потеря соединения с базой данных или какой-то баг в запущенном приложении (например утечка памяти). Давайте сымитируем такой сценарий. На наших виртуальных машинах запущен только веб-сервер NGINX, давайте остановим его. Но сначала включим проверку состояния ВМ.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
В консоли управления откройте вкладку Обзор для вашей группы виртуальных машин, нажмите кнопку Изменить и активируйте проверку состояний. Сохраните изменения.
image
В браузере откройте страницу с внешним IP-адресом балансировщика, привязанного к вашей группе, и посмотрите, на какую из машин выводится трафик. Узнайте внешний IP-адрес этой машины.
В новой вкладке браузера откройте IP-адрес этой виртуальной машины и убедитесь, что выводится приветственная страница, т. е. сервер доступен.
Помните, когда вы меняли файл конфигурации для группы машин, вы добавили в него пользователя my-user? Теперь он вам пригодится — из консоли зайдите на ВМ от его имени:
ssh my-user@<внешний_IP-адрес_ВМ>
Посмотрите список запущенных процессов:
ps axu
Убедитесь, что в списке есть процессы nginx:
image
Теперь остановите эти процессы, чтобы сделать сервер недоступным:
sudo killall nginx
В браузере обновите страницу балансировщика. Вы увидите, что теперь трафик направляется на другую виртуальную машину группы. Это означает, что Instance Group обнаружил сбой приложения и переключил трафик.
Теперь обновите страницу виртуальной машины, на которой вы остановили NGINX. Убедитесь, что сервер теперь недоступен.
Откройте список машин вашей группы и проследите, как меняется состояние одной из машин.
Сначала будет закрыт трафик (статус Closing traffic), затем виртуальная машина будет остановлена (статус Stopping instance), а затем перезапущена (статус Running actual).
Убедитесь, что веб-сервер на этой ВМ снова доступен.
Мы проверили четыре основных сценария сбоев и убедились, что Yandex Cloud автоматически отрабатывает их и восстанавливает работоспособность группы.
Теперь вы можете удалить группу виртуальных машин, в этом курсе она больше не понадобится.
Task:
Часто бывает полезно отслеживать более широкий набор метрик, чем тот, что доступен в Yandex Monitoring «из коробки».
Предположим, вам интересно узнать, сколько людей заходит на ваш сайт и как их число зависит от времени дня или дня недели. Вы можете выгружать эти данные из Яндекс Метрики или вашей собственной аналитической системы и самостоятельно загружать в Yandex Monitoring с помощью API.
Давайте попробуем сделать это с нашим сайтом.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Отправка метрик через API
Получите IAM-токен:
Инструкция для аккаунта на Яндексе.
Инструкция для сервисного аккаунта.
Обратите внимание — токены устаревают через 12 часов после создания. Поэтому если вы сделаете паузу при выполнении данной практической работы, для продолжения лучше запросить новый токен.
Сохраните токен в переменной окружения, так его будет проще использовать:
export IAM_TOKEN=<IAM-токен>
Создайте файл с телом запроса, например my-metrics.json. В свойстве metrics указывается список метрик для записи. Пусть это будет количество пользователей сайта. В массиве timeseries указываются значения на разные моменты времени (измените число на сегодняшнее в формате год-месяц-день).
{
"metrics": [
  {
   "name": "number_of_users",
   "labels": {
   "site": "aibolit"
   },
   "type": "IGAUGE",
   "timeseries": [
    {
     "ts": "2021-05-10T10:00:00Z",
     "value": "22"
    },
    {
     "ts": "2021-05-10T11:00:00Z",
     "value": "44"
    },
    {
     "ts": "2021-05-10T12:00:00Z",
     "value": "11"
    },
    {
     "ts": "2021-05-10T13:00:00Z",
     "value": "55"
    },
    {
     "ts": "2021-05-10T14:00:00Z",
     "value": "33"
    }
   ]
  }
]

Отправьте запрос, указав в нем идентификатор каталога и имя сервиса custom (это имя указывается для всех пользовательских метрик):
curl -X POST \
   -H "Content-Type: application/json" \
   -H "Authorization: Bearer ${IAM_TOKEN}" \
   -d '@<путь_к_файлу_my-metrics.json>' \
'https://monitoring.api.cloud.yandex.net/monitoring/v2/data/write?folderId=<идентификатор_каталога>&service=custom'
При выполнении этой команды может вернуться ошибка: 
{"writtenMetricsCount":0,"errorMessage":"Response not OK from all clusters\nUNKNOWN_SHARD: Metrics storage is not initialized yet, please wait\nUNKNOWN_SHARD: Metrics storage is not initialized yet, please wait"}
Если это произошло - подождите несколько минут и повторите запрос.
Мониторинг пользовательских метрик
Создайте на вашем дашборде новый виджет с графиком, назовите его «Число пользователей сайта».
В виджете создайте запрос с параметрами service = Custom Metrics и name = number_of_users. Убедитесь, что в виджете выбран нужный период:
image
Этот график станет нагляднее, если вместо точек отображать столбцы. Тип графика можно изменить с помощью кнопки в правом верхнем углу виджета:
image
Мониторинг метрик Linux
Другой пример — ваши приложения запущены на виртуальных машинах под Linux. По умолчанию вы можете посмотреть утилизацию ресурсов процессора или диска для ВМ в целом. Но вам будет полезно знать, сколько ресурсов потребляет каждое из них. В Yandex Monitoring вы можете отслеживать системные метрики Linux, такие как объём свободной памяти или загрузка процессора. Но для этого нужно дополнительно настроить отправку этих метрик с помощью Yandex Unified Agent, который мы уже упоминали.
Установка Yandex Unified Agent
Создайте виртуальную машину. На неё вы будете устанавливать Yandex Unified Agent. Можете использовать образ с ОС Ubuntu, который вы создали ранее и поместили в Container Registry. Назовите машину, например, for-ua.
При создании используйте ваш сервисный аккаунт. Задайте логин (например ua-user) и ssh-ключ.
Для сервисного аккаунта добавьте роль monitoring.editor.
Посмотрите публичный IP-адрес машины for-ua и зайдите на неё по ssh:
ssh ua-user@<публичный_адрес_ВМ> 
Теперь вы можете установить Yandex Unified Agent:
ua_version=$(curl -s https://storage.yandexcloud.net/yc-unified-agent/latest-version) bash -c 'curl -s -O https://storage.yandexcloud.net/yc-unified-agent/releases/$ua_version/unified_agent && chmod +x ./unified_agent' 
Также вы можете выбрать опцию Установить в поле Агент сбора метрик при создании ВМ, тогда Yandex Unified Agent будет установлен автоматически.
Создайте файл config.yml с типовой спецификацией для доставки метрик Linux.
В параметре folder_id укажите идентификатор вашего каталога.
status:
port: "16241"
storages:
- name: main
   plugin: fs
   config:
   directory: /var/lib/yandex/unified_agent/main
   max_partition_size: 100mb
   max_segment_size: 10mb
channels:
- name: cloud_monitoring
   channel:
   pipe:
     - storage_ref:
       name: main
   output:
     plugin: yc_metrics
     config:
     folder_id: "<идентификатор_каталога>"
     iam:
       cloud_meta: {}
routes:
- input:
   plugin: linux_metrics
   config:
   namespace: sys
channel:
  channel_ref:
   name: cloud_monitoring
- input:
   plugin: agent_metrics
   config:
   namespace: ua
channel:
   pipe:
   - filter:
     plugin: filter_metrics
     config:
       match: "{scope=health}"
   channel_ref:
   name: cloud_monitoring
import:
- /etc/yandex/unified_agent/conf.d/*.yml 
В секции status достаточно указать порт для просмотра статуса Yandex Unified Agent.
Секция storage содержит список хранилищ, в которых будут находиться выгруженные данные. Для практической работы достаточно одного файлового хранилища (fs).
Секция channels содержит список именованных каналов, к этим каналам можно обращаться по имени из других секций спецификации. Здесь обозначен один канал с именем cloud_monitoring. К нему идёт обращение из секции routes, которая содержит список маршрутов доставки метрик.
Подробнее о конфигурировании Yandex Unified Agent вы можете почитать в документации.
Скопируйте файл спецификации в виртуальную машину for-ua:
scp config.yml ua-user@84.252.135.237:config.yml 
Теперь запустите Unified Agent с созданной спецификацией:
sudo ./unified_agent --config config.yml 
Если запуск прошел успешно, в конце вы увидите сообщение такого вида:
... NOTICE agent started 
Настройка виджета для мониторинга метрик Linux
Создайте на вашем дашборде новый виджет с графиком, назовите его «Метрики Linux».
В виджете создайте запрос с параметром service = Custom Metrics. В параметре name выберите любой параметр, начинающийся с sys — всё это системные метрики, поставляемые Unified Agent. Например, name = sys.memory.MemAvailable.
image
Теперь в виджете отображается график наличия свободной оперативной памяти в виртуальной машине for-ua.
Task:
Как мы уже говорили раньше, метрики можно выгружать из Yandex Cloud Monitoring в сторонние приложения и сервисы. Пожалуй, чаще всего их выгружают для сервера Prometheus.
На сегодняшний день Prometheus — один из самых популярных инструментов для мониторинга приложений и сервисов. В основе его лежит специализированная СУБД для анализа временных рядов, которая обеспечивает высокое быстродействие. В отличие от большинства систем мониторинга, Prometheus не ждёт, пока сторонние приложения передадут ему свои метрики, а сам опрашивает подключенные к нему приложения и собирает нужные данные.
Prometheus и Yandex Cloud Monitoring решают схожие задачи — хранят значения разных метрик. Prometheus фактически является стандартом для обмена метриками. Поэтому даже используя сервисы Yandex Cloud, IT-администраторы часто хотят отслеживать их работу с помощью Prometheus. Чтобы не лишать специалистов привычных инструментов, Yandex Cloud Monitoring поддерживает выгрузку данных в формате Prometheus. Для этого используется метод prometheusMetrics.
Для визуализации данных, собираемых Prometheus, можно использовать сервис Grafana (в нем можно зарегистрироваться бесплатно на тестовый период). Вы можете установить Grafana на свой компьютер, а можете работать в облачной версии.
Посмотрим, как происходит выгрузка метрик в Prometheus и работа с ними в Grafana. Вы снова будете мониторить сайт клиники «Доктор Айболит».
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Подготовка
Создайте API-ключ через консоль управления Yandex Cloud или CLI.
Если вы создаете ключ в консоли управления, то перейдите в каталог, из которого будете выгружать метрики (например default). Затем перейдите на вкладку Сервисные аккаунты и выберите существующий аккаунт. Нажмите кнопку Создать новый ключ и выберите Создать API-ключ. В описании ключа можно указать, например, «для доступа к Prometheus». Сохраните секретную часть ключа в отдельный файл, например, prometheus-key.txt.
Назначьте сервисному аккаунту роль monitoring.viewer на выбранный каталог.
Создайте файл спецификации prometheus.yml (см. пример ниже, замените в нем значение параметра folderId на идентификатор каталога, а значение для bearer_token — на ключ доступа из файла prometheus-key.txt):
global:
scrape_interval:   15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
rule_files:
scrape_configs:
- job_name: 'prometheus'
  static_configs:
  - targets: ['localhost:9090']
- job_name: 'yc-monitoring-export'
  metrics_path: '/monitoring/v2/prometheusMetrics'
  params:
   folderId:
   - '<идентификатор_каталога>' 
   service:
   - 'storage' 
  bearer_token: '<секретная_часть_API-ключа>'
  static_configs:
  - targets: ['monitoring.api.cloud.yandex.net']
   labels:
     folderId: '<идентификатор_каталога>'
     service: 'storage' 
Запуск сервера Prometheus
Если вы уже работаете с Prometheus, пропустите все шаги по установке — просто добавьте секцию scrape_configs из примера выше в спецификацию вашего сервера Prometheus и перезапустите сервер, а затем переходите к настройке Grafana.
Для запуска сервера Prometheus используйте официальный Docker-образ prom/prometheus.
Сначала загрузите образ. Для этого запустите Docker Desktop (в терминале выполните команду):
docker pull prom/prometheus
Чтобы на сервере сразу был ваш файл спецификации, создайте свой образ на основе prom/prometheus. Подготовьте Dockerfile с двумя командами:
FROM prom/prometheus
ADD prometheus.yml /etc/prometheus/
Сохраните этот файл в тот же каталог, где находится prometheus.yml. Назовите его именем по умолчанию: Dockerfile.
В терминале перейдите в каталог с Dockerfile. Создайте образ с вашей конфигурацией (используйте ваш идентификатор в Yandex Container Registry):
docker build . -t cr.yandex/<идентификатор_реестра>/my-prometheus:latest -f Dockerfile
Аутентифицируйтесь в Yandex Container Registry с помощью Docker Credential helper (чтобы Docker мог от вашего имени отправить образ в ваш реестр):
yc container registry configure-docker
Теперь отправьте образ в ваше хранилище в облаке:
docker push cr.yandex/<идентификатор_реестра>/my-prometheus:latest
Создайте виртуальную машину с помощью Container Optimized Image, вы уже делали это раньше в практической работе (в разделе Выбор образа загрузочного диска переключитесь на вкладку Container Solution и нажмите Настроить. Выберите из реестра созданный вами образ, остальные настройки оставьте по умолчанию и нажмите Применить).
При создании виртуальной машины используйте ваш сервисный аккаунт. Задайте логин (например prom) и ssh-ключ.
Назовите машину, например, for-prometheus.
Проверьте статус сервера по адресу http://<публичный IP-адрес ВМ с Prometheus>:9090/targets. Через несколько минут после запуска статус процессов prometheus и yc-monitoring-export должен стать UP.
image
Подайте нагрузку на ваш сайт:
while true; do wget -q -O- <адрес_сайта>; done 
Подождите несколько минут и проверьте, как поставляются метрики в Prometheus.
В верхнем меню выберите пункт Graph. Нажмите на значок «Земли». Откроется меню с доступными метриками. Выберите метрику, которую вы хотите проверить, например, traffic и нажмите кнопку Execute.
Переключитесь на вкладку Graph. Выберите текущее время, для наглядности уменьшите интервал запроса данных (например до 15 минут). Вскоре вы увидите график изменения выбранной метрики.
image
Настройка Grafana
Теперь посмотрим, как метрики визуализируются в системе Grafana.
Если у вас еще нет аккаунта в Grafana, создайте его с помощью нескольких простых шагов, это бесплатно. Вам откроется интерфейс по адресу https://<ваш_логин>.grafana.net/.
Добавление источника данных
Настройте Prometheus в качестве источника данных. На главной странице нажмите кнопку Connect data. Из предложенного списка выберите источник Prometheus data source и нажмите кнопку Create Prometheus data source.
image
В следующем окне в поле URL введите endpoint сервера Prometheus http://<публичный IP-адрес ВМ с Prometheus>:9090. Больше никакие настройки менять не нужно.
  Внизу нажмите кнопку Save & Test. Должна отобразиться надпись Data source is working.
Добавление дашборда
Вернитесь на главную страницу (нажав на логотип в левом верхнем углу) и нажмите Create your first dashboard. Откроется окно настройки дашборда.
В нижней части экрана на вкладке Query выберите источник данных — Prometheus.
Выберите метрику, которую вы хотите отслеживать. Нажмите на поле Metrics, в открывшемся списке выберите метрику traffic.
Сверху отобразится график выбранной метрики.
image
Вверху справа в поле Panel Title укажите название графика (например, «Трафик сайта»).
Теперь сохраните настройки — в правом верхнем углу нажмите кнопку Save и укажите название дашборда (например, «Мой дашборд»).
image
Вы научились отслеживать метрики Yandex.Cloud не только средствами Yandex Monitoring, но и с помощью сторонних систем, в том числе широко используемых Prometheus и Grafana. Но метрики можно использовать не только для визуальной оценки состояния облака и его ресурсов. В следующих двух уроках мы посмотрим, как можно облегчить работу специалиста DevOps и автоматизировать мониторинг.
Task:
В этой практической работе вы создадите алерт для случая, если трафик на сайте вдруг начнет существенно расти. Снова используйте сайт клиники «Доктор Айболит», для которого настраивали графики на дашборде.
Вы можете перейти на вкладку Алерты и там настроить алерт с нуля. А можете отталкиваться от графиков, которые уже выведены в виджете. Ниже рассматривается именно второй вариант.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Создание алерта
Вернитесь на созданный вами дашборд и в меню виджета «Трафик сайта» выберите пункт Создать алерт.
image
В блоке Условия срабатывания выберите Запрос для проверки - B (суммирующую функцию)
Теперь задайте имя и, если хотите, описание алерта. Остальные настройки можете оставить без изменений.
Теперь нужно выбрать канал для получения алертов. У вас пока ещё нет настроенных каналов, поэтому система предложит вам создать его. В блоке Уведомления нажмите Добавить и кликните на Плюс.
Укажите имя канала, выберите метод — Email, SMS или Push-уведомления. Укажите получателей — себя. Затем нажмите кнопку Создать.
image
В настройках алерта выберите только что созданный канал.
Вы можете указать для одного алерта несколько каналов уведомлений. Например, если вы хотите получать алерты об увеличении трафика сайта не только в виде Push-уведомлений, но и по электронной почте, создайте еще один канал с методом Email и выберите также и его.
Для каждого канала можно настроить режим повторения уведомлений. Например, в данном случае при превышении трафика будет отправлен один алерт по электронной почте, а алерты в виде push-уведомлений будут отправляться каждые 5 минут до тех пор, пока проблема не будет устранена.
image
Нажмите кнопку Создать алерт.
Вы увидите настройки созданного алерта, а сверху — его текущий статус OK.
image
Нажмите слева на вкладку Алерты. Вы увидите ваш алерт, сейчас он единственный в списке. Когда алертов станет больше, вам понадобятся инструменты для работы с ними. Например, вы сможете отобрать из списка только алерты, имеющие статус Alarm или Warning, или временно деактивировать отдельные алерты.
Срабатывание алерта
Теперь посмотрим, как срабатывает алерт. Подайте трафик на сайт, который вы мониторите:
while true; do wget -q -O- <адрес_сайта>; done
Подождите немного и понаблюдайте за ростом нагрузки. Через какое-то время трафик начнет превышать пороговое значение Warning, и вы начнете получать Push-уведомления.
image
Если у администратора настроены другие каналы для алертов, он получил бы SMS или Email с предупреждением о пороговом значении трафика.
Как видите, алерты позволяют вовремя привлекать внимание администратора и устранять даже потенциальные, ещё не случившиеся проблемы.
Task:
Создаём вашу первую функцию
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Как добавить код функции
На главной странице консоли управления в списке сервисов выберите Cloud Functions:
image
На открывшейся странице нажмите кнопку Создать функцию:
image
Укажите имя функции, введите короткое описание того, что она будет делать, и нажмите кнопку Создать:
image
Затем выберите среду выполнения кода и нажмите кнопку Продолжить:
image
По умолчанию сервис предлагает создать Hello World — файл с примером кода на выбранном языке программирования. Этот файл будет создан и автоматически загружен в контейнер. В поле Способ укажите Редактор кода и выберите файл index.go.
image
По умолчанию сервис предлагает работать с редактором кода прямо в веб-интерфейсе (как на скриншоте выше). Однако вместо этого вы можете загрузить файл с кодом из бакета Object Storage (этот способ подойдёт для файлов больше 3,5 МБ) или загрузить ZIP-архив с кодом с локальной машины. Переключатель способа добавления кода находится прямо над окном редактора.
Код вашей функции может находиться как в одном файле, так и в нескольких. Вы также можете создавать папки. При этом обязательно нужно указывать точку входа — часть кода, которая будет вызываться первой и принимать параметры вызова. Формат точки входа — <имя файла с функцией>.<имя обработчика вызова>. Например, index.Handler.
Вверху справа нажмите кнопку Создать версию, чтобы сохранить текущее состояние функции.
image
Сервис создаст версию функции и покажет справочную страницу о ней.
image
Как протестировать созданную функцию
Теперь в панели слева перейдите на вкладку Тестирование. В поле Шаблон данных выберите HTTPS-вызов. Сервис автоматически сгенерирует входные данные в формате JSON.
image
Под полем с входными данными нажмите кнопку Запустить тест. Сервис выполнит HTTPS-вызов созданной функции и сформирует ответ (также в формате JSON).
Task:
В предыдущей практической работе вы познакомились с созданием функции через консоль управления. На этом уроке вы научитесь создавать функцию с помощью интерфейса командной строки (утилиты yc).
Пользоваться консолью управления бывает очень удобно, но вести большой проект всё же лучше локально, с помощью среды разработки. Артефакты локальной разработки можно с лёгкостью переносить в облако с помощью консольных утилит. Выполняя последовательно шаги, вы изучите основные команды для создания функций в облаке.
# Разработка Навыки для Алисы.
Decision:
Шаг 1. Создание сервисного аккаунта
Создание аккаунта
Для начала убедитесь, что у вас установлена и инициализирована утилита yc.
У вас уже есть сервисные аккаунты, созданные на предыдущих занятиях. Однако гораздо лучше, когда для каждой конкретной задачи (или блока задач) вы заводите отдельный сервисный аккаунт. Это обеспечивает прозрачность в управлении доступом и контроле за ролями в сервисах.
Предварительно установите утилиту jq, она потребуется для выполнения задания:
sudo apt install jq 
Создайте сервисный аккаунт с именем service-account-for-cf:
export SERVICE_ACCOUNT=$(yc iam service-account create \
--name service-account-for-cf \
--description "service account for cloud functions" \
--format json | jq -r .) 
Проверьте текущий список сервисных аккаунтов:
yc iam service-account list
echo $SERVICE_ACCOUNT 
После проверки запишите идентификатор (ID) созданного сервисного аккаунта в переменную SERVICE_ACCOUNT_ID:
echo "export SERVICE_ACCOUNT_ID=<идентификатор_сервисного_аккаунта>" >> ~/.bashrc && . ~/.bashrc
echo $SERVICE_ACCOUNT_ID 
Назначение роли сервисному аккаунту
Добавьте вновь созданному сервисному аккаунту роль editor:
echo "export FOLDER_ID=$(yc config get folder-id)" >> ~/.bashrc && . ~/.bashrc 
echo $FOLDER_ID
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_ID \
--role editor 
Не удаляйте файл ~/.bashrc после прохождения практической работы, он понадобится нам в дальнейшем. 
Шаг 2. Создание и настройка функции
Создание функции
Создайте функцию с именем my-first-function:
yc serverless function create --name my-first-function 
Вы получите URL, по которому можно будет сделать вызов функции http_invoke_url. По умолчанию функция будет непубличной.
Загрузка кода функции
Рассматривайте практические работы этого курса как разные проекты и создавайте файлы для каждой из них в отдельной папке.
Создайте файл index.py :
sudo nano index.py 
Добавьте в index.py следующее содержимое:
def handler(event, context):
  return {
    'statusCode': 200,
    'body': 'Hello World!',
  } 
Успешное выполнение этой функции вернёт небольшую веб-страницу.
Загрузите код функции в облако и создайте её версию. Для этого перейдите в папку с файлом index.py и выполните команду:
yc serverless function version create \
  --function-name my-first-function \
  --memory 256m \
  --execution-timeout 5s \
  --runtime python37 \
  --entrypoint index.handler \
  --service-account-id $SERVICE_ACCOUNT_ID \
  --source-path index.py 
Успешное выполнение команды приведёт к созданию версии функции. С помощью консоли управления убедитесь, что версия создана.
Вызов функции
Получите список функций, а затем — информацию о функции my-first-function:
yc serverless function list
yc serverless function version list --function-name my-first-function 
В результате вызова последней команды из столбца FUNCTION ID вы узнаете идентификатор функции и сможете сделать вызов функции с помощью следующей команды:
yc serverless function invoke <идентификатор_функции> 
По умолчанию функция создаётся непубличной. Чтобы сделать функцию my-first-function публичной, выполните следующую команду:
yc serverless function allow-unauthenticated-invoke my-first-function 
После этого вы сможете вызвать её в браузере. Получите параметр http_invoke_url для функции my-first-function:
yc serverless function get my-first-function 
Введите значение параметра http_invoke_url в браузере и наслаждайтесь вызовом вашей функции.
Task:
В предыдущем практическом уроке вы познакомились с созданием одной функции с помощью интерфейса командной строки (yc). В этом уроке мы продолжим разработку этой функции: модифицируем её содержание, добавим переменные окружения и т.д.
# Разработка Навыки для Алисы.
Decision:
Шаг 1. Модификация сервисного аккаунта
Добавление роли сервисному аккаунту
По итогам прохождения предыдущей практической работы у вас есть сервисный аккаунт с именем service-account-for-cf. Для работы с Object Storage добавьте этому сервисному аккаунту роль storage.editor:
yc resource-manager folder add-access-binding $FOLDER_ID \
  --role storage.editor \
  --subject serviceAccount:$SERVICE_ACCOUNT_ID 
Создание ключа доступа для сервисного аккаунта
Этот этап нужен для получения идентификатора ключа доступа и секретного ключа, которые будут использованы для загрузки файлов в Object Storage, а также в том случае, если на следующем шаге для создания бакета в Object Storage вы планируете использовать Terraform.
Для создания ключа доступа необходимо вызвать следующую команду:
yc iam access-key create --service-account-name service-account-for-cf 
В результате вы получите примерно следующее:
access_key:
  id: ajefraollq5puj2tir1o
  service_account_id: ajetdv28pl0a1a8r41f0
  created_at: "2021-08-23T21:13:05.677319393Z"
  key_id: BTPNvWthv0ZX2xVmlPIU
secret: cWLQ0HrTM0k_qAac43cwMNJA8VV_rfTg_kd4xVPi 
Где:
key_id — идентификатор ключа доступа, ACCESS_KEY.
secret — секретный ключ, SECRET_KEY.
Переменные ACCESS_KEY и SECRET_KEY будут использованы для задания соответствующих значений aws_access_key_id и aws_secret_access_key при использовании библиотеки boto3 на следующих этапах.
Шаг 2. Object Storage
Самый простой способ создания бакета в Object Storage — через консоль управления. Более сложный, позволяющий автоматизировать разработку, — использование Terraform. Вы можете выбрать любой из них.
Способ 1. Консоль управления
В консоли управления в вашем рабочем каталоге выберите сервис Object Storage. Нажмите кнопку Создать бакет.
На странице создания бакета:
Введите имя бакета, пусть это будет bucket-for-trigger.
При необходимости ограничьте максимальный размер бакета, установив значение, например, 1 ГБ.
Выберите тип доступа, в нашем уроке установим значения в Публичный во всех случаях.
Выберите класс хранилища, по умолчанию используется Стандартное.
Нажмите кнопку Создать бакет для завершения операции. Далее вы всегда сможете поменять класс хранилища, его размер и настройки доступа.
image
Способ 2. Terraform
Прежде всего необходимо получить OAuth-токен для работы с Yandex Cloud. Для этого можно сделать запрос к сервису Яндекс.OAuth. Подробнее прочитать можно в документации.
Сохраните OAuth-токен в переменную OAuth, но никому не передавайте. Также вам потребуются значения переменных: идентификатор облака — CLOUD_ID и идентификатор каталога FOLDER_ID (сохранен в переменную ранее).
Также на предыдущем шаге вы получили ключ доступа для сервисного аккаунта. Нам потребуется идентификатор ключа доступа ACCESS_KEY и секретный ключ SECRET_KEY.
В файл main.tf, представленный далее, внесём все собранные переменные. Важно: переменная BUCKET_NAME содержит имя создаваемого бакета в Object Storage, куда будем загружать файлы. Допустим, переменная будет равна bucket-for-trigger. Сохраним все значения:
terraform {
required_providers {
  yandex = {
   source = "yandex-cloud/yandex"
  }
}
required_version = ">= 0.13"
}
provider "yandex" {
token   = "<OAuth>"
cloud_id = "<CLOUD_ID>"
folder_id = "<FOLDER_ID>"
}
resource "yandex_storage_bucket" "bucket" {
access_key = "<ACCESS_KEY>"
secret_key = "<SECRET_KEY>"
bucket = "<BUCKET_NAME>"

После внесения правок, находясь в каталоге с файлом main.tf, последовательно выполните следующие команды:
terraform init
terraform plan
terraform apply 
Успешное выполнение команд приведёт к созданию бакета bucket-for-trigger в объектном хранилище в вашем рабочем каталоге.
Шаг 3. Модификация функции
В предыдущей практической работе мы создали функцию с именем my-first-function с помощью следующей команды:
yc serverless function create --name my-first-function 
При создании функции вы получили URL, по которому можно будет сделать вызов функции http_invoke_url. 
Загрузка кода новой версии
Новая версия функции имеет зависимости, которые описаны в файле requirements.txt, а это значит, что для загрузки функции в облако необходимо файлы index.py и requirements.txt заархивировать и получить файл my-first-function.zip.
Новая версия index.py:
import os
import datetime
import boto3
import pytz
ACCESS_KEY = os.getenv("ACCESS_KEY")
SECRET_KEY = os.getenv("SECRET_KEY")
BUCKET_NAME = os.getenv("BUCKET_NAME")
TIME_ZONE = os.getenv("TIME_ZONE", "Europe/Moscow")
TEMP_FILENAME = "/tmp/temp_file"
TEXT_FOR_TEMP_FILE = "This is text file"
def write_temp_file():
  temp_file = open(TEMP_FILENAME, 'w')
  temp_file.write(TEXT_FOR_TEMP_FILE)
  temp_file.close()
  print("\U0001f680 Temp file is written")
def get_now_datetime_str():
  now = datetime.datetime.now(pytz.timezone(TIME_ZONE))  
  return now.strftime('%Y-%m-%d__%H-%M-%S')
def get_s3_instance():
  session = boto3.session.Session()
  return session.client(
    aws_access_key_id=ACCESS_KEY,
    aws_secret_access_key=SECRET_KEY,
    service_name='s3',
    endpoint_url='https://storage.yandexcloud.net'
  )
def upload_dump_to_s3():
  print("\U0001F4C2 Starting upload to Object Storage")
  get_s3_instance().upload_file(
    Filename=TEMP_FILENAME,
    Bucket=BUCKET_NAME,
    Key=f'file-{get_now_datetime_str()}.txt'
  )
  print("\U0001f680 Uploaded")
def remove_temp_files():
  os.remove(TEMP_FILENAME)
  print("\U0001F44D That's all!")
def handler(event, context):
  write_temp_file()
  upload_dump_to_s3()
  remove_temp_files()
  return {
    'statusCode': 200,
    'body': 'File is uploaded',
  } 
Первая версия requirements.txt:
boto3==1.13.10
botocore==1.16.10
python-dateutil==2.8.1
pytz==2020.1 
Находясь в каталоге с файлом my-first-function.zip вызовите следующую команду, это позволит вам загрузить код функции в облако и создать её версию:
yc serverless function version create \
--function-name my-first-function \
--memory 256m \
--execution-timeout 5s \
--runtime python37 \
--entrypoint index.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--source-path my-first-function.zip 
Новая версия функции при вызове будет загружать в Object Storage новый файл. Для создания этой версии необходимо подготовить несколько переменных. Переменные ACCESS_KEY и SECRET_KEY вы получили на первом шаге, а значение BUCKET_NAME на втором:
echo "export ACCESS_KEY=<ACCESS_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export SECRET_KEY=<SECRET_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export BUCKET_NAME=bucket-for-trigger" >> ~/.bashrc && . ~/.bashrc 
Определим идентификатор (ID) для последней загруженной версии функции:
yc serverless function version list --function-name my-first-function 
Создадим новую версию функции, задав при этом переменные окружения. Для этого выставим значение параметра source-version-id равное полученному ID в следующей команде:
yc serverless function version create \
--function-name my-first-function \
--memory 256m \
--execution-timeout 5s \
--runtime python37 \
--entrypoint index.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--source-version-id <ID> \
--environment ACCESS_KEY=$ACCESS_KEY \
--environment SECRET_KEY=$SECRET_KEY \
--environment BUCKET_NAME=$BUCKET_NAME 
Успешное выполнение команды приведёт к созданию версии функции.
Вызов функции
Получите список функций и информацию о функции my-first-function:
yc serverless function list
yc serverless function version list --function-name my-first-function 
В результате вызова последней команды в столбце FUNCTION ID вы узнаете идентификатор функции и сможете сделать вызов функции с помощью следующей команды:
yc serverless function invoke <идентификатор_функции> 
В предыдущей практической работе мы сделали функцию my-first-function публичной с помощью команды:
yc serverless function allow-unauthenticated-invoke my-first-function 
Теперь мы можем сделать её вызов в браузере. Получите параметр http_invoke_url для функции my-first-function
yc serverless function get my-first-function 
Введите значение параметра http_invoke_url в браузере и наслаждайтесь вызовом вашей функции. Во время её вызова в Object Storage будет создан новый файл.
Шаг 4. Создание триггера
Создание функции
Для создания триггера нам необходима функция, которую триггер будет запускать. Аналогично предыдущему шагу создадим функцию my-trigger-function и её версию на основе файла index.py.
def handler(event, context):
  print("\U0001F4C2 Starting function after trigger")
  print(event)   
  return {
    'statusCode': 200,
    'body': 'File is uploaded',
  } 
Находясь в каталоге с файлом index.py, вызовите следующие команды:
yc serverless function create --name my-trigger-function
yc serverless function version create \
--function-name my-trigger-function \
--memory 256m \
--execution-timeout 5s \
--runtime python37 \
--entrypoint index.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--source-path index.py
yc serverless function version list --function-name my-trigger-function 
Создание триггера
Чтобы создать триггер my-first-trigger, который вызывает функцию my-trigger-function при создании нового объекта в бакете BUCKET_NAME, выполните команду:
yc serverless trigger create object-storage \
--name my-first-trigger \
--bucket-id $BUCKET_NAME \
--events 'create-object' \
--invoke-function-name my-trigger-function \
--invoke-function-service-account-id $SERVICE_ACCOUNT_ID 
Вызов цепочки событий
Чтобы запустить цепочку событий, вызовем первую функцию my-first-function. Получите список функций и информацию о функции my-first-function:
yc serverless function list
yc serverless function version list --function-name my-first-function 
В результате вызова последней команды в столбце FUNCTION ID вы узнаете идентификатор функции и сможете сделать вызов функции с помощью команды:
yc serverless function invoke <идентификатор_функции> 
После этого вы можете сделать её вызов в браузере. Получите параметр http_invoke_url для функции my-first-function
yc serverless function get my-first-function 
Введите значение параметра http_invoke_url в браузере. Во время вызова функции в Object Storage будет создан новый объект. Сразу после этого сработает триггер my-first-trigger, который вызовет функцию my-trigger-function. В итоге, наша вторая функция запишет в логи содержание переменной event. Убедиться в этом вы сможете как в UI, так и через CLI.
yc serverless function logs my-trigger-function 
Task:
В предыдущих практических работах вы создали сервисный аккаунт с именем service-account-for-cf, добавили ему роли editor и storage.editor и создали ключ доступа. 
Также вы создали бакет в Object Storage с именем bucket-for-trigger, триггер my-first-trigger для его обработки и вызываемую им функцию my-trigger-function. 
Ещё была создана функция my-first-function, её использовали для того, чтобы запустить цепочку событий. Публичный вызов этой функции приводил к созданию нового объекта в бакете в Object Storage. Это запускало вызов триггера my-first-trigger, который стартовал функцию my-trigger-function. В итоге последняя функция записывала в логи содержание переменной event.
Если вы удалили бакет и сервисный аккаунт, необходимо вернуться к предыдущим урокам и повторить их создание.
# Разработка Навыки для Алисы.
Decision:
Шаг 1. Создание функции
На предыдущем уроке мы создали функцию с именем my-first-function. Поменяем её исходный код так, чтобы обрабатывать запросы от Алисы.
На основе функции будет создан навык Попугай, который повторяет все, что ему написал или сказал пользователь.
Функция parrot
Создадим новую функцию с именем parrot с помощью команды:
yc serverless function create \
--name parrot \
--description "function for Alice" 
По умолчанию функция не является публичной.
Загрузка кода новой версии
Функция имеет зависимости, которые описаны в файле requirements.txt, а это значит, что для загрузки функции в облако необходимо заархивировать файлы parrot.py и requirements.txt и получить файл parrot.zip.
Содержание функции parrot.py:
import os
import datetime
import boto3
import pytz
ACCESS_KEY = os.getenv("ACCESS_KEY")
SECRET_KEY = os.getenv("SECRET_KEY")
BUCKET_NAME = os.getenv("BUCKET_NAME")
TIME_ZONE = os.getenv("TIME_ZONE", "Europe/Moscow")
TEMP_FILENAME = "/tmp/temp_file"
TEXT_FOR_TEMP_FILE = "This is text file"
def write_temp_file(text_for_s3):
  TEXT_FOR_TEMP_FILE = text_for_s3
  temp_file = open(TEMP_FILENAME, 'w')  
  temp_file.write(TEXT_FOR_TEMP_FILE)
  temp_file.close()
  print("\U0001f680 Temp file is written")
def get_now_datetime_str():
  now = datetime.datetime.now(pytz.timezone(TIME_ZONE))
  return now.strftime('%Y-%m-%d__%H-%M-%S')
def get_s3_instance():
  session = boto3.session.Session()
  return session.client(
    aws_access_key_id=ACCESS_KEY,
    aws_secret_access_key=SECRET_KEY,
    service_name='s3',
    endpoint_url='https://storage.yandexcloud.net'
  )
def upload_dump_to_s3():
  print("\U0001F4C2 Starting upload to Object Storage")
  get_s3_instance().upload_file(
    Filename=TEMP_FILENAME,
    Bucket=BUCKET_NAME,
    Key=f'file-{get_now_datetime_str()}.txt'
  )
  print("\U0001f680 Uploaded")
def remove_temp_files():
  os.remove(TEMP_FILENAME)
  print("\U0001F44D That's all!")
def handler(event, context):
  """
  Entry-point for Serverless Function.
  :param event: request payload.
  :param context: information about current execution context.
  :return: response to be serialized as JSON.
  """
  text = 'Hello! I\'ll repeat anything you say to me.'
  if 'request' in event and \
      'original_utterance' in event['request'] \
      and len(event['request']['original_utterance']) > 0:
    text = event['request']['original_utterance']
    write_temp_file(text)
    upload_dump_to_s3()
    remove_temp_files()
  return {
    'version': event['version'],
    'session': event['session'],
    'response': {
      # Respond with the original request or welcome the user if this is the beginning of the dialog and the request has not yet been made.
      'text': text,
      # Don't finish the session after this response.
      'end_session': 'false'
    },
  } 
Содержание файла зависимостей requirements.txt:
boto3==1.13.10
botocore==1.16.10
python-dateutil==2.8.1
pytz==2020.1 
Находясь в каталоге с файлом parrot.zip, вызовите приведенную ниже команду. Это позволит вам загрузить код функции в облако и создать её версию:
yc serverless function version create \
--function-name=parrot \
--memory=256m \
--execution-timeout=5s \
--runtime=python37 \
--entrypoint=parrot.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--source-path parrot.zip 
Шаг 2. Создание новой версии функции
Новая версия функции при вызове будет загружать в Object Storage новый файл. Для создания этой новой версии функции необходимы переменные.
Если переменные среды не сохранились, то в консоли управления можно посмотреть имя бакета, а ACCESS_KEY и SECRET_KEY скопировать из предыдущей функции my-first-function: 
echo "export ACCESS_KEY=<ACCESS_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export SECRET_KEY=<SECRET_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export BUCKET_NAME=bucket-for-trigger" >> ~/.bashrc && . ~/.bashrc 
Определим идентификатор (ID) последней загруженной версии функции:
yc serverless function version list --function-name parrot 
Создадим новую версию функции, задав переменные окружения. Для этого выставим значение параметра source-version-id равное полученному идентификатору версии функции (ID) в следующей команде:
yc serverless function version create \
--function-name parrot \
--memory 256m \
--execution-timeout 5s \
--runtime python37 \
--entrypoint parrot.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--source-version-id <идентификатор_версии_функции> \
--environment ACCESS_KEY=$ACCESS_KEY \
--environment SECRET_KEY=$SECRET_KEY \
--environment BUCKET_NAME=$BUCKET_NAME 
Успешное выполнение команды приведёт к созданию версии функции.
Шаг 3. Вызов функции и её тестирование
По умолчанию функция создаётся непубличной. Чтобы сделать функцию parrot публичной, вызовите следующую команду:
yc serverless function allow-unauthenticated-invoke parrot 
Протестируйте функцию parrot, чтобы проверить правильность кода перед созданием связки с Алисой. В консоли управления на странице сервиса Cloud Functions выберите созданную функцию и перейдите на вкладку Тестирование. В поле Шаблон данных данных укажите Навык Алисы и нажмите кнопку Запустить тест.
image
В блоке Результат тестирования убедитесь, что функция выполнена и приведен ответ. 
image
Перейдите по ссылке https://dialogs.yandex.ru/developer/ и создайте новый диалог Алисы (подробности о создании навыков вы можете узнать из документации):
Нажмите кнопку Создать диалог. Выберите тип диалога Навык в Алисе, у вас откроется форма на вкладке Настройки.
image
Заполните имя навыка, оно должно состоять минимум из двух слов, например My parrot.
В блоке Backend выберите вариант Функция в Яндекс.Облаке и в выпадающем списке выберите созданную вами функцию parrot.
image
В блоке Тип доступа в выпадающем списке выберите Приватный.
image
В блоке Публикация в каталоге выберите Примеры запросов, например Запусти навык - My parrot, Имя разработчика, Категорию, Описание и Иконку.
image
Нажмите кнопку Сохранить и перейдите на вкладку Тестирование.
image
image
Если вы сделали всё правильно, то на экране появится приветствие навыка. Далее навык будет повторять всё, что вы ему напишете. При этом фразы, которые вы отправите Алисе, будут сохраняться в новом файле в бакете. Вы можете это проверить в консоли управления.
Task:
На этом практическом занятии вы создадите функцию для проверки доступности сайта ya.ru, которая будет измерять время ответа. Результаты работы функции будут передаваться в базу данных сервиса Yandex Managed Service for PostgreSQL с использованием подключения к управляемой БД из функции. Также вы запустите триггер-таймер, который будет регулярно производить опрос сайта ya.ru.
# Разработка Навыки для Алисы.
Decision:
Шаг 1. Дополнительная роль для сервисного аккаунта
В предыдущих практических работах вы создали сервисный аккаунт с именем service-account-for-cf, назначили ему роли editor и storage.editor и создали ключ доступа. Чтобы подключаться к управляемым БД из функции, нужно добавить сервисному аккаунту роль serverless.mdbProxies.user.
Для этого выполните следующую команду:
yc resource-manager folder add-access-binding $FOLDER_ID \
--role serverless.mdbProxies.user \
--subject serviceAccount:$SERVICE_ACCOUNT_ID 
Шаг 2. Создание базы данных
Создание кластера PostgreSQL
Конечно, кластер PostgreSQL можно создать с помощью консоли управления, но в этой практической работе мы используем CLI. Прежде всего, давайте определим подсеть, в которой будет расположен кластер. Разместим кластер в зоне ru-central1-c и с помощью следующей команды узнаем идентификатор(ID) соответствующей подсети:
yc vpc subnet list 
Создадим кластер версии PostgreSQL 15 с именем my-pg-database. Установим тип хоста burstable c3-c2-m4 — это самый дешёвый и простой вариант хоста. Из-за невысокой производительности он подходит только для тестовых целей. Используем для хоста жёсткий диск (HDD) размером 10 ГБ.
Сразу создадим пользователя с именем user1 и паролем user1user1, а также базу данных db1. Для удобства администрирования откроем доступ из консоли управления. Используйте опцию websql-access — это позволит выполнять SQL-запросы прямо в консоли управления. Чтобы открыть возможность подключения к PostgreSQL из функции, необходимо подключить опцию serverless-access.
Следующая команда за несколько минут создаст кластер PostgreSQL (не забудьте подставить идентификатор вашей подсети):
yc managed-postgresql cluster create \
--name my-pg-database \
--description 'For Serverless' \
--postgresql-version 15 \
--environment production \
--network-name default \
--resource-preset c3-c2-m4 \
--host zone-id=ru-central1-c,subnet-id=<идентификатор_подсети> \
--disk-type network-hdd \
--disk-size 10 \
--user name=user1,password=user1user1 \
--database name=db1,owner=user1 \
--websql-access \
--serverless-access 
После успешного создания кластера проверьте результат:
yc managed-postgresql cluster list
yc managed-postgresql cluster get <имя или идентификатор кластера> 
Создание таблицы для хранения данных
При создании кластера мы использовали опцию websql-access, что открывает нам возможности по исполнению SQL-команд в консоли управления. Воспользуемся этим и сделаем таблицу в созданной нами базе данных. В эту таблицу мы будем складывать результаты выполнения функции. В консоли управления перейдите в каталог, в котором создан кластер PostgreSQL. Откройте сервис Managed Service for PostgreSQL и перейдите в кластер my-pg-database.
В боковом меню перейдите на вкладку SQL. Для базы данных db1введите имя user1 и пароль user1user1, нажмите кнопку Подключиться.
image
В открывшемся окне введите SQL-запрос и исполните его:
CREATE TABLE measurements (
  result integer,
  time float
); 
image
Успешное выполнение команды создаст таблицу, куда мы будем складывать результаты.
Шаг 3. Подключение к управляемой БД из функции
Создание подключения
В консоли управления перейдите в каталог, в котором хотите создать подключение. Откройте сервис Cloud Functions. В боковом меню перейдите на вкладку Подключения к БД. Нажмите кнопку Создать подключение.
image
Введите имя, описание подключения и в выпадающем списке выберите тип подключения — PostgreSQL.
Укажите кластер — my-pg-database.
Укажите базу данных — db1.
Введите данные пользователя БД: имя user1 и пароль user1user1.
Нажмите кнопку Создать.
image
Выберите созданное подключение. На вкладке Обзор скопируйте параметры Идентификатор и Точка входа. Они будут использованы в функции на следующем шаге.
image
Шаг 4. Создание функции
Перед созданием функции определите переменные для инициации подключения: CONNECTION_ID — идентификатор подключения, DB_USER — имя пользователя БД, DB_HOST — точка входа. Используйте следующие команды:
echo "export CONNECTION_ID=<CONNECTION_ID>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_USER=<DB_USER>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_HOST=<DB_HOST>" >> ~/.bashrc && . ~/.bashrc 
Они будут использованы в функции function-for-postgresql.py. Код функции:
import datetime
import logging
import requests
import os
#Эти библиотеки нужны для работы с PostgreSQL
import psycopg2
import psycopg2.errors
CONNECTION_ID = os.getenv("CONNECTION_ID")
DB_USER = os.getenv("DB_USER")
DB_HOST = os.getenv("DB_HOST")
# Настраиваем функцию для записи информации в журнал функции
# Получаем стандартный логер языка Python
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Вычитываем переменную VERBOSE_LOG, которую мы указываем в переменных окружения 
verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool
#Функция log, которая запишет текст в журнал выполнения функции, если в переменной окружения VERBOSE_LOG будет значение True
def log(logString):
  if verboseLogging:
    logger.info(logString)
#Запись в базу данных
def save(result, time, context):
  connection = psycopg2.connect(
    database=CONNECTION_ID, # Идентификатор подключения
    user=DB_USER, # Пользователь БД
    password=context.token["access_token"],
    host=DB_HOST, # Точка входа
    port=6432,
    sslmode="require")
  cursor = connection.cursor()  
  postgres_insert_query = """INSERT INTO measurements (result, time) VALUES (%s,%s)"""
  record_to_insert = (result, time)
  cursor.execute(postgres_insert_query, record_to_insert)
  connection.commit()
# Это обработчик. Он будет вызван первым при запуске функции
def entry(event, context):
  #Выводим в журнал значения входных параметров event и context
  log(event)
  log(context)
  # Тут мы запоминаем текущее время, отправляем запрос к ya.ru и вычисляем время выполнения запроса
  try:
    now = datetime.datetime.now()
    #здесь указано два таймаута: 1c для установки связи с сервисом и 3 секунды на получение ответа
    response = requests.get('https://ya.ru', timeout=(1.0000, 3.0000))
    timediff = datetime.datetime.now() - now
    #сохраняем результат запроса
    result = response.status_code
  #если в процессе запроса сработали таймауты, то в результат записываем соответствующие коды
  except requests.exceptions.ReadTimeout:
    result = 601
  except requests.exceptions.ConnectTimeout:
    result = 602
  except requests.exceptions.Timeout:
    result = 603
  log(f'Result: {result} Time: {timediff.total_seconds()}')  
  save(result, timediff.total_seconds(), context)
  #возвращаем результат запроса
  return {
    'statusCode': result,
    'headers': {
      'Content-Type': 'text/plain'
    },
    'isBase64Encoded': False
  } 
Перейдем в директорию с кодом функции и создадим нашу функцию function-for-postgresql. При этом сразу зададим все необходимые переменные и сервисный аккаунт:
yc serverless function create \
--name function-for-postgresql \
--description "function for postgresql"
yc serverless function version create \
--function-name=function-for-postgresql \
--memory=256m \
--execution-timeout=5s \
--runtime=python37 \
--entrypoint=function-for-postgresql.entry \
--service-account-id $SERVICE_ACCOUNT_ID \
--environment VERBOSE_LOG=True \
--environment CONNECTION_ID=$CONNECTION_ID \
--environment DB_USER=$DB_USER \
--environment DB_HOST=$DB_HOST \
--source-path function-for-postgresql.py 
Проверим работоспособность функции: 
yc serverless function version list --function-name function-for-postgresql
yc serverless function invoke --name function-for-postgresql 
Успешный вызов функции приведёт к измерению времени ответа сайта и формированию записи в базе данных. 
Шаг 5. Создание триггера
Создание триггера-таймера
Проверять доступность сайта лучше в автоматическом режиме через равные промежутки времени. Для этой задачи создайте триггер-таймер. Он будет использовать cron-выражения:
yc serverless trigger create timer \
--name trigger-for-postgresql \
--invoke-function-name function-for-postgresql \
--invoke-function-service-account-id $SERVICE_ACCOUNT_ID \
--cron-expression '* * * * ? *' 
Cron-выражение * * * * ? * означает вызов функции function-for-postgresql один раз в минуту. Успешное выполнение функции раз в минуту будет создавать запись в базе данных, в чём вы можете убедиться, просмотрев записи в таблице. 
Убедились? Поздравляем: вы успешно создали функцию, которая через заданный промежуток времени выполняется по триггеру, чтобы проверить доступность ya.ru и записать результат проверки в базу данных.
Удаление триггера-таймера
После завершения практической работы не забудьте удалить созданный триггер trigger-for-postgresql, иначе он будет продолжать работать:
yc serverless trigger delete trigger-for-postgresql 
Task:
На предыдущем практическом занятии мы создали простую систему, которая проверяет доступность сайта yandex.ru и измеряет время ответа на запрос. Полученную информацию функция записывала в базу данных PostgreSQL. На этом уроке мы доработаем начатый проект и добавим REST API, который позволит получать до 50 результатов проверки из базы данных.
# Разработка HTTP API запросов.
Decision:
Шаг 1. Проверить наличие сервисного аккаунта
Для работы нам понадобится сервисный аккаунт с именем service-account-for-cf и ролями editor, serverless.mdbProxies.user, который мы создали ранее.
Шаг 2. Yandex API Gateway
Создание спецификации
В рабочем каталоге создадим спецификацию hello-world.yaml:
openapi: "3.0.0"
info:
version: 1.0.0
title: Test API
paths:
/hello:
  get:
   summary: Say hello
   operationId: hello
   parameters:
    - name: user
     in: query
     description: User name to appear in greetings
     required: false
     schema:
      type: string
      default: 'world'
   responses:
    '200':
     description: Greeting
     content:
      'text/plain':
       schema:
        type: "string"
   x-yc-apigateway-integration:
    type: dummy
    http_code: 200
    http_headers:
     'Content-Type': "text/plain"
    content:
     'text/plain': "Hello, {user}!\n" 
Мы можем создать API-шлюз с помощью консоли управления, но сейчас воспользуемся CLI.
Инициализация спецификации
Чтобы развернуть API-шлюз, используем спецификацию hello-world.yaml:
yc serverless api-gateway create \
--name hello-world \
--spec=hello-world.yaml \
--description "hello world" 
В результате успешного создания API-шлюза получим значение параметра domain:
yc serverless api-gateway list
yc serverless api-gateway get --name hello-world 
Скопируем служебный домен, чтобы проверить работоспособность API-шлюза. Вставим его в адресную строку браузера и допишем в конец /hello. Должно получиться следующее:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/hello 
Теперь протестируем запрос с параметрами. Добавьте к предыдущему запросу ?user=my_user. Должно получиться следующее:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/hello?user=my_user 
В первом случае в окне браузера вы увидите «Hello, world!», во втором «Hello, my_user!».
Шаг 3. Создание функции
Работа с библиотеками и переменными
До этого момента мы использовали рантайм python37, который не требовал явного указания библиотек, но начиная с версии python39, нужно указывать библиотеки явно. Для работы с requirements.txt можно воспользоваться удобной Python-библиотекой pipreqs: чтобы сгенерировать requirements.txt с помощью pipreqs, достаточно указать рабочий каталог. 
Чтобы сформировать файл requirements.txt, команда pipreqs анализирует все Python-скрипты в текущей папке и вычисляет нужные зависимости. Поэтому создавайте для выполнения практических работ этого курса отдельные папки.
В большинстве интерпретаторов Linux для указания текущего каталога предусмотрена переменная $PWD. Если файл requirements.txt уже существует, актуализируйте его с помощью флага --force, например:
pip install pipreqs
pipreqs $PWD --print
pipreqs $PWD --force 
Чтобы создать функцию, проверим доступность переменных для инициации подключения CONNECTION_ID, DB_USER, DB_HOST, которые мы создали в предыдущей работе с помощью следующих команд:
echo "export CONNECTION_ID=<CONNECTION_ID>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_USER=<DB_USER>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_HOST=<DB_HOST>" >> ~/.bashrc && . ~/.bashrc 
Создание функции
Создадим функцию function-for-user-requests.py:
import json
import logging
import requests
import os 
#Эти библиотеки нужны для работы с PostgreSQL
import psycopg2
import psycopg2.errors
import psycopg2.extras 
CONNECTION_ID = os.getenv("CONNECTION_ID")
DB_USER = os.getenv("DB_USER")
DB_HOST = os.getenv("DB_HOST") 
# Настраиваем функцию для записи информации в журнал функции
# Получаем стандартный логер языка Python
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Вычитываем переменную VERBOSE_LOG, которую мы указываем в переменных окружения
verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool 
#Функция log, которая запишет текст в журнал выполнения функции, если в переменной окружения VERBOSE_LOG будет значение True
def log(logString):
  if verboseLogging:
    logger.info(logString) 
#Запись в базу данных
def save(result, time, context):
  connection = psycopg2.connect(
    database=CONNECTION_ID, # Идентификатор подключения
    user=DB_USER, # Пользователь БД
    password=context.token["access_token"],
    host=DB_HOST, # Точка входа
    port=6432,
    sslmode="require") 
  cursor = connection.cursor() 
  postgres_insert_query = """INSERT INTO measurements (result, time) VALUES (%s,%s)"""
  record_to_insert = (result, time)
  cursor.execute(postgres_insert_query, record_to_insert)
  connection.commit() 
#Формируем запрос
def generateQuery():
  select = f"SELECT * FROM measurements LIMIT 50"
  result = select
  return result 
#Получаем подключение
def getConnString(context):
  """
  Extract env variables to connect to DB and return a db string
  Raise an error if the env variables are not set
  :return: string
  """
  connection = psycopg2.connect(
    database=CONNECTION_ID, # Идентификатор подключения
    user=DB_USER, # Пользователь БД
    password=context.token["access_token"],
    host=DB_HOST, # Точка входа
    port=6432,
    sslmode="require") 
  return connection 
def handler(event, context):
  try:
    secret = event['queryStringParameters']['secret']
    if secret != 'cecfb23c-bc86-4ca2-b611-e79bc77e5c31':
      raise Exception()
  except Exception as error:
    logger.error(error)
    statusCode = 401
    return {
      'statusCode': statusCode
    } 
  sql = generateQuery()
  log(f'Exec: {sql}') 
  connection = getConnString(context)
  log(f'Connecting: {connection}')
  cursor = connection.cursor()
  try:
    cursor.execute(sql)
    statusCode = 200
    return {
      'statusCode': statusCode,
      'body': json.dumps(cursor.fetchall()),
    }
  except psycopg2.errors.UndefinedTable as error:
    connection.rollback()
    logger.error(error)
    statusCode = 500
  except Exception as error:
    logger.error(error)
    statusCode = 500
  cursor.close()
  connection.close() 
  return {
    'statusCode': statusCode,
    'body': json.dumps({
      'event': event,
    }),
  } 
Обратите внимание, в коде функции мы заложили параметр secret и его значение cecfb23c-bc86-4ca2-b611-e79bc77e5c31, при котором функция будет выполняться. Таким образом мы обеспечиваем дополнительную защиту при доступе к БД.
При создании функции сразу зададим все необходимые переменные и сервисный аккаунт:
yc serverless function create \
--name function-for-user-requests \
--description "function for response to user" 
yc serverless function version create \
--function-name=function-for-user-requests \
--memory=256m \
--execution-timeout=5s \
--runtime=python37 \
--entrypoint=function-for-user-requests.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--environment VERBOSE_LOG=True \
--environment CONNECTION_ID=$CONNECTION_ID \
--environment DB_USER=$DB_USER \
--environment DB_HOST=$DB_HOST \
--source-path function-for-user-requests.py 
Шаг 4. Обновление спецификации API Gateway
Наша функция готова, но по умолчанию она не является публичной. Предоставим доступ к этой функции с помощью API-шлюза — обновим ранее созданную спецификацию hello-world.yaml. Не забудьте вставить в файл идентификаторы вашей функции и вашего сервисного аккаунта:
openapi: "3.0.0"
info:
version: 1.0.0
title: Updated API
paths:
/results:
  get:
   x-yc-apigateway-integration:
    type: cloud-functions
    function_id: <идентификатор функции>
    service_account_id: <идентификатор сервисного аккаунта>
   operationId: function-for-user-requests 
Вызовем перезагрузку нашей спецификации:
yc serverless api-gateway update \
--name hello-world \
--spec=hello-world.yaml 
Для тестирования вызовем функцию в браузере сначала без параметра secret, а затем — с ним:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/results
https://<идентификатор API Gateway>.apigw.yandexcloud.net/results?secret=cecfb23c-bc86-4ca2-b611-e79bc77e5c31 
В ответе увидим результаты тестирования сервиса yandex.ru из базы данных.
Иногда приходится тестировать функцию в процессе разработки: для этого в консоли управления на странице функции перейдите на вкладку Тестирование, в поле Шаблон данных выберите HTTPS-вызов. Нажмите кнопку Запустить тест, и вы увидите код ошибки.
image
Код функции проверяет параметр secret для авторизации, то есть при вызове вы должны передать секретную последовательность, чтобы функция выдала результат. Добавим secret в параметры запроса в поле Входные данные:
  "queryStringParameters": {
    "a": "2",
    "b": "1",
    "secret": "cecfb23c-bc86-4ca2-b611-e79bc77e5c31"
  }, 
Запустим тест ещё раз. В ответе отобразятся данные из базы, как и с запросами через браузер.
Task:
На предыдущем уроке мы рассмотрели, как работать с YDB через Document API — низкоуровневый HTTP API, совместимый с AWS DynamoDB API. В этом уроке рассмотрим операции создания таблицы, записи, чтения, изменения и удаления данных в таблице с помощью AWS CLI
# Разработка запросов AWS CLI для создания таблиц БД, запись и чтения данных.
Decision:
Сервисный аккаунт и ключ доступа
Для работы инструментов AWS вам понадобится создать сервисный аккаунт в облаке.
Выберите вкладку Сервисные аккаунты в каталоге, где расположена БД.
image
Нажмите кнопку Создать сервисный аккаунт.
Введите имя сервисного аккаунта. Чтобы назначить сервисному аккаунту роль на текущий каталог, нажмите Добавить роль и выберите роль, например editor.
image
Нажмите кнопку Создать.
image
Выберите созданный сервисный аккаунт и нажмите на строку с его именем. Нажмите кнопку Создать новый ключ на верхней панели. Выберите пункт Создать статический ключ доступа.
Сохраните идентификатор и секретный ключ.
image
Работа с AWS CLI
Установите AWS CLI с сайта https://aws.amazon.com/ru/cli/.
Для Windows: загрузите и запустите 64- или 32-разрядный установщик.
Для Mac и Linux: установите AWS CLI с помощью утилиты pip (требуется Python 2.6.5 или более поздней версии).
pip install awscli 
Для настройки AWS CLI запустите команду:
aws configure 
Введите сохраненные значения идентификатора ключа AWS Access Key ID и ключа AWS Secret Access Key и укажите ru-central1 в качестве Default region name.
Убедитесь, что в качестве переменной окружения ENDPOINT указано корректное значение эндпойнта вашей базы данных, либо добавьте его, как вы это делали в прошлом уроке: сохраните значение эндпойнта, указанное в строке Document API эндпоинт, в переменной окружения с помощью команды
export ENDPOINT=<значение endpoint> 
Создание таблицы
Создайте таблицу с помощью команды:
aws dynamodb create-table \
--table-name docapitest/series \
--attribute-definitions \
AttributeName=series_id,AttributeType=N \
AttributeName=title,AttributeType=S \
--key-schema \
AttributeName=series_id,KeyType=HASH \
AttributeName=title,KeyType=RANGE \
--endpoint $ENDPOINT 
Убедитесь, что в директории docapitest появилась таблица series.
image
Добавление данных в таблицу
Добавьте в таблицу две строки c помощью команд:
aws dynamodb put-item \
--table-name docapitest/series \
--item '{"series_id": {"N": "1"}, "title": {"S": "IT Crowd"}, "series_info": {"S": "The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by Ash Atalla and starring Chris ODowd, Richard Ayoade, Katherine Parkinson, and Matt Berry."}, "release_date": {"S": "2006-02-03"}}' \
--endpoint $ENDPOINT 
и
aws dynamodb put-item \
--table-name docapitest/series \
--item '{"series_id": {"N": "2"}, "title": {"S": "Silicon Valley"}, "series_info": {"S": "Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and Dave Krinsky."}, "release_date": {"S": "2014-04-06"}}' \
--endpoint $ENDPOINT 
Чтение данных из таблицы
Для того чтобы прочитать данные из таблицы, выполните команду:
aws dynamodb get-item --consistent-read \
--table-name docapitest/series \
--key '{"series_id": {"N": "1"}, "title": {"S": "IT Crowd"}}' \
--endpoint $ENDPOINT 
В качестве вывода вы увидите:
{
  "Item": {
    "release_date": {
      "S": "2006-02-03"
    },
    "series_id": {
      "N": "1"
    },
    "series_info": {
      "S": "The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by Ash Atalla and starring Chris ODowd, Richard Ayoade, Katherine Parkinson, and Matt Berry."
    },
    "title": {
      "S": "IT Crowd"
    }
  }

Для того, чтобы выбрать данные из таблицы series по ключу series_id, выполните следующую команду:
aws dynamodb query \
--table-name docapitest/series \
--key-condition-expression "series_id = :name" \
--expression-attribute-values '{":name":{"N":"2"}}' \
--endpoint $ENDPOINT 
В качестве результата вы увидите:
{
  "Items": [
    {
      "release_date": {
        "S": "2014-04-06"
      },
      "series_id": {
        "N": "2"
      },
      "series_info": {
        "S": "Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and Dave Krinsky."
      },
      "title": {
        "S": "Silicon Valley"
      }
    }
  ],
  "Count": 1,
  "ScannedCount": 1,
  "ConsumedCapacity": null

Удаление таблицы
aws dynamodb delete-table \
--table-name docapitest/series \
--endpoint $ENDPOINT 
На следующем практическом занятии мы разберём пример использования AWS SDK для работы с YDB в serverless-режиме.
Task:
В предыдущем уроке вы прошли подготовительные этапы: создали и настроили сервисный аккаунт, выпустили статический ключ, а также научились работать с таблицами и данными с помощью низкоуровневого API и CLI.
В этом уроке вы продолжите работу с инструментами AWS и с помощью AWS SDK для языка Python научитесь выполнять такие базовые операции, как создание таблиц БД, запись и чтение данных.
# Разработка запросов AWS CLI для создания таблиц БД, запись и чтения данных.
Decision:
Для выполнения работы вам понадобится Python версии 3.6 и выше и библиотека boto3.
Установить эту библиотеку можно с помощью команды:
pip install boto3 
Создание таблицы
Создайте файл с именем SeriesCreateTable.py и скопируйте в него исходный код программы:
import boto3
def create_series_table():
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.create_table(
    TableName = 'docapitest/series', # Series — имя таблицы 
    KeySchema = [
      {
        'AttributeName': 'series_id',
        'KeyType': 'HASH' # Ключ партицирования
      },
      {
        'AttributeName': 'title',
        'KeyType': 'RANGE' # Ключ сортировки
      }
    ],
    AttributeDefinitions = [
      {
        'AttributeName': 'series_id',
        'AttributeType': 'N' # Целое число
      },
      {
        'AttributeName': 'title',
        'AttributeType': 'S' # Строка
      },
    ]
  )
  return table
if __name__ == '__main__':
  series_table = create_series_table()
  print("Table status:", series_table.table_status) 
Отредактируйте исходный код файла и укажите значение endpoint_url вашей базы. Затем запустите написанный код:
python SeriesCreateTable.py 
С помощью консоли управления убедитесь, что в директории docapitest появилась таблица series.
Первоначальная загрузка данных
Для того чтобы вставить данные в созданную таблицу series, создайте файл с именем SeriesLoadData.py и скопируйте в него следующий исходный код программы:
from decimal import Decimal
import json
import boto3
def load_series(series):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document API эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  for serie in series:
    series_id = int(serie['series_id'])
    title = serie['title']
    print("Series added:", series_id, title)
    table.put_item(Item = serie)
if __name__ == '__main__':
  with open("seriesdata.json") as json_file:
    serie_list = json.load(json_file, parse_float = Decimal)
  load_series(serie_list) 
Отредактируйте файл SeriesLoadData.py и укажите значение endpoint_url вашей базы.
Для загрузки данных приложение будет использовать данные, которые записаны в файл seriesdata.json. Создайте этот файл и скопируйте в него описание сериалов:
[{
  "series_id": 1,
  "title": "IT Crowd",
  "info": {
   "release_date": "2006-02-03T00:00:00Z",
   "series_info": "The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry"
  }
},
{
  "series_id": 2,
  "title": "Silicon Valley",
  "info": {
   "release_date": "2014-04-06T00:00:00Z",
   "series_info": "Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley"
  }
},
{
  "series_id": 3,
  "title": "House of Cards",
  "info": {
   "release_date": "2013-02-01T00:00:00Z",
   "series_info": "House of Cards is an American political thriller streaming television series created by Beau Willimon. It is an adaptation of the 1990 BBC miniseries of the same name and based on the 1989 novel of the same name by Michael Dobbs"
  }
},
{
  "series_id": 3,
  "title": "The Office",
  "info": {
   "release_date": "2005-03-24T00:00:00Z",
   "series_info": "The Office is an American mockumentary sitcom television series that depicts the everyday work lives of office employees in the Scranton, Pennsylvania, branch of the fictional Dunder Mifflin Paper Company"
  }
},
{
  "series_id": 3,
  "title": "True Detective",
  "info": {
   "release_date": "2014-01-12T00:00:00Z",
   "series_info": "True Detective is an American anthology crime drama television series created and written by Nic Pizzolatto. The series, broadcast by the premium cable network HBO in the United States, premiered on January 12, 2014"
  }
},
{
  "series_id": 4,
  "title": "The Big Bang Theory",
  "info": {
   "release_date": "2007-09-24T00:00:00Z",
   "series_info": "The Big Bang Theory is an American television sitcom created by Chuck Lorre and Bill Prady, both of whom served as executive producers on the series, along with Steven Molaro"
  }
},
{
  "series_id": 5,
  "title": "Twin Peaks",
  "info": {
   "release_date": "1990-04-08T00:00:00Z",
   "series_info": "Twin Peaks is an American mystery horror drama television series created by Mark Frost and David Lynch that premiered on April 8, 1990, on ABC until its cancellation after its second season in 1991 before returning as a limited series in 2017 on Showtime"
  }
}

Запустите программу (для её успешного выполнения может понадобиться указать в скрипте SeriesLoadData.py полный путь к файлу seriesdata.json):
python SeriesLoadData.py 
В результате выполнения вы увидите вывод программы:
Series added: 1 IT Crowd
Series added: 2 Silicon Valley
Series added: 3 House of Cards
Series added: 3 The Office
Series added: 3 True Detective
Series added: 4 The Big Bang Theory
Series added: 5 Twin Peaks 
Работа с записями
Создание записи
Теперь создайте файл SeriesItemPut.py и скопируйте в него следующий код:
from pprint import pprint
import boto3
def put_serie(series_id, title, release_date, series_info):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  response = table.put_item(
   Item = {
      'series_id': series_id,
      'title': title,
      'info': {
        'release_date': release_date,
        'series_info': series_info
      }
    }
  )
  return response
if __name__ == '__main__':
  serie_resp = put_serie(3, "Supernatural", "2015-09-13",
             "Supernatural is an American television series created by Eric Kripke")
  print("Series added successfully:")
  pprint(serie_resp, sort_dicts = False) 
В результате выполнения этого кода в таблице добавится запись о сериале Supernatural.
Чтение записи
Создайте файл SeriesItemGet.py и скопируйте в него следующий код:
from pprint import pprint
import boto3
from botocore.exceptions import ClientError
def get_serie(title, series_id):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  try:
    response = table.get_item(Key = {'series_id': series_id, 'title': title})
  except ClientError as e:
    print(e.response['Error']['Message'])
  else:
    return response['Item']
if __name__ == '__main__':
  serie = get_serie("Supernatural", 3,)
  if serie:
    print("Record read:")
    pprint(serie, sort_dicts = False) 
Результатом будет сообщение в формате JSON с данными о сериале.
Обновление записи
В файле SeriesItemUpdate.py разместите код обновления записи:
from decimal import Decimal
from pprint import pprint
import boto3
def update_serie(title, series_id, release_date, rating):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  response = table.update_item(
    Key = {
      'series_id': series_id,
      'title': title
    },
    UpdateExpression = "set info.release_date = :d, info.rating = :r ",
    ExpressionAttributeValues = {
      ':d': release_date,
      ':r': Decimal(rating)
    },
    ReturnValues = "UPDATED_NEW"
  )
  return response
if __name__ == '__main__':
  update_response = update_serie(
    "Supernatural", 3, "2005-09-13", 8)
  print("Series updated:")
  pprint(update_response, sort_dicts = False) 
Результатом будет сообщение в формате JSON с измененными данными.
Удаление записи
Создайте файл SeriesItemDelete.py и скопируйте в него следующий код:
from decimal import Decimal
from pprint import pprint
import boto3
from botocore.exceptions import ClientError
def delete_underrated_serie(title, series_id, rating):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  try:
    response = table.delete_item(
      Key = {
        'series_id': series_id,
        'title': title
      },
      ConditionExpression = "info.rating <= :val",
      ExpressionAttributeValues = {
        ":val": Decimal(rating)
      }
    )
  except ClientError as e:
    if e.response['Error']['Code'] == "ConditionalCheckFailedException":
      print(e.response['Error']['Message'])
    else:
      raise
  else:
    return response
if __name__ == '__main__':
  print("Deleting...")
  delete_response = delete_underrated_serie("Supernatural", 3, 8)
  if delete_response:
    print("Series data deleted:")
    pprint(delete_response, sort_dicts = False) 
Убедитесь, что данные о сериале Supernatural удалены из таблицы.
Поиск по ключам партицирования и сортировки
Код поиска разместите в новом файле SeriesQuery.py:
from pprint import pprint
import boto3
from boto3.dynamodb.conditions import Key
def query_and_project_series(series_id, title_range):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  response = table.query(
    ProjectionExpression = "series_id, title, info.release_date",
    KeyConditionExpression = Key('series_id').eq(series_id) & Key('title').begins_with(title_range)
  )
  return response['Items']
if __name__ == '__main__':
  query_id = 3
  query_range = 'T'
  print(f"Series with ID = {query_id} and names beginning with "
     f"{query_range}")
  series = query_and_project_series(query_id, query_range)
  for serie in series:
    print(f"\n{serie['series_id']} : {serie['title']}")
    pprint(serie['info']) 
Результатом будет сообщение:
Series with ID = 3 and names beginning with T
3 : The Office
{'release_date': '2005-03-24T00:00:00Z'}
3 : True Detective
{'release_date': '2014-01-12T00:00:00Z'} 
Запуск операции Scan
Создайте файл SeriesTableScan.py и скопируйте в него следующий код:
from pprint import pprint
import boto3
from boto3.dynamodb.conditions import Key
def scan_series(id_range, display_series):
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  scan_kwargs = {
    'FilterExpression': Key('series_id').between(*id_range),
    'ProjectionExpression': "series_id, title, info.release_date"
  }
  done = False
  start_key = None
  while not done:
    if start_key:
      scan_kwargs['ExclusiveStartKey'] = start_key
    response = table.scan(**scan_kwargs)
    display_series(response.get('Items', []))
    start_key = response.get('LastEvaluatedKey', None)
    done = start_key is None
if __name__ == '__main__':
  def print_series(series):
    for serie in series:
      print(f"\n{serie['series_id']} : {serie['title']}")
      pprint(serie['info'])
  query_range = (1, 3)
  print(f"Series with IDs from {query_range[0]} to {query_range[1]}...")
  scan_series(query_range, print_series) 
Результатом будет сообщение:
Series with IDs from 1 to 3...
3 : House of Cards
{'release_date': '2013-02-01T00:00:00Z'}
3 : The Office
{'release_date': '2005-03-24T00:00:00Z'}
3 : True Detective
{'release_date': '2014-01-12T00:00:00Z'}
1 : IT Crowd
{'release_date': '2006-02-03T00:00:00Z'}
2 : Silicon Valley
{'release_date': '2014-04-06T00:00:00Z'} 
Удаление таблицы
Создайте файл SeriesTableDelete.py и скопируйте в него следующий код:
import boto3
def delete_serie_table():
  ydb_docapi_client = boto3.resource('dynamodb', endpoint_url = "<Document_API_эндпоинт>")
  table = ydb_docapi_client.Table('docapitest/series')
  table.delete()
if __name__ == '__main__':
  delete_serie_table()
  print("Table Series deleted") 
Убедитесь, что таблица удалена из базы данных.
Task:
В этом уроке вы доработаете систему проверки доступности веб-ресурсов, которую создали на предыдущих практических занятиях. В текущем варианте она проверяет только доступность сайта ya.ru. Теперь давайте добавим в неё возможность ставить задачи по проверке доступности других веб-ресурсов.
# Разработка системы проверки доступности веб-ресурсов URL с доступом по REST API.
Decision:
Общая архитектура системы
У системы есть два метода:
CheckUrl — ставит задачу на проверку указанного URL.
GetResult — считывает результаты проверки.
Метод CheckUrl обрабатывается функцией, которая будет складывать все запросы в очередь. Функция-обработчик будет вызываться раз в секунду, считывать URL из очереди, проверять его доступность и записывать результат в базу данных. Оттуда этот результат можно будет получить с помощью метода GetResult.
image
Мы не будем менять уже созданные функции и таблицу в PostgreSQL, сделаем новые.
Работать с YMQ из функций мы будем с помощью библиотеки boto3. Чтобы её использовать, нужно создать сервисный аккаунт с секретным ключом доступа, а затем настроить зависимости функции. Сделаем это после того, как создадим очередь.
Шаг 1. Проверить наличие сервисного аккаунта
Если вы ранее создавали сервисный аккаунт с именем service-account-for-cf, добавляли вновь созданному сервисному аккаунту роли editor и другие, то вам остаётся только создать ключ доступа:
yc iam access-key create --service-account-name service-account-for-cf 
В результате вы получите примерно следующее:
  access_key:
    id: ajefraollq5puj2tir1o
    service_account_id: ajetdv28pl0a1a8r41f0
    created_at: "2021-08-23T21:13:05.677319393Z"
    key_id: BTPNvWthv0ZX2xVmlPIU
  secret: cWLQ0HrTM0k_qAac43cwMNJA8VV_rfTg_kd4xVPi 
Здесь key_id — это идентификатор ключа доступа ACCESS_KEY. А secret — это секретный ключ SECRET_KEY. Переменные ACCESS_KEY и SECRET_KEY могут быть использованы для задания соответствующих значений aws_access_key_id и aws_secret_access_key при использовании библиотеки boto3.
Шаг 2. Создание очереди Yandex Message Queue
Вы можете создать очередь одним из трёх способов:
через консоль управления;
с помощью консольной утилиты aws;
с помощью Terraform.
В этом уроке мы будем использовать консоль управления. Откройте раздел Message Queue и нажмите кнопку Создать очередь.
image
В настройках создаваемой очереди задайте имя очереди my-first-queue, затем выберите тип очереди Стандартная и нажмите кнопку Создать.
image
Очередь создана.
image
Теперь зайдите в настройки очереди, чтобы посмотреть параметры подключения к ней. Нам потребуется значение URL.
image
Шаг 3. Создание функции
Для создания функции зададим ряд переменных:
VERBOSE_LOG — определяет, пишет ли функция подробности своего выполнения в журнал.
AWS_ACCESS_KEY_ID — значение «Идентификатор ключа» из сервисного аккаунта, который мы сделали ранее.
AWS_SECRET_ACCESS_KEY — значение «Секретный ключ» из того же сервисного аккаунта.
QUEUE_URL — URL на очередь, его можно получить на обзорной странице созданной ранее очереди.
Чтобы задать переменные, выполните в консоли следующие команды:
echo "export VERBOSE_LOG=True" >> ~/.bashrc && . ~/.bashrc
echo "export AWS_ACCESS_KEY_ID=<AWS_ACCESS_KEY_ID>" >> ~/.bashrc && . ~/.bashrc
echo "export AWS_SECRET_ACCESS_KEY=<AWS_SECRET_ACCESS_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export QUEUE_URL=<QUEUE_URL>" >> ~/.bashrc && . ~/.bashrc 
Создайте файл my-url-receiver-function.py со следующим содержанием:
import logging
import os
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool
queue_url = os.environ['QUEUE_URL']
def log(logString):
  if verboseLogging:
    logger.info(logString)
def handler(event, context):
  # Get url
  try:
    url = event['queryStringParameters']['url']
  except Exception as error:
    logger.error(error)
    statusCode = 400
    return {
      'statusCode': statusCode
    }
  # Create client
  client = boto3.client(
    service_name='sqs',
    endpoint_url='https://message-queue.api.cloud.yandex.net',
    region_name='ru-central1'
  )
  # Send message to queue
  client.send_message(
    QueueUrl=queue_url,
    MessageBody=url
  )
  log('Successfully sent test message to queue')
  statusCode = 200
  return {
    'statusCode': statusCode
  } 
Затем воспользуйтесь командой pipreqs $PWD --force для формирования файла requirements.txt и упакуйте файлы с функцией и требованиями в ZIP-архив.
zip my-url-receiver-function my-url-receiver-function.py requirements.txt 
Создайте функцию и её версию:
yc serverless function create \
--name my-url-receiver-function \
--description "function for url"
yc serverless function version create \
--function-name=my-url-receiver-function \
--memory=256m \
--execution-timeout=5s \
--runtime=python312 \
--entrypoint=my-url-receiver-function.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--environment VERBOSE_LOG=$VERBOSE_LOG \
--environment AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
--environment AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
--environment QUEUE_URL=$QUEUE_URL \
--source-path my-url-receiver-function.zip 
Тестирование функции
Перейдите в раздел Cloud Functions консоли управления облаком и выберите созданную функцию my-url-receiver-function. На вкладке Тестирование в боковом меню выберите шаблон HTTPS-вызов и замените раздел queryStringParameters:
  "queryStringParameters": {
    "a": "2",
    "b": "1",
  },   
на аналогичный, но с параметром url с любым сайтом. Важно указывать ссылку целиком.
  "queryStringParameters": {
    "url": "https://ya.ru/"
  },   
Нажмите кнопку Запустить тест.
image
Если вы всё сделали правильно, то увидите код статуса 200. При этом в очереди увеличится количество сообщений.
image
Шаг 4. Обновление спецификации API Gateway
Функция готова, но по умолчанию она не является публичной. Предоставим доступ к ней с помощью API-шлюза. Для этого необходимо обновить ранее созданную спецификацию hello-world.yaml. Если у вас нет её под рукой, выгрузите её из облака:
yc serverless api-gateway get-spec \
--name hello-world >> hello-world-new.yaml 
Внесите изменения, добавив секцию о ранее созданной функции:
  /check:
    get:
      x-yc-apigateway-integration:
        type: cloud-functions
        function_id: <идентификатор функции>
        service_account_id: <идентификатор сервисного аккаунта>
      operationId: add-url 
Обновите конфигурацию:
yc serverless api-gateway update \
--name hello-world \
--spec=hello-world-new.yaml 
Для тестирования выполните вызов функции в браузере:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/check?url=https://ya.ru/ 
После каждого запроса количество сообщений в очереди будет увеличиваться на одно.
image
Шаг 5. Создание функции для чтения из очереди
В предыдущих работах мы создавали функцию, использующую подключение к БД. Здесь мы повторим этот опыт.
Проверим, что нам доступны переменные для инициации подключения: CONNECTION_ID, DB_USER, DB_HOST. Мы создавали их в предыдущей работе с помощью следующих команд:
echo "export CONNECTION_ID=<CONNECTION_ID>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_USER=<DB_USER>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_HOST=<DB_HOST>" >> ~/.bashrc && . ~/.bashrc 
Также для работы с очередью нам потребуются переменные VERBOSE_LOG, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY и QUEUE_URL, заданные на предыдущих шагах.
Создадим функцию function-for-url-from-mq.py и воспользуемся командой pipreqs $PWD --force, чтобы сформировать для нее файл requirements.txt.
import logging
import os
import boto3
import datetime
import requests
#Эти библиотеки нужны для работы с PostgreSQL
import psycopg2
import psycopg2.errors
import psycopg2.extras
CONNECTION_ID = os.getenv("CONNECTION_ID")
DB_USER = os.getenv("DB_USER")
DB_HOST = os.getenv("DB_HOST")
QUEUE_URL = os.environ['QUEUE_URL']
# Настраиваем функцию для записи информации в журнал функции
# Получаем стандартный логер языка Python
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Вычитываем переменную VERBOSE_LOG, которую мы указываем в переменных окружения 
verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool
#Функция log, которая запишет текст в журнал выполнения функции, если в переменной окружения VERBOSE_LOG будет значение True
def log(logString):
  if verboseLogging:
    logger.info(logString)
#Получаем подключение
def getConnString(context):
  """
  Extract env variables to connect to DB and return a db string
  Raise an error if the env variables are not set
  :return: string
  """
  connection = psycopg2.connect(
    database=CONNECTION_ID, # Идентификатор подключения
    user=DB_USER, # Пользователь БД
    password=context.token["access_token"],
    host=DB_HOST, # Точка входа
    port=6432,
    sslmode="require")
  return connection
"""
  Create SQL query with table creation
"""
def makeCreateDataTableQuery(table_name):
  query = f"""CREATE TABLE public.{table_name} (
  url text,
  result integer,
  time float
  )"""
  return query
def makeInsertDataQuery(table_name, url, result, time):
  query = f"""INSERT INTO {table_name} 
  (url, result,time)
  VALUES('{url}', {result}, {time})
  """
  return query
def handler(event, context):
  # Create client
  client = boto3.client(
    service_name='sqs',
    endpoint_url='https://message-queue.api.cloud.yandex.net',
    region_name='ru-central1'
  )
  # Receive sent message
  messages = client.receive_message(
    QueueUrl=QUEUE_URL,
    MaxNumberOfMessages=1,
    VisibilityTimeout=60,
    WaitTimeSeconds=1
  ).get('Messages')
  if messages is None:
    return {
      'statusCode': 200
    }
  for msg in messages:
    log('Received message: "{}"'.format(msg.get('Body')))
  # Get url from message
  url = msg.get('Body');
  # Check url
  try:
    now = datetime.datetime.now()
    response = requests.get(url, timeout=(1.0000, 3.0000))
    timediff = datetime.datetime.now() - now
    result = response.status_code
  except requests.exceptions.ReadTimeout:
    result = 601
  except requests.exceptions.ConnectTimeout:
    result = 602
  except requests.exceptions.Timeout:
    result = 603
  log(f'Result: {result} Time: {timediff.total_seconds()}')  
  connection = getConnString(context)
  log(f'Connecting: {connection}')  
  cursor = connection.cursor()
  table_name = 'custom_request_result'
  sql = makeInsertDataQuery(table_name, url, result, timediff.total_seconds())
  log(f'Exec: {sql}')
  try:
    cursor.execute(sql)
  except psycopg2.errors.UndefinedTable as error:
    log(f'Table not exist - create and repeate insert')
    connection.rollback()
    logger.error(error)
    createTable = makeCreateDataTableQuery(table_name)
    log(f'Exec: {createTable}')
    cursor.execute(createTable)
    connection.commit()
    log(f'Exec: {sql}')
    cursor.execute(sql)
  except Exception as error:
    logger.error( error)
  connection.commit()
  cursor.close()
  connection.close()
  # Delete processed messages
  for msg in messages:
    client.delete_message(
      QueueUrl=QUEUE_URL,
      ReceiptHandle=msg.get('ReceiptHandle')
    )
    print('Successfully deleted message by receipt handle "{}"'.format(msg.get('ReceiptHandle')))
  statusCode = 200
  return {
    'statusCode': statusCode
  } 
При создании сразу задайте все необходимые переменные и сервисный аккаунт:
zip function-for-url-from-mq function-for-url-from-mq.py requirements.txt
yc serverless function create \
--name function-for-url-from-mq \
--description "function for url from mq"
yc serverless function version create \
--function-name=function-for-url-from-mq \
--memory=256m \
--execution-timeout=5s \
--runtime=python312 \
--entrypoint=function-for-url-from-mq.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--environment VERBOSE_LOG=True \
--environment CONNECTION_ID=$CONNECTION_ID \
--environment DB_USER=$DB_USER \
--environment DB_HOST=$DB_HOST \
--environment AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
--environment AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
--environment QUEUE_URL=$QUEUE_URL \
--source-path function-for-url-from-mq.zip 
Протестируйте функцию.
image
После её выполнения количество сообщений в очереди уменьшится, а в базе данных появится новая таблица с результатами тестирования доступности функции.
image
image
Шаг 6. Создание триггера
Создадим триггер, который будет вызывать функцию обработки сообщений из очереди один раз в минуту. Он будет использовать cron-выражение:
yc serverless trigger create timer \
--name trigger-for-mq \
--invoke-function-name function-for-url-from-mq \
--invoke-function-service-account-id $SERVICE_ACCOUNT_ID \
--cron-expression '* * * * ? *' 
Cron-выражение * * * * ? * означает вызов функции function-for-url-from-mq один раз в минуту. Подробнее про cron-выражения можно прочитать в документации.
image
Теперь у нас есть функция, которая раз в минуту будет пробовать взять из очереди URL и проверить его. Также есть метод REST API, который позволяет записывать URL в очередь независимо от работы обработчика. Мы можем вызывать созданный метод как угодно часто. Очередь будет просто накапливаться, а затем обработчик будет постепенно её разбирать.
В итоге вы получили асинхронную систему проверки доступности URL с доступом по REST API. Вы не создали ни одной виртуальной машины, но решили вопросы масштабирования и отказоустойчивости системы.
Удаление триггера-таймера
По завершении практической работы не забудьте удалить созданный вами триггер trigger-for-mq, иначе он будет работать, пока не исчерпает деньги на аккаунте:
yc serverless trigger delete trigger-for-mq 
Не забудьте удалить или остановить все созданные вами ресурсы: триггеры, очереди YMQ и кластер базы данных.
Task:
В этой практической работе мы реализуем проект, который позволит пользователям конвертировать видеофайлы в GIF. Такая задача хорошо подходит для Cloud Functions, потому что конвертирование отнимает немало ресурсов процессора, и чем качественнее видео, тем больше ресурсов требуется на его обработку.
# Разработка конвертировании видеофайлы в GIF.
Decision:
Почему для решения этой задачи нужны очереди?
Представим, что мы попытались решить эту задачу «в лоб». Пользователь заходит на страницу и вводит ссылку на видеофайл. Сервис скачивает его, конвертирует и отдает ссылку на GIF. Возникают две серьёзные проблемы:
Синхронное соединение не всегда стабильно. Чем дольше вы его держите, тем выше вероятность, что оно разорвётся. В этом случае всё придётся сделать заново. А если соединение нестабильно, то пользователь может и не дождаться результата.
Задача ресурсоёмкая: если сервисом одновременно воспользуются много пользователей с большими видеороликами, мощностей может не хватить.
Чтобы избежать этих проблем, в архитектуру сервиса необходимо встроить очередь.
Шаг 1. Сервисный аккаунт и Lockbox
Создание сервисного аккаунта
Создайте сервисный аккаунт с именем ffmpeg-account-for-cf:
export SERVICE_ACCOUNT=$(yc iam service-account create --name ffmpeg-account-for-cf \
--description "service account for serverless" \
--format json | jq -r .) 
Проверьте текущий список сервисных аккаунтов:
yc iam service-account list 
После проверки запишите ID созданного сервисного аккаунта в переменную SERVICE_ACCOUNT_ID:
echo "export SERVICE_ACCOUNT_FFMPEG_ID=<ID>" >> ~/.bashrc && . ~/.bashrc
echo $SERVICE_ACCOUNT_FFMPEG_ID 
Назначение роли сервисному аккаунту
Добавим вновь созданному сервисному аккаунту роли storage.viewer, storage.uploader, ymq.reader, ymq.writer, ydb.admin, serverless.functions.invoker, и lockbox.payloadViewer:
echo "export FOLDER_ID=$(yc config get folder-id)" >> ~/.bashrc && . ~/.bashrc
echo $FOLDER_ID
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role storage.viewer
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role storage.uploader
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role ymq.reader
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role ymq.writer
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role ydb.admin
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role serverless.functions.invoker
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role lockbox.payloadViewer
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role editor 
Вы можете назначить несколько ролей и с помощью команды set-access-binding. Но эта команда полностью перезаписывает права доступа к ресурсу и все текущие роли на него будут удалены! Поэтому сначала убедитесь, что ресурсу не назначены роли, которые вы не хотите потерять:
yc resource-manager folder list-access-bindings $FOLDER_ID
yc resource-manager folder set-access-bindings $FOLDER_ID \
--access-binding role=storage.viewer,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=storage.uploader,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=ymq.reader,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=ymq.writer,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=ydb.admin,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=serverless.functions.invoker,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=lockbox.payloadViewer,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=editor,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID 
Создание ключа доступа для сервисного аккаунта
Этот этап нужен для получения идентификатора ключа доступа и секретного ключа, которые будут использованы для загрузки файлов в Object Storage, работы с Yandex Message Queue и т. д. Для создания ключа доступа необходимо вызвать следующую команду:
yc iam access-key create --service-account-name ffmpeg-account-for-cf 
В результате вы получите примерно следующее:
  access_key:
    id: ajefraollq5puj2tir1o
    service_account_id: ajetdv28pl0a1a8r41f0
    created_at: "2021-08-23T21:13:05.677319393Z"
    key_id: BTPNvWthv0ZX2xVmlPIU
  secret: cWLQ0HrTM0k_qAac43cwMNJA8VV_rfTg_kd4xVPi 
Здесь key_id — это идентификатор ключа доступа ACCESS_KEY_ID. А secret — это секретный ключ SECRET_ACCESS_KEY. Переменные ACCESS_KEY_ID и SECRET_ACCESS_KEY могут быть использованы для задания соответствующих значений aws_access_key_id и aws_secret_access_key при использовании библиотеки boto3.
Создание элемента в сервисе Lockbox
В сервисе Lockbox (находится на стадии Preview) создайте ваш первый секрет, состоящий из набора версий, в которых хранятся ваши данные. Версия содержит наборы ключей и значений:
Ключ — несекретное название для значения, по которому вы будете его идентифицировать.
Значение — это секретные данные.
Версия не изменяется. Для любого изменения количества пар ключей-значений или их содержимого необходимо создать новую версию. Создадим секрет с именем ffmpeg-sa-key и парой ключей ACCESS_KEY_ID и SECRET_ACCESS_KEY:
yc lockbox secret create --name ffmpeg-sa-key \
--folder-id $FOLDER_ID \
--description "keys for serverless" \
--payload '[{"key": "ACCESS_KEY_ID", "text_value": <ACCESS_KEY_ID>}, {"key": "SECRET_ACCESS_KEY", "text_value": "<SECRET_ACCESS_KEY>"}]' 
Получим и запишем значение SECRET_ID, оно нам потребуется при создании функции:
yc lockbox secret list
yc lockbox secret get --name ffmpeg-sa-key
echo "export SECRET_ID=<SECRET_ID>" >> ~/.bashrc && . ~/.bashrc
echo $SECRET_ID 
Шаг 2. Создание очереди Yandex Message Queue
Для создания очереди Yandex Message Queue вы можете использовать три разных способа:
консоль управления;
консольная утилита aws;
Terraform.
Создание очереди с помощью утилиты aws
Воспользуемся AWS CLI. Для начала задайте конфигурацию с помощью команды aws configure. При этом от вас потребуется ввести:
AWS Access Key ID — идентификатор ключа доступа key_id сервисного аккаунта, полученный на предыдущем шаге.
AWS Secret Access Key — секретный ключ secret сервисного аккаунта, полученный на предыдущем шаге.
Default region name — используйте значение ru-central1.
По завершению конфигурации вы сможете создать очередь:
aws configure
aws sqs create-queue --queue-name ffmpeg --endpoint https://message-queue.api.cloud.yandex.net/ 
В результате успешного выполнения предыдущей команды в ответ вы получите URL:
  {
    "QueueUrl": "https://message-queue.api.cloud.yandex.net/b1ga4gj7agij03ln6aov/dj6000000003kv2t02b3/ffmpeg"
  } 
Запишем значения URL в переменную YMQ_QUEUE_URL. Она потребуется нам при создании функции:
echo "export YMQ_QUEUE_URL=<YMQ_QUEUE_URL>" >> ~/.bashrc && . ~/.bashrc
echo $YMQ_QUEUE_URL 
Ещё вам потребует значение атрибута QueueArn, получим его:
aws sqs get-queue-attributes \
--endpoint https://message-queue.api.cloud.yandex.net \
--queue-url $YMQ_QUEUE_URL \
--attribute-names QueueArn 
В результате вы получите ответ вида:
  {
    "Attributes": {
      "QueueArn": "yrn:yc:ymq:ru-central1:b1gl21bkgss4msekt08i:ffmpeg"
    }
  } 
Сохраним значение QueueArn в переменную YMQ_QUEUE_ARN:
echo "export YMQ_QUEUE_ARN=<YMQ_QUEUE_ARN>" >> ~/.bashrc && . ~/.bashrc
echo $YMQ_QUEUE_ARN 
Шаг 3. Создание базы данных в сервисе YDB
Создадим базу данных YDB с именем ffmpeg и типом serverless, используя для этого флаг --serverless:
yc ydb database create ffmpeg \
--serverless \
--folder-id $FOLDER_ID
yc ydb database list 
Сразу получим и сохраним document_api_endpoint в значение переменной DOCAPI_ENDPOINT:
yc ydb database get --name ffmpeg
echo "export DOCAPI_ENDPOINT=<DOCAPI_ENDPOINT>" >> ~/.bashrc && . ~/.bashrc
echo $DOCAPI_ENDPOINT 
Как только база данных создана, воспользуемся ранее использованной утилитой AWS CLI для создания документной таблицы в этой базе данных. Всю конфигурацию возьмем из файла tasks.json:
{
"AttributeDefinitions": [
  {
   "AttributeName": "task_id",
   "AttributeType": "S"
  }
],
"KeySchema": [
  {
   "AttributeName": "task_id",
   "KeyType": "HASH"
  }
],
"TableName": "tasks"

Находясь в одном каталоге с файлом tasks.json, вызовите следующую команду для создания таблицы:
aws dynamodb create-table \
--cli-input-json file://tasks.json \
--endpoint-url $DOCAPI_ENDPOINT \
--region ru-central1 
В консоли управления убедитесь, что БД ffmpeg создана, и в ней есть пустая таблица tasks.
Шаг 4. Создание бакета в сервисе Object Storage
Самый простой способ создания бакета в Object Storage — это использование консоли управления.
В консоли управления в вашем рабочем каталоге выберите сервис Object Storage. Нажмите кнопку Создать бакет. На странице создания бакета введите имя, в нашем примере это будет storage-for-ffmpeg, остальные параметры не меняйте.
Нажмите кнопку Создать бакет для завершения операции. Далее вы всегда сможете поменять класс хранилища, его размер и настройки доступа.
Сохраним название бакета для дальнейшего использования:
echo "export S3_BUCKET=<имя бакета>" >> ~/.bashrc && . ~/.bashrc
echo $S3_BUCKET 
Шаг 5. Создание функций
При создании функций нам потребуется ряд переменных:
SECRET_ID — идентификатор секрета (можно получить из таблицы со списком секретов);
YMQ_QUEUE_URL — URL очереди (можно получить на странице обзора);
DOCAPI_ENDPOINT — его можно получить на странице обзора БД, нужен именно Document API;
S3_BUCKET — имя бакета, в нашем случае это storage-for-ffmpeg.
Проверим заданные ранее переменные:
echo $SERVICE_ACCOUNT_FFMPEG_ID
echo $SECRET_ID
echo $YMQ_QUEUE_URL
echo $DOCAPI_ENDPOINT
echo $S3_BUCKET 
Для обработки видео понадобится утилита FFmpeg. Скачайте статический релизный бинарный файл для Linux amd64 на сайте ffmpeg.org (обычно он находится в разделе FFmpeg Static Builds и называется примерно так: ffmpeg-release-amd64-static.tar.xz). Извлеките из архива файл ffmpeg. Обратите внимание, что у этого файла должны быть заданы права доступа на выполнение (установить нужный флаг можно с помощью команды chmod a+x ffmpeg).
Поскольку через консоль управления можно прикладывать файлы размером не более 3,5 МБ, загрузим код функций и файл ffmpeg в объектное хранилище (Object Storage).
Исходный код в файле index.py содержит обе необходимые нам функции:
import json
import os
import subprocess
import uuid
from urllib.parse import urlencode
import boto3
import requests
import yandexcloud
from yandex.cloud.lockbox.v1.payload_service_pb2 import GetPayloadRequest
from yandex.cloud.lockbox.v1.payload_service_pb2_grpc import PayloadServiceStub
boto_session = None
storage_client = None
docapi_table = None
ymq_queue = None
def get_boto_session():
  global boto_session
  if boto_session is not None:
    return boto_session
  # initialize lockbox and read secret value
  yc_sdk = yandexcloud.SDK()
  channel = yc_sdk._channels.channel("lockbox-payload")
  lockbox = PayloadServiceStub(channel)
  response = lockbox.Get(GetPayloadRequest(secret_id=os.environ['SECRET_ID']))
  # extract values from secret
  access_key = None
  secret_key = None
  for entry in response.entries:
    if entry.key == 'ACCESS_KEY_ID':
      access_key = entry.text_value
    elif entry.key == 'SECRET_ACCESS_KEY':
      secret_key = entry.text_value
  if access_key is None or secret_key is None:
    raise Exception("secrets required")
  print("Key id: " + access_key)
  # initialize boto session
  boto_session = boto3.session.Session(
    aws_access_key_id=access_key,
    aws_secret_access_key=secret_key
  )
  return boto_session
def get_ymq_queue():
  global ymq_queue
  if ymq_queue is not None:
    return ymq_queue
  ymq_queue = get_boto_session().resource(
    service_name='sqs',
    endpoint_url='https://message-queue.api.cloud.yandex.net',
    region_name='ru-central1'
  ).Queue(os.environ['YMQ_QUEUE_URL'])
  return ymq_queue
def get_docapi_table():
  global docapi_table
  if docapi_table is not None:
    return docapi_table
  docapi_table = get_boto_session().resource(
    'dynamodb',
    endpoint_url=os.environ['DOCAPI_ENDPOINT'],
    region_name='ru-central1'
  ).Table('tasks')
  return docapi_table
def get_storage_client():
  global storage_client
  if storage_client is not None:
    return storage_client
  storage_client = get_boto_session().client(
    service_name='s3',
    endpoint_url='https://storage.yandexcloud.net',
    region_name='ru-central1'
  )
  return storage_client
# API handler
def create_task(src_url):
  task_id = str(uuid.uuid4())
  get_docapi_table().put_item(Item={
    'task_id': task_id,
    'ready': False
  })
  get_ymq_queue().send_message(MessageBody=json.dumps({'task_id': task_id, "src": src_url}))
  return {
    'task_id': task_id
  }
def get_task_status(task_id):
  task = get_docapi_table().get_item(Key={
    "task_id": task_id
  })
  if task['Item']['ready']:
    return {
      'ready': True,
      'gif_url': task['Item']['gif_url']
    }
  return {'ready': False}
def handle_api(event, context):
  action = event['action']
  if action == 'convert':
    return create_task(event['src_url'])
  elif action == 'get_task_status':
    return get_task_status(event['task_id'])
  else:
    return {"error": "unknown action: " + action}
# Converter handler
def download_from_ya_disk(public_key, dst):
  api_call_url = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?' + \
         urlencode(dict(public_key=public_key))
  response = requests.get(api_call_url)
  download_url = response.json()['href']
  download_response = requests.get(download_url)
  with open(dst, 'wb') as video_file:
    video_file.write(download_response.content)
def upload_and_presign(file_path, object_name):
  client = get_storage_client()
  bucket = os.environ['S3_BUCKET']
  client.upload_file(file_path, bucket, object_name)
  return client.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': object_name}, ExpiresIn=3600)
def handle_process_event(event, context):
  for message in event['messages']:
    task_json = json.loads(message['details']['message']['body'])
    task_id = task_json['task_id']
    # Download video
    download_from_ya_disk(task_json['src'], '/tmp/video.mp4')
    # Convert with ffmpeg
    subprocess.run(['ffmpeg', '-i', '/tmp/video.mp4', '-r', '10', '-s', '320x240', '/tmp/result.gif'])
    result_object = task_id + ".gif"
    # Upload to Object Storage and generate presigned url
    result_download_url = upload_and_presign('/tmp/result.gif', result_object)
    # Update task status in DocAPI
    get_docapi_table().update_item(
      Key={'task_id': task_id},
      AttributeUpdates={
        'ready': {'Value': True, 'Action': 'PUT'},
        'gif_url': {'Value': result_download_url, 'Action': 'PUT'},
      }
    )
  return "OK" 
Сгенерируйте файл requirements.txt:
pipreqs $PWD --force 
Находясь в директории с исходными файлами, упакуем все нужные файлы в ZIP-архив.
zip src.zip index.py requirements.txt ffmpeg 
В Object Storage для простоты используем тот же бакет, куда далее будем складывать видео. На вкладке Объекты, вверху справа нажмите кнопку Загрузить и выберите созданный архив.
Создадим функции ffmpeg-api и ffmpeg-converter, при этом сразу зададим все необходимые переменные и сервисный аккаунт:
yc serverless function create \
--name ffmpeg-api \
--description "function for ffmpeg-api"
yc serverless function create \
--name ffmpeg-converter \
--description "function for ffmpeg-converter"
yc serverless function version create \
--function-name ffmpeg-api \
--memory=256m \
--execution-timeout=5s \
--runtime=python37 \
--entrypoint=index.handle_api \
--service-account-id $SERVICE_ACCOUNT_FFMPEG_ID \
--environment SECRET_ID=$SECRET_ID \
--environment YMQ_QUEUE_URL=$YMQ_QUEUE_URL \
--environment DOCAPI_ENDPOINT=$DOCAPI_ENDPOINT \
--package-bucket-name $S3_BUCKET \
--package-object-name src.zip
yc serverless function version create \
--function-name ffmpeg-converter \
--memory=2048m \
--execution-timeout=600s \
--runtime=python37 \
--entrypoint=index.handle_process_event \
--service-account-id $SERVICE_ACCOUNT_FFMPEG_ID \
--environment SECRET_ID=$SECRET_ID \
--environment YMQ_QUEUE_URL=$YMQ_QUEUE_URL \
--environment DOCAPI_ENDPOINT=$DOCAPI_ENDPOINT \
--environment S3_BUCKET=$S3_BUCKET \
--package-bucket-name $S3_BUCKET \
--package-object-name src.zip 
Тестирование функции
В консоли управления перейдите из рабочего каталога в раздел Cloud Functions и выберите ранее созданную функцию ffmpeg-api. Перейдите на вкладку Тестирование в боковом меню, выберите шаблон данных Без шаблона и добавьте во вводные данные JSON:
{"action":"convert", "src_url":"https://disk.yandex.ru/i/38RbVC0spb_jQQ"} 
Нажмите кнопку Запустить тест. Этим самым мы загрузим файл в хранилище и создадим задачу в БД. Если всё сделано правильно, то вы увидите такой результат:
  {
    "task_id": "133e05c2-1b98-41cc-9aab-b816d71af343"
  } 
image
Воспользуемся полученным идентификатором задачи task_id для получения статуса из базы данных. Для этого внесите в вводные данные JSON следующие изменения:
{"action":"get_task_status", "task_id":"<идентификатор задачи>"} 
Нажмите кнопку Запустить тест. Так как мы ещё не обрабатывали задачи в очереди, результат очевиден:
  {
    "ready": false
  } 
image
Шаг 6. Создание триггера
Теперь создайте триггер, который будет вызывать функцию обработки сообщений из очереди. После создания триггер начинает работать через пять минут. Он будет брать по одному сообщению и раз в 10 секунд отправлять в функцию:
yc serverless trigger create message-queue \
--name ffmpeg \
--queue $YMQ_QUEUE_ARN \
--queue-service-account-id $SERVICE_ACCOUNT_FFMPEG_ID \
--invoke-function-name ffmpeg-converter \
--invoke-function-service-account-id $SERVICE_ACCOUNT_FFMPEG_ID \
--batch-size 1 \
--batch-cutoff 10s 
С этого момента очередь начнёт обрабатываться. Можно проверить, готова ли задача, и, если это так, запросить по URL результат обработки из Object Storage.
Теперь у нас есть функция, которая выполняет функцию API, через которую мы можем ставить задачи в очередь на исполнение. Триггер раз в 10 секунд берет по одному сообщению в очереди и передает функции обработчику. Функция-обработчик формирует результат и обновляет данные в базе данных. При этом мы получаем сконвертированные GIF-файлы из видео.
Протестируйте систему, используя полученный ранее идентификатор задачи task_id для получения статуса из базы данных. Для этого внесите изменения в вводные данные JSON:
{"action":"get_task_status", "task_id":"133e05c2-1b98-41cc-9aab-b816d71af343"} 
Нажмите кнопку Запустить тест. Если задача уже успела обработаться, то вы получите URL.
image
Удаление триггера
По завершении работы не забудьте удалить созданный триггер ffmpeg, иначе он будет продолжать работать:
yc serverless trigger delete ffmpeg 
Не забудьте также удалить или остановить все созданные вами ресурсы.
Task:
В рамках этого курса вы изучили несколько ключевых сервисов Yandex Cloud, относящихся к группе Serverless. Давайте объединим их для решения ещё одной практической задачи и создадим сервис, который конвертирует длинные ссылки в короткие.
# Разработка Сокращатель ссылок.
Decision:
Шаг 1. Сервисный аккаунт
Создание аккаунта
Создайте сервисный аккаунт с именем serverless-shortener:
export SERVICE_ACCOUNT_SHORTENER_ID=$(yc iam service-account create --name serverless-shortener \
--description "service account for serverless" \
--format json | jq -r .) 
Проверьте текущий список сервисных аккаунтов:
yc iam service-account list 
После проверки запишите идентификатор созданного сервисного аккаунта в переменную SERVICE_ACCOUNT_SHORTENER_ID:
echo "export SERVICE_ACCOUNT_SHORTENER_ID=<идентификатор сервисного аккаунта>" >> ~/.bashrc && . ~/.bashrc
echo $SERVICE_ACCOUNT_SHORTENER_ID 
Назначение ролей
Добавьте созданному сервисному аккаунту роли editor, storage.viewer и ydb.admin:
echo "export FOLDER_ID=$(yc config get folder-id)" >> ~/.bashrc && . ~/.bashrc
echo $FOLDER_ID
echo "export OAUTH_TOKEN=$(yc config get token)" >> ~/.bashrc && . ~/.bashrc
echo $OAUTH_TOKEN
echo "export CLOUD_ID=$(yc config get cloud-id)" >> ~/.bashrc && . ~/.bashrc
echo $CLOUD_ID
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_SHORTENER_ID \
--role editor
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_SHORTENER_ID \
--role ydb.admin
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_SHORTENER_ID \
--role storage.viewer 
Шаг 2. Создание бакета в Object Storage
Сделаем для нашего сервиса веб-интерфейс. Поскольку это будет статическая веб-страница, разместим её в объектном хранилище. 
В консоли управления в вашем рабочем каталоге выберите сервис Object Storage. Нажмите кнопку Создать бакет.
На странице создания бакета:
Введите имя бакета. В нашем примере это будет storage-for-serverless-shortener.
Ограничьте максимальный размер бакета (например 1 ГБ).
Выберите тип доступа Публичный во всех случаях.
Выберите класс хранилища Стандартное.
Нажмите кнопку Создать бакет для завершения операции.
image
Создайте файл index.html и загрузите его в созданный бакет — это будет стартовая страничка для нашего сокращателя:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Сокращатель URL</title>
  <!-- предостережет от лишнего GET запроса на адрес /favicon.ico -->
  <link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>
<h1>Добро пожаловать</h1>
<form action="javascript:shorten()">
  <label for="url">Введите ссылку:</label><br>
  <input id="url" name="url" type="text"><br>
  <input type="submit" value="Сократить">
</form>
<p id="shortened"></p>
</body>
<script>
  function shorten() {
    const link = document.getElementById("url").value
    fetch("/shorten", {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: link
    })
      .then(response => response.json())
      .then(data => {
        const url = data.url
        document.getElementById("shortened").innerHTML = `<a href=${url}>${url}</a>`
      })
      .catch(error => {
        document.getElementById("shortened").innerHTML = `<p>Произошла ошибка ${error}, попробуйте еще раз</p>`
      })
  }
</script>
</html> 
Шаг 3. Создание базы данных
Создадим бессерверную базу данных YDB с именем for-serverless-shortener. Чтобы не переключаться из терминала, снова воспользуемся CLI. Обязательно укажите флаг --serverless для выбора типа создаваемой базы данных.
yc ydb database create for-serverless-shortener \
--serverless \
--folder-id $FOLDER_ID
yc ydb database list 
Далее выполните команду: 
yc ydb database get --name for-serverless-shortener 
В выводе вы увидите значение endpoint. Оно состоит из двух частей: собственно эндпоинта (обычно это ydb.serverless.yandexcloud.net:2135) и пути базы данных (он указывается после ключевого слова database и начинается с символа /, например /ru-central1/...). 
Сохраним адрес эндпоинта в переменную YDB_ENDPOINT, а путь базы данных — в переменную YDB_DATABASE. Они пригодятся нам для подключения функции.
echo "export YDB_ENDPOINT=<YDB_ENDPOINT>" >> ~/.bashrc && . ~/.bashrc
echo $YDB_ENDPOINT
echo "export YDB_DATABASE=<YDB_DATABASE>" >> ~/.bashrc && . ~/.bashrc
echo $YDB_DATABASE 
Для дальнейшей работы нам понадобится утилита интерфейса командной строки YDB CLI:
curl https://storage.yandexcloud.net/yandexcloud-ydb/install.sh | bash 
С помощью CLI создадим авторизованный ключ сервисного аккаунта serverless-shortener:
yc iam key create \
--service-account-name serverless-shortener \
--output serverless-shortener.sa 
Сохраним путь к файлу с ключом в переменную окружения:
echo "export SA_KEY_FILE=$PWD/serverless-shortener.sa" >> ~/.bashrc && . ~/.bashrc
echo $SA_KEY_FILE 
Проверим работоспособность с помощью команды:
ydb \
--endpoint $YDB_ENDPOINT \
--database $YDB_DATABASE \
--sa-key-file $SA_KEY_FILE \
discovery whoami \
--groups 
Сохраним в файл links.yql SQL-скрипт для создания таблицы:
CREATE TABLE links
(
  id Utf8,
  link Utf8,
  PRIMARY KEY (id)
);
COMMIT; 
Запустите создание таблицы, а затем проверьте результат:
ydb \
--endpoint $YDB_ENDPOINT \
--database $YDB_DATABASE \
--sa-key-file $SA_KEY_FILE \
scripting yql --file links.yql
ydb \
--endpoint $YDB_ENDPOINT \
--database $YDB_DATABASE \
--sa-key-file $SA_KEY_FILE \
scheme describe links 
Шаг 4. Создание функции
В рабочем каталоге создайте файл index.py:
import ydb
import urllib.parse
import hashlib
import base64
import json
import os
def decode(event, body):
  # тело запроса может быть закодировано
  is_base64_encoded = event.get('isBase64Encoded')
  if is_base64_encoded:
    body = str(base64.b64decode(body), 'utf-8')
  return body
def response(statusCode, headers, isBase64Encoded, body):
  return {
    'statusCode': statusCode,
    'headers': headers,
    'isBase64Encoded': isBase64Encoded,
    'body': body,
  }
def get_config():
  endpoint = os.getenv("endpoint")
  database = os.getenv("database")
  if endpoint is None or database is None:
    raise AssertionError("Нужно указать обе переменные окружения")
  credentials = ydb.construct_credentials_from_environ()
  return ydb.DriverConfig(endpoint, database, credentials=credentials)
def execute(config, query, params):
  with ydb.Driver(config) as driver:
    try:
      driver.wait(timeout=5)
    except TimeoutError:
      print("Connect failed to YDB")
      print("Last reported errors by discovery:")
      print(driver.discovery_debug_details())
      return None
    session = driver.table_client.session().create()
    prepared_query = session.prepare(query)
    return session.transaction(ydb.SerializableReadWrite()).execute(
      prepared_query,
      params,
      commit_tx=True
    )
def insert_link(id, link):
  config = get_config()
  query = """
    DECLARE $id AS Utf8;
    DECLARE $link AS Utf8;
    UPSERT INTO links (id, link) VALUES ($id, $link);
    """
  params = {'$id': id, '$link': link}
  execute(config, query, params)
def find_link(id):
  print(id)
  config = get_config()
  query = """
    DECLARE $id AS Utf8;
    SELECT link FROM links where id=$id;
    """
  params = {'$id': id}
  result_set = execute(config, query, params)
  if not result_set or not result_set[0].rows:
    return None
  return result_set[0].rows[0].link
def shorten(event):
  body = event.get('body')
  if body:
    body = decode(event, body)
    original_host = event.get('headers').get('Origin')
    link_id = hashlib.sha256(body.encode('utf8')).hexdigest()[:6]
    # в ссылке могут быть закодированные символы, например, %. это помешает работе api-gateway при редиректе,
    # поэтому следует избавиться от них вызовом urllib.parse.unquote
    insert_link(link_id, urllib.parse.unquote(body))
    return response(200, {'Content-Type': 'application/json'}, False, json.dumps({'url': f'{original_host}/r/{link_id}'}))
  return response(400, {}, False, 'В теле запроса отсутствует параметр url')
def redirect(event):
  link_id = event.get('pathParams').get('id')
  redirect_to = find_link(link_id)
  if redirect_to:
    return response(302, {'Location': redirect_to}, False, '')
  return response(404, {}, False, 'Данной ссылки не существует')
# эти проверки нужны, поскольку функция у нас одна
# в идеале сделать по функции на каждый путь в api-gw
def get_result(url, event):
  if url == "/shorten":
    return shorten(event)
  if url.startswith("/r/"):
    return redirect(event)
  return response(404, {}, False, 'Данного пути не существует')
def handler(event, context):
  url = event.get('url')
  if url:
    # из API-gateway url может прийти со знаком вопроса на конце
    if url[-1] == '?':
      url = url[:-1]
    return get_result(url, event)
  return response(404, {}, False, 'Эту функцию следует вызывать при помощи api-gateway') 
Создайте файл requirements.txt со следующим содержимым:
ydb==2.13.3 
Находясь в директории с исходными файлами, упакуйте их в zip-архив:
zip src.zip index.py requirements.txt 
Создадим нашу функцию for-serverless-shortener, задав все необходимые переменные. В переменные окружения функции необходимо добавить:
endpoint — нужно указать протокол grpcs:// и добавить значение Эндпоинт из секции YDB эндпоинт, обычно получается grpcs://ydb.serverless.yandexcloud.net:2135;
database — это значение поля База данных из секции YDB эндпоинт (начинается с /ru-central1/....);
USE_METADATA_CREDENTIALS — выставите значение переменной в 1.
Также сразу сделаем функцию публичной.
yc serverless function create \
--name for-serverless-shortener \
--description "function for serverless-shortener"
yc serverless function version create \
--function-name for-serverless-shortener \
--memory=256m \
--execution-timeout=5s \
--runtime=python39 \
--entrypoint=index.handler \
--service-account-id $SERVICE_ACCOUNT_SHORTENER_ID \
--environment USE_METADATA_CREDENTIALS=1 \
--environment endpoint=grpcs://ydb.serverless.yandexcloud.net:2135 \
--environment database=$YDB_DATABASE \
--source-path src.zip
yc serverless function allow-unauthenticated-invoke for-serverless-shortener 
Шаг 5. Конфигурирование Yandex API Gateway
Создадим спецификацию for-serverless-shortener.yml со следующим содержанием:
openapi: 3.0.0
info:
title: for-serverless-shortener
version: 1.0.0
paths:
/:
  get:
   x-yc-apigateway-integration:
    type: object_storage
    bucket:       <bucket_name>    # <-- имя бакета
    object:       <html_file>     # <-- имя html-файла
    presigned_redirect: false
    service_account:  <service_account_id> # <-- идентификатор сервисного аккаунта
   operationId: static
/shorten:
  post:
   x-yc-apigateway-integration:
    type: cloud_functions
    function_id: <function_id>       # <-- идентификатор функции
   operationId: shorten
/r/{id}:
  get:
   x-yc-apigateway-integration:
    type: cloud_functions
    function_id: <function_id>       # <-- идентификатор функции
   operationId: redirect
   parameters:
    - description: id of the url
     explode: false
     in: path
     name: id
     required: true
     schema:
      type: string
     style: simple 
Не забудьте подставить в спецификацию актуальные для вас значения переменных.
Используем спецификацию для инициализации:
yc serverless api-gateway create \
--name for-serverless-shortener \
--spec=for-serverless-shortener.yml \
--description "for serverless shortener" 
В результате успешного создания API-шлюза получим значение параметра domain:
yc serverless api-gateway list
yc serverless api-gateway get --name for-serverless-shortener 
Чтобы проверить работоспособность API-шлюза и созданного приложения целиком, скопируйте служебный домен (вида https://<идентификатор API Gateway>.apigw.yandexcloud.net/) и вставьте адрес в браузер.
Добавляйте адреса сайтов в форму, они будут сохранятся в базу данных. А вам будет доступна ссылка, за которой будет скрываться оригинальный адрес. Ваше приложение полностью работоспособно. Теперь вы умеете использовать serverless-стек технологий Yandex Cloud.
image
Итак, вы создали приложение с использованием Cloud Functions, API Gateway, Object Storage и YDB.
Task:
В этом уроке вы научитесь работать с сервисными аккаунтами и назначать для них роли и права доступа к объектам. В качестве объекта будет выступать созданный в Yandex Key Management Service (KMS) ключ шифрования. Подробнее об этом сервисе вы узнаете на одном из следующих занятий. А сейчас предположим, что перед вами стоит задача использовать для ротации ключей сервисный аккаунт.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Чтобы решить эту задачу, понадобится выполнить следующие шаги:
Создать сервисный аккаунт.
Получить права на управление этим сервисным аккаунтом.
Создать и привязать к сервисному аккаунту авторизованные ключи.
Создать в сервисе KMS ключ шифрования.
Назначить сервисному аккаунту роль kms.admin для управления этим ключом.
И, наконец, ротировать ключ, то есть создать его новую версию, с помощью сервисного аккаунта.
Стоит заметить, что через консоль управления сервисному аккаунту можно назначить роль только на каталог, в котором он был создан. Роли на все остальные ресурсы назначаются с помощью CLI или API. Поэтому для выполнения этой практической работы вам понадобится вспомнить то, чему вы научились в курсе «DevOps и автоматизация», и поработать с интерфейсом командной строки CLI Yandex Cloud.
Ну что же, поехали!
Шаг 1
Для начала создайте сервисный аккаунт. Если на вашем компьютере ещё не установлена утилита yc (CLI Yandex Cloud), то сделайте это, руководствуясь инструкцией в документации. 
Откройте терминал и выполните команду:
yc iam service-account create --name <имя_сервисного_аккаунта> 
Требования к формату имени сервисного аккаунта:
длина — от 3 до 63 символов;
имя может содержать только строчные буквы латинского алфавита, цифры и дефисы;
первый символ должен быть буквой;
последний символ не должен быть дефисом.
Если вы хотите добавить создаваемому сервисному аккаунту описание, то в этом случае команда будет выглядеть, например, так:
yc iam service-account create --name security-labs \
--description "Service account for Security course labs" 
Шаг 2
Сервисный аккаунт является ресурсом, и для работы с ним нужна соответствующая роль. Добавьте вашему аккаунту роль editor на созданный сервисный аккаунт.
Для начала убедитесь, что у вашего аккаунта есть роль admin на облако. Конечно, если вы являетесь владельцем облака, то ваша роль — resource-manager.clouds.owner — включает разрешения всех остальных ролей и позволяет выполнять любые операции с облаком и ресурсами в нём.
Проверить, какие роли в облаке у вас есть, можно с помощью команды:
yc iam role list 
Чтобы назначить вашему аккаунту роль на сервисный аккаунт, нужно сначала узнать их идентификаторы.
Получите идентификатор своего аккаунта с помощью команды:
yc iam user-account get <ваш_логин> 
Параметр <ваш_логин> — это логин или адрес электронной почты, под которым вы авторизуетесь в облаке.
Получите идентификатор сервисного аккаунта, запустив команду …
yc iam service-account list 
или:
yc iam service-account get <имя_сервисного_аккаунта> 
Теперь предоставьте вашему аккаунту права на управление созданным сервисным аккаунтом. Для этого нужно выполнить команду:
yc iam service-account add-access-binding <id_сервисного_аккаунта> \
--role editor \
--subject userAccount:<id_пользовательского_аккаунта> 
Шаг 3
Теперь нужно настроить аутентификацию под сервисным аккаунтом на вашем компьютере.
Создайте авторизованные ключи, которые нужны, чтобы получать IAM-токен для сервисного аккаунта, и сохраните их в JSON-файле (например key.json).
Воспользуйтесь для этого командой:
yc iam key create \
--service-account-name <имя_сервисного_аккаунта> \
--output key.json 
Создайте профиль CLI Yandex Cloud, который будет использоваться сервисным аккаунтом. Придумайте имя этого профиля (например key-rotator) и выполните команду:
yc config profile create <имя_профиля_сервисного_аккаунта> 
Привяжите к этому профилю созданные авторизованные ключи и укажите идентификатор вашего рабочего каталога в качестве каталога по умолчанию:
yc config set service-account-key key.json
yc config set folder-id <идентификатор_рабочего_каталога> 
Чтобы убедиться, что всё сделано правильно, выведите информацию о параметрах профиля:
yc config list 
Шаг 4
Создайте ключ шифрования, ротацией которого вы будете управлять с помощью сервисного аккаунта. Для этого в консоли управления облаком перейдите в дашборд вашего рабочего каталога, нажмите кнопку Создать ресурс (вверху справа) и выберите Ключ шифрования.
В открывшемся окне задайте Имя ключа и нажмите кнопку Создать.
image
Выбрав созданный ключ на открывшейся странице Ключи, вы перейдёте на страницу Обзор с детальной информацией о нём.
Скопируйте идентификатор ключа, он понадобится вам на следующем шаге.
image
Шаг 5
Назначьте сервисному аккаунту роль kms.admin для управления созданным ключом шифрования. Перед этим нужно вернуться в профиль вашего аккаунта:
yc config profile list
yc config profile activate <имя_основного_профиля> 
Выполните команду:
yc kms symmetric-key add-access-binding \
--id <id_ключа_шифрования> \
--role kms.admin \
--subject serviceAccount:<id_сервисного_аккаунта> 
Теперь вы можете управлять этим ключом с помощью сервисного аккаунта.
Шаг 6
Переключитесь обратно в профиль сервисного аккаунта:
yc config profile activate <имя_профиля_сервисного_аккаунта> 
Ротируйте ключ шифрования:
yc kms symmetric-key rotate --id <id_ключа_шифрования> 
После выполнения команды вернитесь в консоль управления на страницу Обзор созданного ключа шифрования и перейдите на вкладку Операции.
image
Вы увидите, что операция по ротации ключа выполнена вашим сервисным аккаунтом. Значит всё получилось, и задача решена!
Task:
Защита данных, передаваемых между вашей локальной инфраструктурой и облаком, — важный элемент информационной безопасности. А удалённая работа, которая получила распространение в период пандемии коронавируса и сейчас закрепилась в практиках многих компаний, сделала эту задачу ещё более актуальной.
Чтобы защитить передаваемую информацию, используют VPN (Virtual Private Network) — технологию, позволяющую развернуть защищённое сетевое соединение «поверх» незащищённой сети (чаще всего это интернет). VPN-соединение представляет собой канал передачи данных между двумя узлами. Этот канал обычно называют VPN-туннелем. Если за одним из узлов находится целая сеть, то его называют VPN-шлюзом.
Механизм работы VPN:
- Перед созданием туннеля узлы идентифицируют друг друга, чтобы удостовериться, что шифрованные данные будут отправлены на нужный узел.
- На обоих узлах нужно заранее определить, какие протоколы будут использоваться для шифрования и обеспечения целостности данных.
- Узлы сверяют настройки, чтобы договориться об используемых алгоритмах. Если настройки разные, туннель не создаётся.
- Если сверка прошла успешно, то создаётся ключ, который используется для симметричного шифрования.
Этот механизм регламентируют несколько стандартов. Один из самых популярных — IPSec (Internet Protocol Security).
На этом уроке вы научитесь настраивать IPSec VPN-туннель между двумя VPN-шлюзами с помощью демона strongSwan. Один шлюз вы настроите на виртуальной машине в Yandex Cloud, второй — на своей локальной машине или виртуальной машине в другой облачной сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Шаг 1. Создание ресурсов
Для практической работы вам понадобится сеть и подсеть в Yandex Cloud, а также созданная в этой подсети тестовая ВМ без публичного IP-адреса. Создайте эти ресурсы, если у вас их нет.
Теперь создадим IPSec-инстанс — ВМ, которая будет служить шлюзом для IPSec-туннеля. Чтобы это сделать:
Откройте ваш каталог, нажмите кнопку Создать ресурс и выберите пункт Виртуальная машина.
В поле Имя задайте имя ВМ, например ipsec-instance.
Выберите зону доступности, где находится подсеть, к которой будет подключён IPSec-инстанс, и тестовая ВМ.
В разделе Выбор образа/загрузочного диска перейдите в блок Cloud Marketplace и выберите образ IPSec-инстанс.
В блоке Сетевые настройки выберите нужную сеть, подсеть и назначьте ВМ публичный IP-адрес из списка или автоматически.
Важно использовать только статические публичные IP-адреса из списка или сделать IP-адрес ВМ статическим после её создания. Динамический IP-адрес может измениться после перезагрузки ВМ, и туннель перестанет работать.
В блоке Доступ укажите логин и SSH-ключ для доступа к ВМ.
Нажмите кнопку Создать ВМ.
Виртуальная машина готова.
Шаг 2. Настраиваем IPSec
Теперь настроим шлюз с публичным IP-адресом, который будет устанавливать IPSec-соединение с удалённым шлюзом (вашей локальной машиной или ВМ в другой облачной сети).
Вы можете создать в своём каталоге ещё одну облачную сеть с подсетью, создать в ней IPSec-инстанс из образа и использовать его в качестве удалённого шлюза. Либо можно использовать в качестве шлюза машину в вашей локальной сети. Вам понадобится публичный IP-адрес удалённого шлюза и CIDR подсети.
Допустим, публичный IP-адрес вашего шлюза — 130.193.32.25, а за ним находится подсеть c префиксом подсети CIDR 10.128.0.0/24. Шлюз будет устанавливать IPSec-соединение с удалённым шлюзом с IP-адресом, например, 1.1.1.1, за которым находится подсеть с префиксом подсети CIDR 192.168.0.0/24.
Подключитесь к ВМ IPSec-инстанс по SSH:
ssh <имя пользователя>@130.193.32.25
Откройте конфигурацию IPSec:
sudo nano /etc/ipsec.conf
В разделе config setup файла конфигурации задайте следующие параметры:
config setup
  charondebug="all"
  uniqueids=yes
  strictcrlpolicy=no
Добавьте новый раздел с описанием тестового соединения, например conn cloud-to-hq.
Задайте параметры тестового соединения:
leftid — публичный IP-адрес IPSec-инстанса.
leftsubnet — CIDR подсети, к которой подключён IPSec-инстанс.
right — публичный IP-адрес шлюза на другом конце VPN-туннеля.
rightsubnet — CIDR подсети, к которой подключён VPN-шлюз на другом конце VPN-туннеля.
Параметры ike и esp — это алгоритмы шифрования, которые поддерживаются на удалённом шлюзе. Перечень поддерживаемых алгоритмов можно посмотреть на сайте strongSwan: IKEv1 и IKEv2.
Укажите остальные настройки, консультируясь с документацией strongSwan и учитывая настройки удалённого шлюза.
У вас должна получиться примерно такая конфигурация:
conn cloud-to-hq
  authby=secret
  left=%defaultroute
  leftid=130.193.32.25
  leftsubnet=10.128.0.0/24
  right=1.1.1.1
  rightsubnet=192.168.0.0/24
  ike=aes256-sha2_256-modp1024!
  esp=aes256-sha2_256!
  keyingtries=0
  ikelifetime=1h
  lifetime=8h
  dpddelay=30
  dpdtimeout=120
  dpdaction=restart
  auto=start
Сохраните изменения и закройте файл.
Откройте файл /etc/ipsec.secrets и укажите в нём пароль для установки соединения:
130.193.32.25 1.1.1.1 : PSK "<пароль>"
Перезапустите strongSwan:
sudo systemctl restart strongswan-starter
Шаг 3. Настраиваем статическую маршрутизацию
Теперь нужно настроить маршрутизацию между IPSec-инстансом и тестовой ВМ без публичного IP-адреса. Для этого создадим таблицу маршрутизации и добавим в неё статические маршруты.
Откройте сервис Virtual Private Cloud в каталоге, где требуется создать статический маршрут.
Выберите раздел Таблицы маршрутизации в панели слева и нажмите кнопку Создать таблицу маршрутизации.
Задайте имя таблицы маршрутизации, выберите сеть, в которой требуется её создать, и нажмите кнопку Добавить маршрут.
В открывшемся окне введите префикс подсети назначения на удалённой стороне, в примере это 192.168.0.0/24.
В поле Next hop укажите внутренний IP-адрес IPSec-шлюза и нажмите кнопку Добавить.
Нажмите кнопку Создать таблицу маршрутизации.
Чтобы использовать статические маршруты, нужно привязать таблицу маршрутизации к подсети. Для этого в разделе Подсети, в строке нужной подсети, нажмите кнопку … и в открывшемся меню выберите пункт Привязать таблицу маршрутизации.
В открывшемся окне выберите созданную таблицу и нажмите кнопку Привязать. Созданный маршрут можно применять и к другим подсетям этой сети.
Шаг 4. Настраиваем IPSec на другом шлюзе
Для работы VPN-туннеля нужно настроить второй шлюз.
Настройте strongSwan аналогично первому IPSec-шлюзу, но с зеркальными настройками IP-адресов и подсетей в файле /etc/ipsec.conf. Должна получиться такая конфигурация:
conn hq-to-cloud
  authby=secret
  left=%defaultroute
  leftid=1.1.1.1
  leftsubnet=192.168.0.0/24
  right=130.193.32.25
  rightsubnet=10.128.0.0/24
  ike=aes256-sha2_256-modp1024!
  esp=aes256-sha2_256!
  keyingtries=0
  ikelifetime=1h
  lifetime=8h
  dpddelay=30
  dpdtimeout=120
  dpdaction=restart
  auto=start
Укажите пароль для соединения в файле /etc/ipsec.secrets, указав IP-адреса шлюзов в обратном порядке:
1.1.1.1 130.193.32.25 : PSK "<пароль>"
Перезапустите strongSwan:
sudo systemctl restart strongswan-starter
Шаг 5. Проверяем, что всё работает
Чтобы убедиться, что туннель между шлюзами установлен, выполните на любом из шлюзов команду:
sudo ipsec status
Если всё в порядке, то у вас должно появиться примерно такое сообщение:
Security Associations (1 up, 0 connecting):
hq-to-cloud[3]: ESTABLISHED 29 minutes ago, 10.128.0.26[130.193.33.12]...192.168.0.23[1.1.1.1]
hq-to-cloud{3}: INSTALLED, TUNNEL, reqid 3, ESP in UDP SPIs: c7fa371d_i ce8b91ad_o
hq-to-cloud{3}: 10.128.0.0/24 === 192.168.0.0/24
Статус ESTABLISHED означает, что туннель между шлюзами создан.
Сведения об установке и работе соединения находятся в логах strongSwan. Просмотреть логи можно с помощью команды:
sudo journalctl -u strongswan-starter
Проверить статус демона strongSwan можно командой:
systemctl status strongswan-starter
Осталось проверить связность соединения. Для этого создайте ещё одну тестовую виртуальную машину за вторым шлюзом, а затем пропингуйте одну тестовую машину с другой.
Decision:
tuser@kvmubuntu:~$ ssh <имя пользователя>@130.193.32.25
tuser@kvmubuntu:~$ sudo vim /etc/ipsec.conf
tuser@kvmubuntu:~$ sudo cat /etc/ipsec.conf
...
config setup
  charondebug="all"
  uniqueids=yes
  strictcrlpolicy=no
...
conn cloud-to-hq
  authby=secret
  left=%defaultroute
  leftid=130.193.32.25
  leftsubnet=10.128.0.0/24
  right=1.1.1.1
  rightsubnet=192.168.0.0/24
  ike=aes256-sha2_256-modp1024!
  esp=aes256-sha2_256!
  keyingtries=0
  ikelifetime=1h
  lifetime=8h
  dpddelay=30
  dpdtimeout=120
  dpdaction=restart
  auto=start
tuser@kvmubuntu:~$ sudo vim /etc/ipsec.secrets
tuser@kvmubuntu:~$ sudo cat /etc/ipsec.secrets
130.193.32.25 1.1.1.1 : PSK "<пароль>"
tuser@kvmubuntu:~$ sudo systemctl restart strongswan-starter
tuser@kvmubuntu:~$ exit
tuser@kvmubuntu:~$ ssh <имя пользователя>@130.193.32.26
tuser@kvmubuntu:~$ sudo vim /etc/ipsec.conf
tuser@kvmubuntu:~$ sudo cat /etc/ipsec.conf
...
config setup
  charondebug="all"
  uniqueids=yes
  strictcrlpolicy=no
...
conn hq-to-cloud
  authby=secret
  left=%defaultroute
  leftid=1.1.1.1
  leftsubnet=192.168.0.0/24
  right=130.193.32.25
  rightsubnet=10.128.0.0/24
  ike=aes256-sha2_256-modp1024!
  esp=aes256-sha2_256!
  keyingtries=0
  ikelifetime=1h
  lifetime=8h
  dpddelay=30
  dpdtimeout=120
  dpdaction=restart
  auto=start
tuser@kvmubuntu:~$ sudo vim /etc/ipsec.secrets
tuser@kvmubuntu:~$ sudo cat /etc/ipsec.secrets
1.1.1.1 130.193.32.25 : PSK "<пароль>"
tuser@kvmubuntu:~$ sudo systemctl restart strongswan-starter
tuser@kvmubuntu:~$ ssh <имя пользователя>@130.193.32.25
tuser@kvmubuntu:~$ sudo ipsec status
tuser@kvmubuntu:~$ sudo journalctl -u strongswan-starter
tuser@kvmubuntu:~$ systemctl status strongswan-starter
Task:
В этой практической работе мы зарегистрируем домен, привяжем его к бакету в объектном хранилище и настроим для этого домена автоматический выпуск сертификата с помощью Certificate Manager.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Шаг 1
Если у вас нет своего домена, зарегистрируйте временный домен, например, на сайте freenom.com:
Проверьте на сайте доступность имени, которое вы придумали для своего домена.
Введите имя вместе с доменом верхнего уровня, например testpracticum2022.ml, иначе при попытке зарезервировать домен сервис будет сообщать, что домен занят.
Если это имя доступно, добавьте домен в корзину и укажите свой email для подтверждения.
Проверьте почту и подтвердите регистрацию домена.
Обновите страницу с заказом.
После подтверждения регистрации домена зайдите в объектное хранилище (Object Storage) и создайте новый публичный бакет. Его название должно совпадать с полным названием домена.
Переключите доступ на чтение объектов в Публичный. Загрузите в бакет файлы статического сайта (вы можете воспользоваться файлами из практической работы курса «Хранение и анализ данных».
Выберите на панели управления раздел Веб-сайт, переключите бакет в режим Хостинг и нажмите Сохранить.
Шаг 2
Настроить защищённый доступ к бакету можно двумя способами: загрузить сертификат прямо в бакет или с помощью Certificate Manager. Воспользуемся вторым способом.
В консоли управления перейдите в сервис Certificate Manager. Для выпуска сертификата с помощью этого сервиса подтвердите владение доменом: в разделе Сертификаты нажмите кнопку Добавить сертификат и выберите Сертификат Let’s Encrypt.
В открывшемся окне задайте имя создаваемого сертификата и заполните поле с именем вашего домена. Нажмите кнопку Создать.
Сервис автоматически направит запрос на создание сертификата, а домен перейдёт в статус проверки.
Для выпуска сертификата необходимо подтвердить статус владения доменом. Откройте страницу с деталями запроса на сертификат:
На этой странице для нас важны два поля: имя DNS-записи и её значение. Если вы создавали домен на freenom.com, то перейдите в личный кабинет на этом сайте, выберите раздел Services → My Domains и нажмите кнопку Manage Domains:
Выберите Manage Freenom DNS:
В открывшемся редакторе добавьте TXT-запись для подтверждения владения доменом. В качестве названия записи задайте _acme-challenge без полного названия домена. В качестве значения TXT-записи — значение со страницы проверки прав на домен в консоли управления Yandex Cloud.
Аналогично внесите значение CNAME-записи со страницы проверки прав на домен в консоли управления Yandex Cloud.
Добавьте также запись CNAME для привязки поддомена WWW к вашему бакету:
В поле Target укажите полное имя бакета, включая .website.yandexcloud.net. Сохраните сделанные изменения.
Если вы используете собственный домен, задайте параметры DNS в настройках вашего DNS-сервера. Для применения настроек DNS потребуется некоторое время — обычно до 15 минут.
После окончания проверки домена Certificate Manager автоматически выпустит сертификат.
Шаг 3
Теперь настроим доступ к сайту, то есть к созданному бакету, по протоколу HTTPS с помощью сертификата. Для этого перейдите в раздел HTTPS и нажмите кнопку Настроить.
В поле Источник выберите Certificate Manager, в поле Сертификат — ранее выпущенный сертификат. Нажмите кнопку Сохранить.
Теперь ваш сайт доступен по протоколу HTTPS. Чтобы проверить это, откройте его в браузере. В адресной строке браузера должен отображаться значок защищённого соединения.
Task:
На прошлом уроке вы познакомились с возможностями сервиса управления ключами шифрования KMS. В этой практической работе вы научитесь создавать ключи шифрования и управлять ими, а также использовать эти ключи для шифрования и расшифрования данных.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Шаг 1
Перейдите в панель управления Yandex Cloud, нажмите кнопку Создать ресурс и выберите из выпадающего списка пункт Ключ шифрования.
Задайте для создаваемого ключа имя (например yc-lab-key1), заполните поле Описание (это необязательно) и выберите алгоритм шифрования. Предположим, что ключ нужно ротировать каждый день. Для этого в поле Период ротации, дни выберите вариант Своё значение и введите число 1 в поле справа.
Нажмите кнопку Создать. Когда операция создания ключа завершится, новый ключ появится в списке.
Нажав на строку с ключом, вы перейдёте на страницу детальной информации. На ней приведены все параметры ключа, а также список его версий. Обратите внимание, что ID (идентификатор) ключа и ID конкретной версии ключа отличаются. Важно их не путать.
Шаг 2
Давайте используем созданный ключ для шифрования и расшифрования данных. Создайте у себя на диске файл (например, текстовый файл с именем plain.txt). Добавьте в него любой текст и сохраните содержимое. Напомним, что размер файла не должен превышать 32 килобайта.
Запустите утилиту командной строки (bash или cmd) и перейдите в каталог с файлом plain.txt. Зашифруйте этот файл с помощью утилиты yc, а результат операции шифрования выведите в файл encrypted.txt. Для этого выполните команду:
yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted.txt
После выполнения команды будет создан файл encrypted.txt, который содержит зашифрованный текст. Утилита yc также выведет информацию о том, каким ключом и какой его версией файл был зашифрован.
Шаг 3
Теперь расшифруйте этот файл, а результат операции выведите в файл decrypted.txt. Для этого выполните команду:
yc kms symmetric-crypto decrypt --id <ID ключа> --ciphertext-file encrypted.txt --plaintext-file decrypted.txt
В результате выполнения команды будет создан файл decrypted.txt с идентичным исходному файлу (plain.txt) содержимым.
Если расшифровать файл не удалось, утилита выдаст сообщение об ошибке.
Шаг 4
Создайте новую версию ключа. Для этого перейдите на страницу детальной информации о ключе и нажмите кнопку Ротировать. Новая версия ключа появится в списке версий и станет основной (Primary). Обратите внимание, что идентификаторы версий отличаются друг от друга.
Шаг 5
Запланируйте удаление первой версии ключа. Для этого в списке версий нажмите на значок … в строке с этой версией, а затем выберите пункт Запланировать удаление.
В появившемся окне установите время, по истечении которого ключ будет удалён, и нажмите Запланировать. Версия ключа не может быть удалена моментально, минимальный период времени для её удаления составляет один день.
После этого в списке версий удаляемый ключ будет помечен как запланированный на удаление (Scheduled For Destruction). Теперь этой версией ключа невозможно расшифровать файлы, которые были зашифрованы с её помощью.
Провести ротацию ключа можно и из командной строки. Для этого используется команда:
yc kms symmetric-key rotate <ID ключа>
Шаг 6
Зашифруйте исходный файл plain.txt с помощью новой версии ключа. Результат запишите в файл encrypted_with_new_key.txt.
yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted_with_new_key.txt
Теперь у вас есть два файла:
- encrypted.txt, зашифрованный версией ключа, которая помечена на удаление;
- encrypted_with_new_version.txt, зашифрованный новой версией ключа.
Попробуйте расшифровать данные из обоих файлов. Вы увидите, что расшифровать первый файл не получилось, а файл, который зашифрован второй версией ключа, расшифрован.
Запланированное удаление первой версии ключа можно отменить. Это позволит расшифровать данные из первого файла.
В строке версии ключа, которая запланирована на удаление, нажмите значок …, а затем кнопку кнопку Отменить удаление. Эта версия снова получит статус активной. Проверьте, что она работает, расшифровав файл encrypted.txt.
Decision:
tuser@kvmubuntu:~$ vim plain.txt
tuser@kvmubuntu:~$ cat plain.txt
test text
tuser@kvmubuntu:~$ yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted.txt
tuser@kvmubuntu:~$ yc kms symmetric-crypto encrypt --id abjkh5a8k2uao3f8qi8k --plaintext-file plain.txt --ciphertext-file encrypted.txt
tuser@kvmubuntu:~$ cat encrypted.txt
abj2vjcrv89d83cef26in#���k��|�V�X�
         ����:�F���*Y��l�(���у��֜�l�S
tuser@kvmubuntu:~$ yc kms symmetric-crypto decrypt --id <ID ключа> --ciphertext-file encrypted.txt --plaintext-file decrypted.txt
tuser@kvmubuntu:~$ cat decrypted.txt
test text
tuser@kvmubuntu:~$ yc kms symmetric-key rotate <ID ключа>
tuser@kvmubuntu:~$ yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted_with_new_key.txt
tuser@kvmubuntu:~$ cat encrypted_with_new_key.txt
abjofv85g302tc8mjhqq��n�nߊz6�5�A�
         �)ڀi�R0���лo��s  �^
�m�C zci�b�'

2023-11-01 - 2024-05-30: Информационно-аналитический центр поддержки ГАС правосудие, Иркутск. Должность: Инженер 2 категории / Системный администратор. Дополнительная информация: Обязанности - Установка, обновление и контроль состояния программного обеспечения на объектах автоматизации, Введение эксплуатационной документации, Поддержка функционирования серверов и сервисов СУБД, Техническая поддержка пользователей. Навыки - Виртуализация, Windows, Sql. Достижения: Разработал скрипты, которые автоматизируют процессы резервного копирования базы данных перед обновлением программы ГАС правосудие.

Show

Цель:
# Восстановить проблему с регулярным увелечением пространства на диске.
# закончилось свободное оракловое табличное просранство, которое надо увеличить вручную путем добавления DATA файла.
# Активировать Windows 10 pro.
# Добавить в исключения ip 10.99.10.42. У клиента после перезагрузки сбрасываются настройки.
# Обновить лицензии DrWeb.
# Обжим витую пару.
# Замена неверного MAC адреса на ПК.
Skills:
# Администрирование базы данных.
# Администрирование локальных, виртуальных и облачных серверов.
Task:
Ошибка после ввода пароля в программе у всех пользователей:
"Не отвечает программа"
ВНЕШНЯЯБАЗА2.exe не работает
На одном из объектов автоматизации второй раз за месяц происходит процесс переполнения внешней базы документооборота
C:\ПУТЬКБАЗЕ\ВНЕШНЯЯБАЗА1.GDB. База данных увеличивается в размерах с 1.8 Гб до 115Гб за три дня. 
После проведения инженером филиала процедуры Бэкап/Рестор внешняя база возвращается к прежнему размеру. 
Данная проблема наблюдается второй раз за месяц с периодичностью в две недели.
# Администрирование базы данных.
Decision:
- Отключить службы Firebird, Сделать backup/restore базы ВНЕШНЯЯБАЗА1.GDB, запустив скрипты 
1. backup.bat
2. restore.bat
после чего сохранится файл ВНЕШНЯЯБАЗА1.GDB в КУДАСОХРАНЯТЬИЗМЕНЕНИЯ. 
Файл заменить на файл в сервере, который занимал 120 гб. Посмотреть лог файлы:
1. retranslator.log
2. retranslator_2022-01-11_15-23-40-694.log
3. retranslator_2022-01-24_16-36-09-627.log
4. retranslator_2022-01-24_16-36-16-806.log
Decision:
$ cat backup.bat
@ echo on
SET ISC_USER=tuser
SET ISC_PASSWORD=tpassword
SET dbpath=localhost:C:\ПУТЬКБАЗЕ\ВНЕШНЯЯБАЗА1.GDB
SET fbpath=C:\ПУТЬFIREBIRD\Firebird_1_5\bin\
SET bbpath=C:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\
"%fbpath%gfix" -shut -force 5 "%dbpath%"
"%fbpath%gbak" -b -v -g -y "%bbpath%%date%.log" "%dbpath%" "%bbpath%ВНЕШНЯЯБАЗА1_%date%.gbk"
@ pause
$ cat restore.bat
@ echo on
SET ISC_USER=tuser
SET ISC_PASSWORD=tpassword
SET fbpath=C:\ПУТЬFIREBIRD\Firebird_1_5\bin\
SET bbpath=localhost:C:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\
"%fbpath%gbak.exe" -c -v -r -y "c:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\%date%_fix.log" "c:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\ВНЕШНЯЯБАЗА1_backup.gbk" "%bbpath%ВНЕШНЯЯБАЗА1_fix.GDB" 
@ pause
$ cat retranslator.log
...
INF|09.12.2022 16:27:36 Сообщения в очереди "viv.client.КОДОА.1" отсутствуют
INF|09.12.2022 16:32:39 Отправка запроса id="ИДЕНТИФИКАТОРПРОЦЕССА1", type_id=ИДЕНТИФИКАТОРТИП ...
ERR|09.12.2022 16:33:37 Out of memory.
  E.ClassName=EOutOfMemory
  Sender.ClassName=TWorkThread
...
INF|11.12.2022 22:28:27 Отправка запроса id="ИДЕНТИФИКАТОРПРОЦЕССА2", type_id=ИДЕНТИФИКАТОРТИП ...
ERR|11.12.2022 22:28:53 При отправке запроса id="ИДЕНТИФИКАТОРПРОЦЕССА2" произошла неустранимая ошибка чтения из БД, запрос отклонён. Текст ошибки:
  Unsuccessful execution caused by a system error that precludes
  successful execution of subsequent statements.
  I/O error for file "C:\ПУТЬКБАЗЕ\ВНЕШНЯЯБАЗА1.GDB".
  Error while trying to write to file.
  Недостаточно места на диске.
...
Decision:
при обработке одного запроса id="ИДЕНТИФИКАТОРПРОЦЕССА1", type_id=ИДЕНТИФИКАТОРТИП происходит нехватка памяти, что далее приводит к сбою функционирования. 
Вероятно в запросе большое вложение. 
Для дальнейшего анализа смотрим БД ВНЕШНЯЯБАЗА2.gdb, в которой покажет технические ошибки состояния базы ВНЕШНЯЯБАЗА2.gdb.
Для их устранения также сделать бэкап/ресторе БД ВНЕШНЯЯБАЗА2.gdb. 
Далее понаблюдать за ситуацией с размером БД ВНЕШНЯЯБАЗА2.GDB, чтобы он весил не больше 19 Гб.
Task:
После отключения света в здании служба OracleServiceНАЗВАНИЕСИСТЕМЫ не запускается. При подключении к БСР клиента на сервере выходит следующая ошибка:
ОШИБКА ORA-01033. ORACLE INITIALIZATION OR SHUTDOWN IN PROGRESS
Не помогает перезапуск службы ORACLE и перезагрузка сервера.
Также пробовали подключиться и настроить базу в командной строке через sql plus. После ввода команды выдает ошибку:
C:\>sqlplus
Enter user-name: СОКРАЩЕННОЕОЕИМЯПОЛЬЗОВАТЕЛЯ as ПОЛНОЕИМЯПОЛЬЗОВАТЕЛЯ
SQL> alter pluggable database all open;
alter pluggable database all open
*
ERROR at line 1:
ORA-00940: invalid ALTER command
Нужно сделать рестарт базы
# Администрирование базы данных.
Decision:
C:\>sqlplus /nolog
SQL> connect СОКРАЩЕННОЕОЕИМЯПОЛЬЗОВАТЕЛЯ/pass@НАЗВАНИЕСИСТЕМЫ as ПОЛНОЕИМЯПОЛЬЗОВАТЕЛЯ
ERROR:
ORA-01017: invalid username/password; logon denied
SQL> conn СОКРАЩЕННОЕОЕИМЯПОЛЬЗОВАТЕЛЯ/oracle@НАЗВАНИЕСИСТЕМЫ as ПОЛНОЕИМЯПОЛЬЗОВАТЕЛЯ
SQL> startup mount
ORA-01081: cannot start already-running ORACLE - shut it down first
SQL> shutdown immediate
ORA-01109: database not open
Database dismounted.
ORACLE instance shut down.
SQL> startup mount
SQL> col file format a5;
SQL> col name format a20;
SQL> col status format a10;
SQL> select file#,name,status from v$datafile;
FILE# NAME     STATUS
---------- -------------------- ----------
1 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM01.DBF
2 C:\ORACLE\ORADATA\GA ONLINE
S\UNDOTBS01.DBF
3 C:\ORACLE\ORADATA\GA ONLINE
S\CWMLITE01.DBF
4 C:\ORACLE\ORADATA\GA ONLINE
S\DRSYS01.DBF
FILE# NAME     STATUS
---------- -------------------- ----------
5 C:\ORACLE\ORADATA\GA ONLINE
S\EXAMPLE01.DBF
6 C:\ORACLE\ORADATA\GA ONLINE
S\INDX01.DBF
7 C:\ORACLE\ORADATA\GA ONLINE
S\ODM01.DBF
8 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME     STATUS
---------- -------------------- ----------
S\TOOLS01.DBF
9 C:\ORACLE\ORADATA\GA ONLINE
S\USERS01.DBF
10 C:\ORACLE\ORADATA\GA ONLINE
S\XDB01.DBF
11 C:\ORACLE\ORADATA\GA ONLINE
S\TBLS_BSR.ORA
FILE# NAME     STATUS
---------- -------------------- ----------
12 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_D.DAT
13 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_I.DAT
14 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_O.DAT
15 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_D.DAT
FILE# NAME     STATUS
---------- -------------------- ----------
16 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_I.DAT
17 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_O.DAT
18 C:\ORACLE\ORADATA\GA ONLINE
S\ARCH_D.DAT
19 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME     STATUS
---------- -------------------- ----------
S\ARCH_I.DAT
20 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_D.DAT
21 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_I.DAT
22 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_O.DAT
FILE# NAME     STATUS
---------- -------------------- ----------
23 C:\ORACLE\ORADATA\GA ONLINE
S\.ORA
24 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM02.ORA
25 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM03.ORA
25 rows selected.
SQL> recover datafile 1;
SQL> select file#,name,status from v$datafile;
FILE# NAME     STATUS
---------- -------------------- ----------
1 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM01.DBF
2 C:\ORACLE\ORADATA\GA ONLINE
S\UNDOTBS01.DBF
3 C:\ORACLE\ORADATA\GA ONLINE
S\CWMLITE01.DBF
4 C:\ORACLE\ORADATA\GA ONLINE
S\DRSYS01.DBF
FILE# NAME     STATUS
---------- -------------------- ----------
5 C:\ORACLE\ORADATA\GA ONLINE
S\EXAMPLE01.DBF
6 C:\ORACLE\ORADATA\GA ONLINE
S\INDX01.DBF
7 C:\ORACLE\ORADATA\GA ONLINE
S\ODM01.DBF
8 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME     STATUS
---------- -------------------- ----------
S\TOOLS01.DBF
9 C:\ORACLE\ORADATA\GA ONLINE
S\USERS01.DBF
10 C:\ORACLE\ORADATA\GA ONLINE
S\XDB01.DBF
11 C:\ORACLE\ORADATA\GA ONLINE
S\TBLS_BSR.ORA
FILE# NAME     STATUS
---------- -------------------- ----------
12 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_D.DAT
13 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_I.DAT
14 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_O.DAT
15 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_D.DAT
FILE# NAME     STATUS
---------- -------------------- ----------
16 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_I.DAT
17 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_O.DAT
18 C:\ORACLE\ORADATA\GA ONLINE
S\ARCH_D.DAT
19 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME     STATUS
---------- -------------------- ----------
S\ARCH_I.DAT
20 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_D.DAT
21 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_I.DAT
22 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_O.DAT
FILE# NAME     STATUS
---------- -------------------- ----------
23 C:\ORACLE\ORADATA\GA ONLINE
S\.ORA
24 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM02.ORA
25 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM03.ORA
25 rows selected.
SQL> alter database open;
Task:
Администрирование базы данных. В ПОДСИСТЕМА не грузятся на сайт 200 дел. Смотрим лог файл:
$ cat importer.log.xml
...
<Error ReadableDateTime="23.01.2023 12:51:31" FileTimeUtc="133189230912753002">
<Message>Îøèáêà ïðè âûïîëíåíèè çàïðîñà:
BEGIN BSR.PK_BSR.CTX_Sinxronize; END;
Òåêñò îøèáêè:
ORA-20000: Oracle Text error:
DRG-50857: oracle error in dreii0fsh
ORA-01653: unable to extend table BSR.DR$CTX_SRH$I by 8192 in tablespace SYSTEM
ORA-06512: at "CTXSYS.DRUE", line 157
ORA-06512: at "CTXSYS.CTX_DDL", line 1328
ORA-06512: at "BSR.PK_BSR", line 313
ORA-06512: at line 1
</Message>
<Details>System.Exception: Îøèáêà ïðè âûïîëíåíèè çàïðîñà:
BEGIN BSR.PK_BSR.CTX_Sinxronize; END;
Òåêñò îøèáêè:
ORA-20000: Oracle Text error:
DRG-50857: oracle error in dreii0fsh
ORA-01653: unable to extend table BSR.DR$CTX_SRH$I by 8192 in tablespace SYSTEM
ORA-06512: at "CTXSYS.DRUE", line 157
ORA-06512: at "CTXSYS.CTX_DDL", line 1328
ORA-06512: at "BSR.PK_BSR", line 313
ORA-06512: at line 1
â ImpFunktions.Database.DbaseIns(String cmdString) 
â ImpFunktions.Importer.synchronize()</Details>
</Error>
пуск - все программы - oracle orahome92 - enterprize manager console - ок - databases - gas - system - oracle - storage - tablespaces
Проблема из за того, что у Вас закончилось свободное оракловое ТП SYSTEM и Tbls_bsr, которые надо увеличить вручную путем добавления DATA файла. "Флаг" в чек боксе Авторасширение не поможет.
Можно сделать это в командной строке, добавив дополнительный файл данных к табличному пространству и изменив размер текущего файла данных, Некоторые версии Oracle не позволяют такого!
Decision:
Обязательно делайте бэкап перед любыми действиями с базой. 
Дважды щелкните ЛКМ табличное пространство SYSTEM и добавьте в него второй файл.
Oracle автоматически подставит расширение ORA. Исправьте его на DBF перед сохранением. 
- system - add datafile - name - SYSTEM04.ORA - file size - 4096MB - storage - automatically extend datafile when full - increment - 10240KB - value - 32767MB - ok - перезапустить службу BSRImport - В админке обновлять авоматический импорт из СДП
Decision:
ALTER TABLESPACE USERS ADD DATAFILE '/u01/oradata/orcl/users02.dbf' size 25m;
ALTER DATABASE DATAFILE '/u01/oradata/orcl/users01.dbf' resize 50M;
Необходимо добавить новый датафайл к табличному пространству SYSTEM.
Task:
Активировать Windows 10 pro.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
PS C:\Windows\system32> Start-Process powershell -Verb runAs
PS C:\Windows\system32> irm https://massgrave.dev/get | iex
1
5
Task:
Добавить в исключения ip 10.99.10.42. У клиента после перезагрузки сбрасываются настройки.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
- dc-0 - regedit - домены - dc-0 - Accounts - dc-0 - IE11(Настройки) - Изменить (параметры)
Task:
Обновление лицензии DrWeb.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
- Администрирование - менеджер лицензии - ОРГАНИЗАЦИЯ - распр-ть ключ на группы и станции - EveryOne - Заменить ключи
Task:
В розетке можно подключить подключить одну витую пару. Но нужно разделить на две витые пары розетку для двух компьютеров.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Коммутатуратор вход 1 - 1бо,2о,3бз,6з.
Коммутатуратор вход 2 - 1с,2бс,3бк,6к.
Патч панель - 1бо,2о,3бз,4с,5бс,6з,7бк,8к.
Розетка комп 1 - 1бо,2о,3бз,6з.
Розетка комп 2 - 4бо,5о,7бз,8з.
Комп 1 - 1бо,2о,3бз,4с,5бс,6з,7бк,8к.
Комп 2 - 1бо,2о,3бз,4с,5бс,6з,7бк,8к.
Task:
обжим кабеля ПК-ПК
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
к,бк,з,бс,с,бз,о,бо/бз,з,бо,с,бс,о,бк.
Task:
обжим кабеля ПК-коммутатор
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
к,бк,з,бс,с,бз,о,бо.
Task:
Замена неверного MAC адреса на ПК
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
C:\wMAC(v1.0.87D)>wMAC.exe c 1C57D81543AE
...
Following OUI is support:
000B6Axxxxxx
00138Fxxxxxx
001966xxxxxx
002522xxxxxx
BC5FF4xxxxxx
D05099xxxxxx
0015B2xxxxxx
803773xxxxxx
00045Fxxxxxx
7085C2xxxxxx
DCFE07xxxxxx
1CEEC9xxxxxx
54B203xxxxxx
A8A159xxxxxx
8C0F6Fxxxxxx
9C6B00xxxxxx
ReturnCode:1
     ########  ###  #### ##
     ##     ## ##  ## ##
     ##    ## ## ## ##
     ###### ##   ## ## ##
     ##   ######### ## ##
     ##   ##   ## ## ##
     ##   ##   ## #### ########
C:\wMAC(v1.0.87D)>wMAC.exe c 1CEEC91543AE
...
ReturnCode:0
     ########   ###   ###### ######
     ##   ## ## ## ##  ## ##  ##
     ##   ## ## ## ##   ##
     ######## ##   ## ###### ######
     ##    #########   ##   ##
     ##    ##   ## ##  ## ##  ##
     ##    ##   ## ###### ######

2011-09-01 - 2018-05-30: Иркутский государственный университет, Иркутск. Должность: Информационные технологии и телекоммуникационные системы - Бакалавр / Электроника и наноэлектроника - Магистр / Информационная безопасность - Дополнительное образование. Дополнительная информация: Навыки - Html, Css, Виртуализация, Linux, Sql, Bash, Clouds, Python. Достижения: Разработал сайт портфолио, куда публиковал все решенные мной интересные задачи, отчеты лабораторных работ и презентации, сверстал простой сайт.

Show

Цель:
# Сверстать сайт портфолио.
# Установить виртуальный сервер для дальнейшего тестирования сайта и веб-сервера.
# Установить и настроить веб-сервер LAMP.
# Разработать базу данных Портфолио.
# Написать Sql запрос.
# Подготовить хостинг и настроить облачный сервер.
# Перенести базу данных c тестовой виртуаььной машины в облачный сервер.
# Установить и настроить веб-сервер Django.
# Перенести базу данных c Sqlite в Postgresql.
# Разработать сайт с админ панелью для управления контентами сайта.
# Установить и настроить веб-сервер React.js для frontend-разработки.
Skills:
# Верстка Html-страниц.
# Администрирование веб-сервера LAMP.
# Администрирование баз данных.
# Разработка баз данных.
# Миграция базы данных.
# Администрирование веб-сервера Django.
# Разработка сайта на Django.
Task:
Верстка Html-страниц.
# Верстка Html-страниц. 
Decision:

Ссылка на репозиторий: github.com
Мой сайт: dato138it.ru
Task:
Установка Apache2.
# Администрирование Веб-сервера LAMP.
Decision:
root@kvmubuntu:~# apt install apache2
root@kvmubuntu:~# ufw app list
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
root@kvmubuntu:~# ufw allow 80
root@kvmubuntu:~# ufw status
Status: active
To   Action From
...
80   ALLOW Anywhere
80 (v6)   ALLOW Anywhere (v6)
tuser@kvmubuntu:~$ google-chrome http://tipubuntu:80
Task:
Установка и настройка Mysql.
# Администрирование баз данных.
Decision:
root@kvmubuntu:~# apt install mysql-server
root@kvmubuntu:~# mysql
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'tpassword';
mysql> exit
root@kvmubuntu:~# mysql_secure_installation
...
Press y|Y for Yes, any other key for No: y
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Change the password for root ? ((Press y|Y for Yes, any other key for No) : n
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : n
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
root@kvmubuntu:~# mysql -u root -p
mysql> SELECT user,authentication_string,plugin,host FROM mysql.user;
mysql> rename user 'root'@'localhost' to 'tuser'@'localhost';
mysql> create database tbase;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| tbase |
...
+--------------------+
mysql> exit
Task:
Добавление учетной записи к базе данных.
# Администрирование баз данных.
Decision:
mysql> CREATE USER 'tuser'@'%' IDENTIFIED BY 'tpassword';
mysql> GRANT ALL ON tbase.* TO 'tuser'@'%';
mysql> exit
tuser@kvmubuntu:~$ mysql -u tuser -p
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| tbase |
+--------------------+
3 rows in set (0.07 sec)
Task:
Настройка Lamp.
# Администрирование Веб-сервера LAMP.
Decision:
root@kvmubuntu:~# apt install php libapache2-mod-php php-mysql
root@kvmubuntu:~# php -v
PHP 8.1.2-1ubuntu2.18 (cli) (built: Jun 14 2024 15:52:55) (NTS)
...
root@kvmubuntu:~# vim /etc/php/8.1/apache2/php.ini
root@kvmubuntu:~# cat /etc/php/8.1/apache2/php.ini | grep upload_max_filesize
...
upload_max_filesize = 32M
post_max_size = 48M
memory_limit = 256M
max_execution_time = 600
max_input_vars = 3000
max_input_time = 1000
root@kvmubuntu:~# service apache2 restart
root@kvmubuntu:~# mkdir /var/www/dato138it
root@kvmubuntu:~# chown -R $USER:$USER /var/www/dato138it
root@kvmubuntu:~# vim /etc/apache2/sites-available/dato138it.conf
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf
<VirtualHost *:80>
ServerName dato138it
ServerAlias www.dato138it
ServerAdmin tmail138@mail.ru
DocumentRoot /var/www/dato138it
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
root@kvmubuntu:~# a2ensite dato138it
root@kvmubuntu:~# a2dissite 000-default
root@kvmubuntu:~# apache2ctl configtest
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# vim /var/www/dato138it/index.html
root@kvmubuntu:~# cat /var/www/dato138it/index.html
<html>
<head>
<title>dato138it website</title>
</head>
<body>
<h1>Hello World!</h1>
<p>This is the landing page of <strong>dato138it</strong>.</p>
</body>
</html>
root@kvmubuntu:~# google-chrome http://tipubuntu:80
root@kvmubuntu:~# cat /etc/apache2/mods-enabled/dir.conf
<IfModule mod_dir.c>
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
</IfModule>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# vim /var/www/dato138it/info.php
root@kvmubuntu:~# cat /var/www/dato138it/info.php
<?php
phpinfo();
root@kvmubuntu:~# google-chrome http://tipubuntu:80/info.php
Task:
Разработать схему БД.
# Разработка баз данных.
Decision:

Task:
Создание таблиц.
# Разработка баз данных.
Decision:
mysql> create table tbase.ttable1
(ttable1_id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(255));
CREATE TABLE tbase.ttable1
(ttable1_id INT PRIMARY KEY AUTO_INCREMENT,
column1 VARCHAR(255),
column2 VARCHAR(255),
column3 VARCHAR(255),
column4 VARCHAR(255),
column5 Date,
column6 Date);
mysql> INSERT INTO tbase.ttable1
(column1, column2)
VALUES
("text1","text2"),
("text3","text4"),
("text5","text6"),
("text7","text8");
mysql> select * from tbase.ttable1;
+------------+---------+---------+---------+---------+---------+---------+
| ttable1_id | column1 | column2 | column3 | column4 | column5 | column6 |
+------------+---------+---------+---------+---------+---------+---------+
| 1 | text1 | text2 | NULL | NULL | NULL | NULL |
| 2 | text3 | text4 | NULL | NULL | NULL | NULL |
| 3 | text5 | text6 | NULL | NULL | NULL | NULL |
| 4 | text7 | text8 | NULL | NULL | NULL | NULL |
+------------+---------+---------+---------+---------+---------+---------+
4 rows in set (0.00 sec)
mysql> CREATE TABLE tbase.ttable2
(ttable2_id INT PRIMARY KEY AUTO_INCREMENT,
columnId INT,
column1 VARCHAR(500),
FOREIGN KEY (columnId) REFERENCES tbase.ttable1 (ttable1_id));
mysql> INSERT INTO tbase.ttable2
(columnId, column1)
VALUES
(1, "text9"),
(1, "text10"),
(2, "text11"),
(3, "text12"),
(4, "text13");
mysql> select * from tbase.ttable2;
+------------+----------+---------+
| ttable2_id | columnId | column1 |
+------------+----------+---------+
| 1 | 1 | text9 |
| 2 | 1 | text10 |
| 3 | 2 | text11 |
| 4 | 3 | text12 |
| 5 | 4 | text13 |
+------------+----------+---------+
5 rows in set (0.00 sec)
Task:
Выборка данных из двух таблиц.
# Разработка баз данных.
Decision:
mysql> select * from tbase.ttable2
inner join tbase.ttable1
on ttable1.ttable1_id = ttable2.columnId;
+------------+----------+---------+------------+---------+---------+---------+---------+---------+---------+
| ttable2_id | columnId | column1 | ttable1_id | column1 | column2 | column3 | column4 | column5 | column6 |
+------------+----------+---------+------------+---------+---------+---------+---------+---------+---------+
| 1 | 1 | text9 | 1 | text1 | text2 | NULL | NULL | NULL | NULL |
| 2 | 1 | text10 | 1 | text1 | text2 | NULL | NULL | NULL | NULL |
| 3 | 2 | text11 | 2 | text3 | text4 | NULL | NULL | NULL | NULL |
| 4 | 3 | text12 | 3 | text5 | text6 | NULL | NULL | NULL | NULL |
| 5 | 4 | text13 | 4 | text7 | text8 | NULL | NULL | NULL | NULL |
+------------+----------+---------+------------+---------+---------+---------+---------+---------+---------+
5 rows in set (0.00 sec)
mysql> exit
Source:
# https://metanit.com/sql/mysql/2.5.php
# https://metanit.com/sql/mysql/5.2.php
Task:
Настройка Php.
# Администрирование веб-сервера LAMP.
Decision:
root@kvmubuntu:~# vim /var/www/dato138it/index.php
root@kvmubuntu:~# cat /var/www/dato138it/index.php
<?php
$user = "tuser";
$password = "tpassword";
$database = "tbase";
$table1 = "ttable1";
$table2 = "ttable2";
try {
$db = new PDO("mysql:host=localhost;dbname=$database", $user, $password);
$query = $db->query("
select * from $database.$table2
inner join $database.$table1
on $table1.experience_id=$table2.experienceId;
");
foreach($query as $row) {
echo '
<li><strong>
' . $row['column1'] . '
-
' . $row['column2'] . '
:</strong>
' . $row['column3'] . '
;
' . $row['column4'] . '
;
' . $row['column5'] . '
;
' . $row['column6'] . '
</li>
';
}
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
root@kvmubuntu:~# google-chrome http://tipubuntu/index.php
Task:
Настройка Phpmyadmin.
# Администрирование веб-сервера LAMP.
Decision:
root@kvmubuntu:~# apt install phpmyadmin php-mbstring php-zip php-gd php-json php-curl
...
Configure database for phpmyadmin with dbconfig-common? [yes/no] y
granting access to database phpmyadmin for phpmyadmin@localhost: failed.
error encountered creating user:
mysql said: ERROR 1819 (HY000) at line 1: Your password does not satisfy the current policy requirements
An error occurred while installing the database:
mysql said: ERROR 1819 (HY000) at line 1: Your password does not satisfy the current policy requirements . Your options are:
* abort - Causes the operation to fail; you will need to downgrade,
reinstall, reconfigure this package, or otherwise manually intervene
to continue using it. This will usually also impact your ability to
install other packages until the installation failure is resolved.
* retry - Prompts once more with all the configuration questions
...
1. abort 2. retry 3. retry (skip questions) 4. ignore
Next step for database installation: 1
...
Errors were encountered while processing:
phpmyadmin
needrestart is being skipped since dpkg has failed
E: Sub-process /usr/bin/dpkg returned an error code (1)
root@kvmubuntu:~# mysql -u tuser -p
mysql> UNINSTALL COMPONENT "file://component_validate_password";
mysql> exit
root@kvmubuntu:~# apt install phpmyadmin
root@kvmubuntu:~# apt install php8.1-fpm php8.1 libapache2-mod-php8.1 php8.1-common php8.1-mysql php8.1-xml php8.1-xmlrpc php8.1-imagick php8.1-cli php8.1-imap php8.1-opcache php8.1-soap php8.1-intl php8.1-bcmath unzip
root@kvmubuntu:~# mysql -u tuser -p
mysql> INSTALL COMPONENT "file://component_validate_password";
mysql> exit
root@kvmubuntu:~# phpenmod mbstring
root@kvmubuntu:~# systemctl restart apache2
root@kvmubuntu:~# cp /etc/phpmyadmin/apache.conf /etc/apache2/conf-available/phpmyadmin.conf
root@kvmubuntu:~# a2enconf phpmyadmin.conf
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# google-chrome http://tipubuntu/phpmyadmin
Source:
# https://bozza.ru/art-260.html
# https://www.digitalocean.com/community/tutorials/how-to-install-linux-apache-mysql-php-lamp-stack-on-ubuntu-22-04
# https://losst.pro/ustanovka-chrome-v-ubuntu-18-04?ysclid=lmdflkvc3v463974954
# https://qna.habr.com/q/439469?ysclid=lmdgkb49q827401606
# https://steptuser.org/course/63054/syllabus
# https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-phpmyadmin-on-ubuntu-20-04
# https://serverspace.ru/support/help/osnovnye-komandy-ufw/
# https://www.tutsmake.com/how-to-install-lamp-apache-mysql-php-in-ubuntu-22-04/
# https://www.php.net/manual/ru/faq.html.php
# https://www.8host.com/blog/kak-rabotayut-stroki-v-php/
# https://www.youtube.com/watch?v=FxwPQkP3OGY&t=611s
# https://timeweb.com/ru/community/articles/kak-ustanovit-stek-lamp-na-ubuntu-20-04
# https://wiki.merionet.ru/articles/perenos-bazy-dannyx-mysql-so-starogo-na-novyj-server
Task:
Настройка приложения Django.
# Администрирование веб-сервера Django.
Decision:
root@kvmubuntu:~# apt install python3.10-venv
root@kvmubuntu:~# python3 -m venv djangoenv
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
Django==5.0.2
djangorestframework==3.14.0
django-ckeditor-5==0.2.12
psycopg2-binary==2.9.9
django-ckeditor==6.7.1
python-decouple==3.8
root@kvmubuntu:~# source djangoenv/bin/activate
root@kvmubuntu:~# pip install -r requirements.txt
root@kvmubuntu:~# pip list
Package Version
------------------- -------
asgiref 3.8.1
Django 5.0.2
django-ckeditor 6.7.1
django-ckeditor-5 0.2.12
django-js-asset 2.2.0
djangorestframework 3.14.0
pillow 10.4.0
pip   22.0.2
psycopg2-binary 2.9.9
pytz  2024.1
setuptools 59.6.0
sqlparse 0.5.1
typing_extensions 4.12.2
Task:
Миграция БД с Sqlite3 на PostgreSQL.
# Миграция базы данных.
Decision:
root@kvmubuntu:~# pip install --upgrade pip
root@kvmubuntu:~# pip install --upgrade setuptools wheel
root@kvmubuntu:~# apt-get install libpq-dev python3-dev
root@kvmubuntu:~# pip install psycopg2-binary==2.9.9
root@kvmubuntu:~# python dato138it/manage.py dumpdata > datadump.json
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
DATABASES = {
'default': {
#'ENGINE': 'django.db.backends.sqlite3',
#'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'tbase',
'USER': 'tuser',
'PASSWORD': 'tpassword',
'HOST': 'tipubuntu',
'PORT': '5432',
}
}
...
root@kvmubuntu:~# python dato138it/manage.py migrate --run-syncdb
...
django.core.exceptions.ImproperlyConfigured: 'django.db.backends.potgresql_psycopg2' isn't an available database backend or couldn't be imported. Check the above exception. To use one of the built-in backends, use 'django.db.backends.XXX', where XXX is one of:
'mysql', 'oracle', 'postgresql', 'sqlite3'
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
DATABASES = {
'default': {
...
'ENGINE': 'django.db.backends.postgresql',
...
}
}
...
root@kvmubuntu:~# python dato138it/manage.py migrate --run-syncdb
root@kvmubuntu:~# python dato138it/manage.py shell
>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.all().delete()
>>> quit()
root@kvmubuntu:~# python dato138it/manage.py loaddata datadump.json
root@kvmubuntu:~# python dato138it/manage.py runserver
Source:
# https://www.youtube.com/watch?v=pQzKHNozHts - Миграция с Sqlite3 на PostgreSQL.
Task:
Миграция БД PostgreSQL с помощью дампа.
# Миграция базы данных.
Decision:
root@kvmubuntu:~# pg_dump -Fc -v --username=tuser --dbname=tbase -f datapsql.dump
root@aw:/# scp datapsql.dump tuser@tipubuntu:/home/tuser/
root@kvmubuntu:~# pg_restore -v --no-owner --port=5432 --username=tuser --dbname=tbase datapsql.dump
root@kvmubuntu:~# psql -U tuser -d tbase -h tipubuntu
tbase=# \d
Source:
# https://procloud.ru/blog/cases/perenos-bazy-dannykh-postgresql-s-pomoshchyu-dampa-i-ee-vosstanovlenie/ - Перенос базы данных PostgreSQL с помощью дампа и ее восстановление.
# https://losst.pro/kopirovanie-fajlov-scp - Копирование файлов scp.
Task:
Применить изменения в проекте Django с момента бэкапа. Запуск проекта.
# Администрирование веб-сервера Django.
Decision:
root@kvmubuntu:~# python dato138it/manage.py makemigrations
root@kvmubuntu:~# python dato138it/manage.py migrate
root@kvmubuntu:~# python dato138it/manage.py runserver tipubuntu:8000 &
root@kvmubuntu:~# google-chrome http://tipubuntu:8000
Source:
# https://pocoz.gitbooks.io/django-v-primerah/content/sozdanie-i-primenenie-migracij.html - Создание и применение миграций.
Task:
Первый запуск проекта Django.
# Разработка сайта на Django.
Decision:
root@aw:/# mkdir drf
root@aw:/# cd drf/
root@aw:/# django-admin startproject dato138it
root@aw:/# cd dato138it/
root@aw:/# google-chrome http://127.0.0.1:8000 &
root@aw:/# python3 manage.py runserver
root@aw:/# python3 manage.py migrate
root@aw:/# vim settings.py
root@aw:/# cat settings.py
... 
#LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ru'
#TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Moscow'
...
root@aw:/# python3 manage.py startapp portfolio
root@aw:/# cat settings.txt
... 
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portfolio.apps.PortfolioConfig',
]
...
root@aw:/# vim portfolio\models.py
root@aw:/# cat portfolio\models.py
from django.db import models
class Portfolio(models.Model):
title=models.CharField(max_length=255, verbose_name="Заголовок")
content=models.TextField(blank=True, verbose_name="Текст статьи")
time_create=models.DateTimeField(auto_now_add=True, verbose_name="Время создания")
time_update=models.DateTimeField(auto_now=True, verbose_name="Время изменения")
is_published=models.BooleanField(default=True, verbose_name="Публикация")
cat=models.ForeignKey('Category', on_delete=models.PROTECT, null=True, verbose_name="Категории")
def __str__(self):
return self.title
class Category(models.Model):
name=models.CharField(max_length=100, db_index=True, verbose_name="Категория")
def __str__(self):
return self.name
root@aw:/# python3 manage.py makemigrations
root@aw:/# python3 manage.py migrate
root@aw:/# python3 manage.py createsuperuser
Имя пользователя (leave blank to use 'tuser'): tuser
Адрес электронной почты: tmail138@mail.ru
Password: 
Password (again): 
root@aw:/# vim portfolio\admin.py
root@aw:/# cat portfolio\admin.py
from django.contrib import admin
from .models import Portfolio
admin.site.register(Portfolio)
root@aw:/# sudo apt install sqlite3
root@aw:/# sqlite3 db.sqlite3
sqlite> SELECT name from sqlite_master where type= "table";
django_migrations
sqlite_sequence
auth_group_permissions
auth_user_groups
auth_user_user_permissions
django_admin_log
django_content_type
auth_permission
auth_group
auth_user
django_session
portfolio_category
portfolio_portfolio
sqlite> INSERT INTO portfolio_category (name)
VALUES ("Категория1"), ("Категория2");
sqlite> select * from portfolio_category;
1|Категория1
2|Категория2
root@aw:/# python3 manage.py startapp portfolio
root@aw:/# cat settings.py
... 
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portfolio.apps.PortfolioConfig',
'rest_framework',
]
...
root@aw:/# python3 manage.py runserver
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics
from .models import Portfolio
from .serializers import PortfolioSerializer
class portfolioAPIView(generics.ListAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
class PortfolioSerializer(serializers.ModelSerializer):
class Meta:
model = Portfolio
fields = ('title', 'cat_id')
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
from django.contrib import admin
from django.urls import path
from portfolio.views import PortfolioAPIView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
]
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/portfoliolist/ &
Task:
Базовый класс APIView для представлений.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics
from .models import Portfolio
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
#class portfolioAPIView(generics.ListAPIView):
# queryset = Portfolio.objects.all()
# serializer_class = PortfolioSerializer
class portfolioAPIView(APIView):
def get(self, request):
lst=portfolio.objects.all().values()
#return Response({'title':'Пост1'})
return Response({'posts':list(lst)})
def post(self, request):
#return Response({'title':'Пост2'})
post_new=portfolio.objects.create(
title=request.data['title'],
content=request.data['content'],
cat_id=request.data['cat_id']
)
return Response({'post':model_to_dict(post_new)})
root@aw:/# google-chrome https://web.postman.co/workspace/ &
Task:
Класс Serializer.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
from rest_framework.renderers import JSONRenderer
class portfolioModel:
def __init__(self, title, content):
self.title = title
self.content = content
#class PortfolioSerializer(serializers.ModelSerializer):
# class Meta:
#   model = Portfolio
#   fields = ('title', 'cat_id')
class PortfolioSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
def encode():
model = PortfolioModel('Post1', 'Content: Post1')
model_sr = PortfolioSerializer(model)
print(model_sr.data, type(model_sr.data), sep='\n')
json = JSONRenderer().render(model_sr.data)
print(json)
root@aw:/# python3 manage.py shell
>>> from portfolio.serializers import encode
>>> encode()
{'title': 'Post1', 'content': 'Content: Post1'}
<class 'rest_framework.utils.serializer_helpers.ReturnDict'>
b'{"title":"Post1","content":"Content: Post1"}'
>>> quit()
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
import io
...
def decode():
stream = io.BytesIO(b'{"title":"Post1", "content":"Content: Post1"}')
data = JSONParser().parse(stream)
serializer = PortfolioSerializer(data=data)
serializer.is_valid()
print(serializer.validated_data)
root@aw:/# python3 manage.py shell
>>> from portfolio.serializers import decode
>>> decode()
OrderedDict([('title', 'Post1'), ('content', 'Content: Post1')])
>>> quit()
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
import io
#class portfolioModel:
# def __init__(self, title, content):
#   self.title = title
#   self.content = content
#class PortfolioSerializer(serializers.ModelSerializer):
# class Meta:
#   model = Portfolio
#   fields = ('title', 'cat_id')
class PortfolioSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
time_create=serializers.DateTimeField(read_only=True)
time_update=serializers.DateTimeField(read_only=True)
is_published=serializers.BooleanField(default=True)
cat_id=serializers.IntegerField() 
#def encode():
# model = PortfolioModel('Post1', 'Content: Post1')
# model_sr = PortfolioSerializer(model)
# print(model_sr.data, type(model_sr.data), sep='\n')
# json = JSONRenderer().render(model_sr.data)
# print(json)
#def decode():
# stream = io.BytesIO(b'{"title":"Post1", "content":"Content: Post1"}')
# data = JSONParser().parse(stream)
# serializer = PortfolioSerializer(data=data)
# serializer.is_valid()
# print(serializer.validated_data)
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
class portfolioAPIView(APIView):
def get(self, request):
#lst=portfolio.objects.all().values()
#return Response({'title':'Пост1'})
#return Response({'posts':list(lst)})
w = portfolio.objects.all()
return Response({'posts':PortfolioSerializer(w, many=True).data})
def post(self, request):
#return Response({'title':'Пост2'})
serializer=PortfolioSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
post_new=portfolio.objects.create(
title=request.data['title'],
content=request.data['content'],
cat_id=request.data['cat_id']
)
#return Response({'post':model_to_dict(post_new)})
return Response({'post':PortfolioSerializer(post_new).data})
root@aw:/# python3 manage.py runserver
Task:
Методы save(), create() и update() класса Serializer.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
...
class PortfolioSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
time_create=serializers.DateTimeField(read_only=True)
time_update=serializers.DateTimeField(read_only=True)
is_published=serializers.BooleanField(default=True)
cat_id=serializers.IntegerField()
def create(self, validated_data):
return portfolio.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title=validated_data.get("title", instance.title)
instance.content=validated_data.get("content", instance.content)
instance.time_update=validated_data.get("time_update", instance.time_update)
instance.is_published=validated_data.get("is_published", instance.is_published)
instance.cat_id=validated_data.get("cat_id", instance.cat_id)
instance.save()
return instance
...
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
...
def post(self, request):
#return Response({'title':'Пост2'})
serializer=PortfolioSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
#post_new=portfolio.objects.create(
# title=request.data['title'],
# content=request.data['content'],
# cat_id=request.data['cat_id']
#)
#return Response({'post':model_to_dict(post_new)})
#return Response({'post':PortfolioSerializer(post_new).data})
serializer.save()
return Response({'post':serializer.data})
def put(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
if not pk:
return Response({"error": "Method PUT not allowed"})
try:
instance = portfolio.objects.get(pk=pk)
except:
return Response({"error": "Object does not exists"})
serializer = PortfolioSerializer(data=request.data, instance=instance)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"post": serializer.data})
def delete(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
if not pk:
return Response({"error": "Method DELETE not allowed"})
return Response({"post":"delete post "+str(pk)})
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
]
root@aw:/# python3 manage.py runserver
Task:
Класс ModelSerializer и представление ListCreateAPIView.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
...
#class PortfolioSerializer(serializers.Serializer):
#title = serializers.CharField(max_length=255)
#content = serializers.CharField()
#time_create=serializers.DateTimeField(read_only=True)
#time_update=serializers.DateTimeField(read_only=True)
#is_published=serializers.BooleanField(default=True)
#cat_id=serializers.IntegerField()
#def create(self, validated_data):
#return portfolio.objects.create(**validated_data)
#def update(self, instance, validated_data):
#instance.title=validated_data.get("title", instance.title)
#instance.content=validated_data.get("content", instance.content)
#instance.time_update=validated_data.get("time_update", instance.time_update)
#instance.is_published=validated_data.get("is_published", instance.is_published)
#instance.cat_id=validated_data.get("cat_id", instance.cat_id)
#instance.save()
#return instance
class PortfolioSerializer(serializers.ModelSerializer):
class Meta:
model=portfolio
#fields = ("title", "content", "cat")
fields="__all__"
...
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
#class portfolioAPIView(generics.ListAPIView):
class portfolioAPIList(generics.ListCreateAPIView):
queryset=portfolio.objects.all()
serializer_class=PortfolioSerializer
...
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
#from portfolio.views import PortfolioAPIView
from portfolio.views import *
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
]
root@aw:/# python3 manage.py runserver
Task:
Представления UpdateAPIView и RetrieveUpdateDestroyAPIView.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
#class portfolioAPIView(APIView):
#def get(self, request):
#lst=portfolio.objects.all().values()
#return Response({'title':'Пост1'})
#return Response({'posts':list(lst)})
#w = portfolio.objects.all()
#return Response({'posts':PortfolioSerializer(w, many=True).data})
#def post(self, request):
#return Response({'title':'Пост2'})
#serializer=PortfolioSerializer(data=request.data)
#serializer.is_valid(raise_exception=True)
#post_new=portfolio.objects.create(
#title=request.data['title'],
#content=request.data['content'],
#cat_id=request.data['cat_id']
#)
#return Response({'post':model_to_dict(post_new)})
#return Response({'post':PortfolioSerializer(post_new).data})
#serializer.save()
#return Response({'post':serializer.data})
#def put(self, request, *args, **kwargs):
#pk = kwargs.get("pk", None)
#if not pk:
#return Response({"error": "Method PUT not allowed"})
#try:
#instance = portfolio.objects.get(pk=pk)
#except:
#return Response({"error": "Object does not exists"})
#serializer = PortfolioSerializer(data=request.data, instance=instance)
#serializer.is_valid(raise_exception=True)
#serializer.save()
#return Response({"post": serializer.data})
#def delete(self, request, *args, **kwargs):
#pk = kwargs.get("pk", None)
#if not pk:
#return Response({"error": "Method DELETE not allowed"})
#return Response({"post":"delete post "+str(pk)})
class portfolioAPIUpdate(generics.UpdateAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
class portfolioAPIDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/<int:pk>/', portfolioAPIUpdate.as_view()),
path('api/v1/portfoliodetail/<int:pk>/', portfolioAPIDetailView.as_view()),
]
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES':[
'rest_framework.renderers.JSONRenderer',
#'rest_framework.renderers.BrowsableAPIRenderer',
]
}
root@aw:/# python3 manage.py runserver
Task:
Viewsets и ModelViewSet
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics, viewsets, mixins
from .models import Portfolio
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
from rest_framework.viewsets import GenericViewSet
...
#class portfolioAPIList(generics.ListCreateAPIView):
#queryset=portfolio.objects.all()
#serializer_class=PortfolioSerializer
...
#class portfolioAPIUpdate(generics.UpdateAPIView):
#queryset = Portfolio.objects.all()
#serializer_class = PortfolioSerializer
#class portfolioAPIDetailView(generics.RetrieveUpdateDestroyAPIView):
#queryset = Portfolio.objects.all()
#serializer_class = PortfolioSerializer
#class portfolioViewSet(viewsets.ModelViewSet):
#class portfolioViewSet(viewsets.ReadOnlyModelViewSet):
class portfolioViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
from django.contrib import admin
from django.urls import path, include
#from portfolio.views import PortfolioAPIView
from portfolio.views import *
from rest_framework import routers
router=routers.SimpleRouter()
router.register(r'portfolio', portfolioViewSet)
urlpatterns = [
  path('admin/', admin.site.urls),
  #path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
  #path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
  #path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
  #path('api/v1/portfoliolist/<int:pk>/', portfolioAPIUpdate.as_view()),
  #path('api/v1/portfoliodetail/<int:pk>/', portfolioAPIDetailView.as_view()),
  #path('api/v1/portfoliolist/', portfolioViewSet.as_view({'get':'list'})),
  #path('api/v1/portfoliodetail/<int:pk>/', portfolioViewSet.as_view({'put':'update'})),
  path('api/v1/', include(router.urls)),
]
root@aw:/# python3 manage.py runserver
Task:
Роутеры: SimpleRouter и DefaultRouter 
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
class MyCustomRouter(routers.SimpleRouter):
routes = [
routers.Route(url=r'^{prefix}$',
mapping={'get': 'list'},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}),
routers.Route(url=r'^{prefix}/{lookup}$',
mapping={'get': 'retrieve'},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Detail'})
]
#router=routers.SimpleRouter()
#router=routers.DefaultRouter()
router=MyCustomRouter()
#router.register(r'portfolio', portfolioViewSet)
#router.register(r'portfolio', portfolioViewSet, basename='men')
router.register(r'portfolio', portfolioViewSet, basename='portfolio')
print(router.urls)
...
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics, viewsets, mixins
from .models import Portfolio, Category
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
...
class portfolioViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
#queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
def get_queryset(self):
#return portfolio.objects.all()[:3]
pk=self.kwargs.get("pk")
if not pk:
return portfolio.objects.all()[:3]
return portfolio.objects.filter(pk=pk)
@action(methods=['get'], detail=True)
def category(self, request, pk=None):
#cats = Category.objects.all()
#return Response({'cats': [c.name for c in cats]})
cats = Category.objects.get(pk=pk)
return Response({'cats': cats.name})
root@aw:/# python3 manage.py runserver
Task:
Ограничения доступа (permissions)
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\models.py
root@aw:/# cat portfolio\models.py
from django.db import models
from django.contrib.auth.models import User
...
cat=models.ForeignKey('Category', on_delete=models.PROTECT, null=True, verbose_name="Категории")
user=models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Пользователь")
def __str__(self):
...
root@aw:/# python3 manage.py makemigrations
Select an option: 1
>>> 1
root@aw:/# python3 manage.py migrate
root@aw:/# vim portfolio/views.py
root@aw:/# cat portfolio/views.py
from django.shortcuts import render
from rest_framework import generics, viewsets, mixins
from .models import Portfolio, Category
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser
from .permissions import IsAdminOrReadOnly, IsOwnerOrReadOnly
class portfolioAPIList(generics.ListCreateAPIView):
queryset=portfolio.objects.all()
serializer_class=PortfolioSerializer
#permissions_classes=(IsAuthenticatedOrReadOnly, )
class portfolioAPIUpdate(generics.RetrieveUpdateAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
permissions_classes=(IsOwnerOrReadOnly, )
class portfolioAPIDestroy(generics.RetrieveDestroyAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
permissions_classes=(IsAdminOrReadOnly, )
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
#class MyCustomRouter(routers.SimpleRouter):
#  routes = [
#    routers.Route(url=r'^{prefix}$',
#           mapping={'get': 'list'},
#           name='{basename}-list',
#           detail=False,
#           initkwargs={'suffix': 'List'}),
#    routers.Route(url=r'^{prefix}/{lookup}$',
#           mapping={'get': 'retrieve'},
#           name='{basename}-detail',
#           detail=True,
#           initkwargs={'suffix': 'Detail'})
#  ]
#router=routers.SimpleRouter()
#router=routers.DefaultRouter()
#router=MyCustomRouter()
#router.register(r'portfolio', portfolioViewSet)
#router.register(r'portfolio', portfolioViewSet, basename='men')
#router.register(r'portfolio', portfolioViewSet, basename='portfolio')
#print(router.urls)
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
#path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIUpdate.as_view()),
#path('api/v1/portfoliodetail/<int:pk>/', portfolioAPIDetailView.as_view()),
#path('api/v1/portfoliolist/', portfolioViewSet.as_view({'get':'list'})),
#path('api/v1/portfoliodetail/<int:pk>/', portfolioViewSet.as_view({'put':'update'})),
#path('api/v1/', include(router.urls)),
path('api/v1/portfolio/', portfolioAPIList.as_view()),
path('api/v1/portfolio/<int:pk>/', portfolioAPIUpdate.as_view()),
path('api/v1/portfoliodelete/<int:pk>/', portfolioAPIDestroy.as_view()),
]
root@aw:/# vim portfolio/serializers.py
root@aw:/# cat portfolio/serializers.py
...
class PortfolioSerializer(serializers.ModelSerializer):
user=serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model=portfolio
#fields = ("title", "content", "cat")
fields="__all__"
...
root@aw:/# touch portfolio/permissions.py
root@aw:/# vim portfolio/permissions.py
root@aw:/# cat portfolio/permissions.py
from rest_framework import permissions
class IsAdminOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return bool(request.user and request.user.is_staff)
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES':[
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PERMISSION_CLASSES':[
#'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.AllowAny',
]
}
root@aw:/# python3 manage.py runserver
Task:
Авторизация и аутентификация. Session-based authentication.
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
[
...
path('api/v1/portfoliodelete/<int:pk>/', portfolioAPIDestroy.as_view()),
path('api/v1/drf-auth/', include('rest_framework.urls')),
]
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/drf-auth/
Task:
Аутентификация по токенам. Пакет Djoser
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portfolio.apps.PortfolioConfig',
'rest_framework',
'rest_framework.authtoken',
'djoser',
]
...
root@aw:/# python3 manage.py migrate
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
from django.urls import path, include, re_path
[
...
path('api/v1/drf-auth/', include('rest_framework.urls')),
path('api/v1/auth/', include('djoser.urls')),
re_path(r'^auth/', include('djoser.urls.authtoken')),
]
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES':[
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser, IsAuthenticated
from .permissions import IsAdminOrReadOnly, IsOwnerOrReadOnly
from rest_framework.authentication import TokenAuthentication
...
class portfolioAPIUpdate(generics.RetrieveUpdateAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
#permissions_classes=(IsOwnerOrReadOnly, )
permissions_classes=(IsAuthenticated, )
#authentication_classes = (TokenAuthentication, )
...
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/auth/ & 
Task:
Делаем авторизацию по JWT-токенам
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
from pathlib import Path
from datetime import timedelta
...
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES':[
#'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": False,
"ALGORITHM": "HS256",
"SIGNING_KEY": SECRET_KEY,
"VERIFYING_KEY": "",
"AUDIENCE": None,
"ISSUER": None,
"JSON_ENCODER": None,
"JWK_URL": None,
"LEEWAY": 0,
#"AUTH_HEADER_TYPES": ("Bearer",),
"AUTH_HEADER_TYPES": ("JWT",),
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
"TOKEN_TYPE_CLAIM": "token_type",
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
"JTI_CLAIM": "jti",
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
from django.contrib import admin
from django.urls import path, include, re_path
#from portfolio.views import PortfolioAPIView
from portfolio.views import *
from rest_framework import routers
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
...
urlpatterns = [
...
re_path(r'^auth/', include('djoser.urls.authtoken')),
path('api/v1/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path('api/v1/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/v1/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/token/ & 
Task:
Добавляем пагинацию (pagination)
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 2,
...
}
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
from rest_framework.pagination import PageNumberPagination
#class portfolioAPIView(generics.ListAPIView):
class portfolioAPIListPagination(PageNumberPagination):
page_size=3
page_size_query_param='page_size'
max_page_size=10000
class portfolioAPIList(generics.ListCreateAPIView):
queryset=portfolio.objects.all()
serializer_class=PortfolioSerializer
permissions_classes=(IsAuthenticatedOrReadOnly, )
pagination_class=portfolioAPIListPagination
...
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/portfolio/ & 
Source:
# https://www.youtube.com/playlist?list=PLA0M1Bcd0w8xZA3Kl1fYmOH_MfLpiYMRs
# https://www.freecodecamp.org/news/python-requirementstxt-explained/
# https://timeweb.cloud/tutorials/sqlite/rukovodstvo-po-nastrojke-sqlite
# https://itfy.org/threads/kak-poluchit-spisok-tablic-v-sqlite3.695/
# https://linuxgenie.net/how-to-install-postman-on-ubuntu-22-04/
# https://pyonlycode.com/post/how-to-solve-nameerror-name-io-is-not-defined/
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/index.html
# https://jwt.io/
Task:
B проекте нужно сделать так, чтобы необязательно заполнялось определенное поле. В нашем случае это поле doc.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# cat portfolio/models.py
...
doc=models.FileField(upload_to="uploads/%Y/%m/%d/", verbose_name="Файлы")
root@kvmubuntu:~# vim portfolio/models.py
root@kvmubuntu:~# cat portfolio/models.py
...
doc=models.FileField(upload_to="uploads/%Y/%m/%d/", verbose_name="Файлы", blank=True, null=True)
root@kvmubuntu:~# python3 manage.py makemigrations
root@kvmubuntu:~# python3 manage.py migrate
root@kvmubuntu:~# python3 manage.py runserver
Source:
# https://ru.stackoverflow.com/questions/1059864/%D0%A1%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C-%D0%BD%D0%B5%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC-%D0%BF%D0%BE%D0%BB%D0%B5-arrayfield-%D0%B2-django-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8 - Сделать необязательным поле ArrayField в Django модели.
# https://ru.stackoverflow.com/questions/758588/%D0%9A%D0%B0%D0%BA-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D0%BD%D0%BE%D0%B2%D0%BE%D0%B5-%D0%BF%D0%BE%D0%BB%D0%B5-%D0%B2-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C-django - Как добавить новое поле в модель django.
Task:
Integrating CKEditor in Django Admin and Rendering HTML in a template.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# pip install django-ckeditor==6.7.1
django-ckeditor-5==0.2.12
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
INSTALLED_APPS = [
...,
'ckeditor', 
'ckeditor_uploader',
]
...
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media/"
#ckeditor upload path
CKEDITOR_UPLOAD_PATH="uploads/"
CKEDITOR_CONFIGS = {
'default': {
# 'skin': 'moono',
# # 'skin': 'office2013',
# 'toolbar_Basic': [
# ['Source', '-', 'Bold', 'Italic']
# ],
'toolbar_Custom': [
{'name': 'document', 'items': ['Source', '-', 'Save', 'NewPage', 'Preview', 'Print', '-', 'Templates']},
{'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']},
{'name': 'editing', 'items': ['Find', 'Replace', '-', 'SelectAll']},
{'name': 'forms',
'items': ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton',
'HiddenField']},
'/',
{'name': 'basicstyles',
'items': ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']},
{'name': 'paragraph',
'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-',
'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl',
'Language']},
{'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']},
{'name': 'insert',
'items': ['Image', 'Youtube','Flash', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', 'Iframe']},
'/',
{'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']},
{'name': 'colors', 'items': ['TextColor', 'BGColor']},
{'name': 'tools', 'items': ['Maximize', 'ShowBlocks']},
{'name': 'about', 'items': ['CodeSnippet']},
{'name': 'about', 'items': ['About']},
'/', # put this to force next toolbar on new line
{'name': 'yourcustomtools', 'items': [
# put the name of your editor.ui.addButton here
'Preview',
'Maximize',
]},
],
'toolbar': 'Custom', # put selected toolbar config here
'toolbarGroups': [{ 'name': 'document', 'groups': [ 'mode', 'document', 'doctools' ] }],
'height': 400,
# 'width': '100%',
'filebrowserWindowHeight': 725,
'filebrowserWindowWidth': 940,
'toolbarCanCollapse': True,
'mathJaxLib': '//cdn.mathjax.org/mathjax/2.2-latest/MathJax.js?config=TeX-AMS_HTML',
'tabSpaces': 4,
'extraPlugins': ','.join([
'uploadimage', # the upload image feature
# your extra plugins here
'div',
'autolink',
'autoembed',
'embedsemantic',
'autogrow',
'devtools',
'widget',
'lineutils',
'clipboard',
'dialog',
'dialogui',
'elementspath',
'codesnippet',
]),
}
}
root@kvmubuntu:~# vim dato138it/urls.py
root@kvmubuntu:~# cat dato138it/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
path('ckeditor/',include('ckeditor_uploader.urls')),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
root@kvmubuntu:~# vim portfolio/models.py
root@kvmubuntu:~# cat portfolio/models.py
from ckeditor_uploader.fields import RichTextUploadingField
...
class Portfolio(models.Model):
title = models.TextField(verbose_name="Достижение")
description = RichTextUploadingField(verbose_name="Статья")
image = models.ImageField(upload_to='uploads/%Y/%m/%d/', blank=True, null=True)
url = models.URLField(blank=True, null=True)
ordinal = models.IntegerField()
...
root@kvmubuntu:~# python manage.py makemigrations
root@kvmubuntu:~# python manage.py migrate
root@kvmubuntu:~# python manage.py runserver
Source:
# https://www.codesnail.com/integrating-ckeditor-in-django-admin-and-rendering-html-in-a-template-django-blog-4/ - Integrating CKEditor in Django Admin and Rendering HTML in a template.
Task:
Как добавить страницу в Django.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# vim dato138it/urls.py
root@kvmubuntu:~# cat dato138it/urls.py
...
#from django.http import HttpResponse
#def show_contents(request):
# print('Кто-то зашёл на главную!')
# return HttpResponse('Привет!')
from portfolio import views
urlpatterns = [
...
path('contents/', views.show_contents),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
root@kvmubuntu:~# vim portfolio/views.py
root@kvmubuntu:~# cat portfolio/views.py
...
from django.http import HttpResponse
...
def show_contents(request):
print('Кто-то зашёл на главную!')
return HttpResponse('Привет!')
root@kvmubuntu:~# python manage.py runserver tipubuntu:8000
root@kvmubuntu:~# google-chrome http://tipubuntu:8000/contents/
root@kvmubuntu:~# cp -r /var/www/tdb ../backend
root@kvmubuntu:~# ls tdb/
css index.html js
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'tdb')],
...
},
]
...
root@kvmubuntu:~# vim portfolio/views.py
root@kvmubuntu:~# cat portfolio/views.py
...
from django.http import HttpResponse
from django.template import loader
...
def show_contents(request):
#print('Кто-то зашёл на главную!')
template = loader.get_template('index.html')
context = {}
rendered_page = template.render(context, request)
return HttpResponse(rendered_page)
Source:
# https://dvmn.org/encyclopedia/django/how-to-add-page/?ysclid=lul44qesp8431289715 - Как добавить страницу в Django.
# https://codeease.net/programming/python/NameError-name-os-is-not-defined-django#:~:text=NameError%3A%20name%20'os'%20is%20not,directory%20operations%2C%20and%20environment%20variables - NameError name os is not defined django.
Task:
Настройка и подключение статических файлов в Django.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# mkdir static
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
import os
...
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
...
]
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'tdb')],
'APP_DIRS': True,
...
...
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
...
root@kvmubuntu:~# mv tdb/css static/
root@kvmubuntu:~# mv tdb/js static/
root@kvmubuntu:~# ls static/
css js
root@kvmubuntu:~# vim tdb/index.html
root@kvmubuntu:~# cat tdb/index.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<!--SECTIONS CARDS-->
<section>
<div class="container">
{% if content.count > 0 %}
<div class="cards">
{% for portfolio in content %}
<div class="card__content" id="" style="text-align: left;"> 
<p class="card_hidden" onclick="card__hidden(this)">{{portfolio.progress}}</p>
<p>{{portfolio.description}}</p>
</div>
{% endfor %}
</div> 
{% endif %}
</div>
</section> 
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
tuser@kvmubuntu:~$ python manage.py runserver tipubuntu:8000
Source:
# https://pythonru.com/uroki/django-static?ysclid=lul6hmdpge797629443 - Настройка и подключение статических файлов в Django.
Task:
Настройка блоков с разделами.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# vim portfolio/models.py 
root@kvmubuntu:~# cat portfolio/models.py 
from django.db import models
...
class Place(models.Model):
...
class Portfolio(models.Model):
progress = models.TextField(verbose_name="Достижение")
description = RichTextUploadingField(verbose_name="Описание")
image = models.ImageField(upload_to='uploads/%Y/%m/%d/', blank=True, null=True)
url = models.URLField(blank=True, null=True)
class Category(models.Model):
...
root@kvmubuntu:~# python manage.py makemigrations
...
Was portfolio.title renamed to portfolio.progress (a TextField)? [y/N] y
...
root@kvmubuntu:~# python manage.py migrate
root@kvmubuntu:~# vim portfolio/views.py
root@kvmubuntu:~# cat portfolio/views.py
...
from .models import Place, Portfolio, Category
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.shortcuts import render
class UserViewSet(viewsets.ModelViewSet):
...
class PlaceViewSet(viewsets.ModelViewSet):
...
class PortfolioViewSet(viewsets.ModelViewSet):
...
class CategoryViewSet(viewsets.ModelViewSet):
...
def show_contents(request):
...
# получение данных из бд
def index(request):
content = Portfolio.objects.all()
return render(request, "index.html", {"content": content})
root@kvmubuntu:~# vim tdb/index.html
root@kvmubuntu:~# cat tdb/index.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<title>dato138it</title>
</head>
<body>
<!--SECTIONS CARDS-->
<section>
<div class="container">
{% if content.count > 0 %}
<div class="cards">
{% for portfolio in content %}
<div class="card__content" id="" style="text-align: left;"> 
{{portfolio.progress|safe}}
<p class="card_hidden" onclick="card__hidden(this)">Show</p>
<div style="display:none;" style=&{head};>{{portfolio.description|safe}}</div>
</div>
{% endfor %}
</div> 
{% endif %}
</div>
</section> 
<!-- JS -->
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
root@kvmubuntu:~# vim dato138it/urls.py
root@kvmubuntu:~# cat dato138it/urls.py
...
from django.urls import path, include
from portfolio import views
urlpatterns = [
...
path("index/", views.index),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Source:
# https://metanit.com/python/django/5.4.php - CRUD.
# https://stackoverflow.com/questions/17880663/ckeditor-shows-me-html-code - CKEditor shows me html code.
Task:
Сделать страницу контентов основной страницей сайта.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# vim /var/www/dato138it/dato138it/urls.py
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/urls.py
....
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
#path('', include('portfolio.urls')),
path("ckeditor5/", include('django_ckeditor_5.urls'), name="ck_editor_5_upload_file"),
#path('contents/', views.contents),
path('urls-rest/', include('portfolio.urls')),
path('', views.contents),
...
Task:
Как скрыть токены и пароли в python?
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# source /var/www/dato138it/djangoenv/bin/activate
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
...
python-decouple==3.8
python-dotenv==1.0.1
root@kvmubuntu:~# pip install -r requirements.txt
root@kvmubuntu:~# vim /var/www/dato138it/.env
root@kvmubuntu:~# cat /var/www/dato138it/.env
DB_NAME = 'tbase'
DB_USER = 'tuser'
DB_PASSWD = 'tpassword'
SERVER_IP = 'tipubuntu'
DB_PORT = '5432'
DJANGO_KEY = 'tkey'
root@kvmubuntu:~# vim /var/www/dato138it/dato138it/settings.py
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/settings.py
from pathlib import Path
import os
#from dotenv import load_dotenv
from decouple import config
...
SECRET_KEY = config('DJANGO_KEY')
...
ALLOWED_HOSTS = [config('SERVER_IP'), 'dato138it.ru']
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWD'),
'HOST': config('SERVER_IP'),
'PORT': config('DB_PORT'),
}
}
...
root@kvmubuntu:~# ls -la /var/www/dato138it/.env
-rw-r--r--. 1 tuser tuser 191 Aug 1 16:06 /var/www/dato138it/.env
root@kvmubuntu:~# chown root:root /var/www/dato138it/.env
root@kvmubuntu:~# chmod 604 /var/www/dato138it/.env
root@kvmubuntu:~# ls -la /var/www/dato138it/.env
-rw----r--. 1 root root 191 Aug 1 16:06 /var/www/dato138it/.env
Source:
# https://www.youtube.com/watch?v=OQ6cEG0ykVs&list=PLV0FNhq3XMOJ31X9eBWLIZJ4OVjBwb-KM&index=10 - PostgreSQL + Скрытие Токена в .env - Aiogram 3.
# https://dev.to/earthcomfy/django-how-to-keep-secrets-safe-with-python-dotenv-5811 - Django - How to keep secrets safe with python-dotenv.
# https://dontrepeatyourself.org/post/how-to-use-python-decouple-with-django/ - How to Use Python Decouple with Django.
# https://redos.red-soft.ru/base/arm/share/network-directories-connection-with-automount/ - Автоматическое монтирование ресурсов CIFS.
Task:
как привязать домен timeweb к начальной странице проекта веб-сервера django если уже установлен Apache?
# Администрирование веб-сервера Django.
Decision:
root@kvmubuntu:~# apt install libapache2-mod-wsgi-py3
root@kvmubuntu:~# systemctl restart apache2
root@kvmubuntu:~# systemctl status apache2
root@kvmubuntu:~# cp -r /home/tuser/dato138it/* /var/www/dato138it/
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/settings.py
...
from pathlib import Path
import os
...
ALLOWED_HOSTS = ['tipubuntu', 'dato138it.ru']
...
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
STATIC_ROOT=os.path.join(BASE_DIR, 'static/') 
...
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media/"
#MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
...
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf
<VirtualHost *:80>
ServerName dato138it
ServerAlias www.dato138it
ServerAdmin webmaster@localhost
DocumentRoot /var/www/dato138it
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
root@kvmubuntu:~# vim /etc/apache2/sites-available/dato138it.conf
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf
<VirtualHost *:8033>
ServerAdmin tmail@mail.ru
ServerName dato138it.ru
ServerAlias www.dato138it.ru
DocumentRoot /var/www/dato138it
ErrorLog ${APACHE_LOG_DIR}/dato138it.ru_error.log
CustomLog ${APACHE_LOG_DIR}/dato138it.ru_access.log combined
Alias /static /var/www/dato138it/static
<Directory /var/www/dato138it/static>
Require all granted
</Directory>
Alias /media /var/www/dato138it/media
<Directory /var/www/dato138it/media>
Require all granted
</Directory>
<Directory /var/www/dato138it/dato138it>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
WSGIDaemonProcess dato138it python-path=/var/www/dato138it python-home=/var/www/dato138it/djangoenv
WSGIProcessGroup dato138it
WSGIScriptAlias / /var/www/dato138it/dato138it/wsgi.py
</VirtualHost>
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf | grep djangoenv
WSGIDaemonProcess dato138it python-path=/var/www/dato138it python-home=/var/www/dato138it/djangoenv
root@kvmubuntu:~# a2ensite dato138it.conf
root@kvmubuntu:~# apache2ctl configtest
root@kvmubuntu:~# systemctl restart apache2
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# google-chrome http://tipubuntu:80
root@kvmubuntu:~# vim /var/www/dato138it/dato138it/settings.py
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/settings.py
...
STATIC_URL = '/static/'
#STATICFILES_DIRS = [
# os.path.join(BASE_DIR, "static"),
#]
STATIC_ROOT=os.path.join(BASE_DIR, 'static/')
...
root@kvmubuntu:~# python /var/www/dato138it/manage.py collectstatic
root@kvmubuntu:~# google-chrome http://tipubuntu:80
Source:
# https://www.linuxtuto.com/how-to-install-django-with-apache-on-ubuntu-22-04/ - How to Install Django with Apache on Ubuntu 22.04.

2024-06-02 - None: Tele2, Иркутск. Должность: Инженер эксплуатации подсистемы базовых станций. Дополнительная информация: Обязанности - Обеспечивать эксплуатацию оборудования контроллеров и базовых станций стандартов 2G/3G/4G, Обеспечивать локальную поддержку работ по аварийному восстановлению работоспособности оборудования контроллеров, Поддерживать ввод в работу новых узлов контроллеров базовых станций, Предоставлять техническую поддержку региональным инженерам по эксплуатации базовых станций и транспортной сети, инженерам по эксплуатации коммутаторов по вопросам работы подсистемы базовых станций. Достижения: Разработал скрипт , который делает бэкап файлов конфигураций базовых станций.

Show

Цель:
# Подключиться к ftp и скопировать файлы в сетевую папку.
# Реализовать подключение и копирование файлов скриптом.
# Написать команды, которые заархивируют скопированные файлы и удалят файлы. Добавить в скрипт команды.
# Обработать все файлы с расширением .xml.
# Реализация бэкап файлов на языке PowerShell.
# Добавить раписание.
# Реализация бэкап файлов на языке Python.
Skills:
# Написание скриптов.
# Администрирование локальных, виртуальных и облачных серверов.
# Разработка бэкап файлов.
Task:
Написать скрипт, который копирует файлы с ftp://tipftp/Backups/tdir/MRBTS* в сетевую папку \\tdomain.ru\tdir каждую неделю ночью.
# Написание скриптов.
Decision:
PS C:\Windows\system32> vim mrbtsbackup.bat
PS C:\Windows\system32> cat mrbtsbackup.bat
rem xcopy C:\Users\tuser\Documents\scripts\new1\*.txt C:\Users\tuser\Documents\scripts\new2 /e
xcopy C:\Users\tuser\Documents\scripts\new1\test2.txt C:\Users\tuser\Documents\scripts\new2 /s
PS C:\Windows\system32> ls .\new1\
Каталог: C:\Users\tuser\Documents\scripts\new1
Mode     LastWriteTime   Length Name
----     -------------   ------ ----
d-----  05.09.2024 14:21    test
d-----  05.09.2024 14:18    test2
PS C:\Windows\system32> ls .\new1\test\
Каталог: C:\Users\tuser\Documents\scripts\new1\test
Mode     LastWriteTime   Length Name
----     -------------   ------ ----
d-----  05.09.2024 14:17    Новая папка
d-----  05.09.2024 14:18    Новая папка (2)
-a----  05.09.2024 14:00   3 test.txt
-a----  05.09.2024 14:00   3 test2.txt
Task:
Подключиться к ftp и скопировать файлы в сетевую папку.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
PS C:\Windows\system32> ftp
ftp> open tipftp
ftp> tuser
ftp> ls Backups/tdir
ftp> get /Backups/tdir/MRBTS2279.xml \\tdomain.ru\tdir\Test.txt
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for /Backups/tdir/MRBTS2279.xml (126410 bytes).
226 Transfer complete.
ftp: 126410 байт получено за 0.24 (сек) со скоростью 533.38 (КБ/сек).
Task:
Реализовать подключение и копирование файлов скриптом.
# Написание скриптов.
Decision:
PS C:\Windows\system32> vim mrbtsbackup.bat
PS C:\Windows\system32> cat mrbtsbackup.bat
@Echo Off
:: ~Параметры соединения
Set $Host=tipftp
Set $User=tuser
Set $Pass=tpassword
:: ~Что и куда копируем
SET $SRC=/Backups/tdir/MRBTS2279.xml
SET $DST=\\tdomain.ru\tdir\Test2.txt
:: ~Временные файлы
Set $FFtp=%~dpn0.cfg
:: Готовим CFG-файл
Echo.%$User%>"%$FFtp%"
Echo.%$Pass%>>"%$FFtp%"
Echo get "%$SRC%" "%$DST%">>"%$FFtp%"
Echo bye>>"%$FFtp%"
:: Выполняем команду
FTP -s:"%$FFtp%" %$Host%
rem exit
pause
Task:
Написать команды, которые заархивируют скопированные файлы и удалят файлы. Добавить в скрипт команды.
# Написание скриптов
Decision:
C:\WINDOWS\system32> "C:\Program Files\7-Zip\7z.exe" a -tzip \\tdomain.ru\tdir\Test.zip \\tdomain.ru\tdir\*.txt
C:\WINDOWS\system32> del \\tdomain.ru\tdir\*.txt
PS C:\Windows\system32> vim mrbtsbackup.bat
PS C:\Windows\system32> cat mrbtsbackup.bat
@Echo Off
:: ~Параметры соединения
Set $Host=tipftp
Set $User=tuser
Set $Pass=tpassword
:: ~Что и куда копируем
SET $SRC=/Backups/tdir/MRBTS2279.xml
SET $DST=\\tdomain.ru\tdir\Test2.txt
:: Формат текущей даты
SET dd=%date:~0,2%
SET mm=%date:~3,2%
SET yyyy=%date:~6,4%
SET curdate=%dd%_%mm%_%yyyy%
:: ~Временные файлы
Set $FFtp=%~dpn0.cfg
:: Готовим CFG-файл
Echo.%$User%>"%$FFtp%"
Echo.%$Pass%>>"%$FFtp%"
Echo get "%$SRC%" "%$DST%">>"%$FFtp%"
Echo bye>>"%$FFtp%"
:: Выполняем команду
FTP -s:"%$FFtp%" %$Host%
:: Добавим в архив скопированные файлы
"C:\Program Files\7-Zip\7z.exe" a -tzip \\tdomain.ru\tdir\Test_%curdate%.zip \\tdomain.ru\tdir\*.txt
:: Удалим лишние файлы
del \\tdomain.ru\tdir\*.txt
rem exit
pause
Task:
Обработать все файлы с расширением .xml.
# Написание скриптов.
Decision:
PS C:\Windows\system32> vim mrbtsbackup.bat
PS C:\Windows\system32> cat mrbtsbackup.bat
@Echo Off
:: ~Параметры соединения
Set server=tipftp
Set user=tuser
Set pass=tpassword
:: ~Что и куда копируем
:: SET $SRC=/Backups/tdir/*.xml
SET src=/Backups/tdir
SET dst=\\tdomain.ru\tdir\
:: Формат текущей даты
SET dd=%date:~0,2%
SET mm=%date:~3,2%
SET yyyy=%date:~6,4%
SET curdate=%dd%_%mm%_%yyyy%
:: ~Временные файлы
::Set $FFtp=%~dpn0.cfg
:: Готовим CFG-файл
Echo open %server%>tempfile.txt
Echo %user%>>tempfile.txt
Echo %pass%>>tempfile.txt
Echo lcd %dst%>>tempfile.txt
Echo cd %src%>>tempfile.txt
:: Echo mget *.* | Y>>tempfile.txt
Echo bye>>tempfile.txt
:: Выполняем команду
FTP -s:tempfile.txt
:: Добавим в архив скопированные файлы
"C:\Program Files\7-Zip\7z.exe" a -tzip \\tdomain.ru\tdir\Test_%curdate%.zip \\tdomain.ru\tdir\*.xml
:: Удалим лишние файлы
del \\tdomain.ru\tdir\*.txt
rem exit
pause
Task:
Реализация бэкап файлов на языке PowerShell.
# Написание скриптов
Decision:
PS C:\Windows\system32> Get-Date -Format "_MM_dd_yyyy_HH_mm"
_09_09_2024_09_23
PS C:\Windows\system32> vim mrbtsbackup.ps1
PS C:\Windows\system32> cat mrbtsbackup.ps1
# Переменные
$ftpServer = "ftp://tipftp/Backups/tdir/"
$ftpUser = "tuser"
$ftpPassword = "tpassword"
$localFolder = "\\tdomain.ru\tdir\"
$curdate = Get-Date -Format "_MM_dd_yyyy_HH_mm"
#Write-Output $curdate
# Создание объекта для загрузки
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
# Получение списка файлов на FTP-сервере
$ftpRequest = [System.Net.FtpWebRequest]::Create($ftpServer)
$ftpRequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory
$ftpRequest.Credentials = $webclient.Credentials
$response = $ftpRequest.GetResponse()
$streamReader = New-Object System.IO.StreamReader($response.GetResponseStream())
# Чтение имен файлов
$files = @()
while($file = $streamReader.ReadLine()) {
$files += $file
}
$streamReader.Close()
$response.Close()
#Write-Output $files
# Скачивание файлов
foreach ($file in $files) {
$remoteFile = "$ftpServer/$file"
$localFile = Join-Path $localFolder $file
$webclient.DownloadFile($remoteFile, $localFile)
}
# Завершение работы
$webclient.Dispose()
$compress = @{
Path = "\\tdomain.ru\tdir\*.xml"
CompressionLevel = "Fastest"
DestinationPath = "\\tdomain.ru\tdir\IRK$curdate.zip"
}
Compress-Archive @compress
Remove-Item \\tdomain.ru\tdir\*.xml
PS C:\Windows\system32> powershell -file mrbtsbackup.ps1
PS C:\Windows\system32> ls \\tdomain.ru\tdir\
Task:
Добавить раписание.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
- Планировщик задач - Создать задачу - Имя - Backup MTBTS - +Выполнить для всех пользователей - Триггеры - +Еженедельно - Начать - суббота - 23:00 - ок - действия - запуск программы - Программа или сценарий - powershell.exe - добавить аргументы
-file "C:\Users\tuser\Documents\scripts\backups\mrbtsbackup.ps1"
- ок
Source:
# https://comp-security.net/%D0%BA%D0%B0%D0%BA-%D1%81%D0%BA%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C-%D1%84%D0%B0%D0%B9%D0%BB-%D0%BF%D0%B0%D0%BF%D0%BA%D1%83-%D0%B2-cmd/ - Как скопировать файл (папку) в командной строке Windows.
# https://pc.ru/articles/kak-dobavit-kommentarii-v-bat-fajl - Как добавить комментарии в bat-файл.
# https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.utility/get-date?view=powershell-7.4 - Get-Date.
# https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.management/remove-item?view=powershell-7.4 - Remove-Item.
# https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.utility/write-output?view=powershell-7.4 - Write-Output.
# https://docs.oracle.com/cd/E19120-01/open.solaris/819-1634/remotehowtoaccess-87541/index.html - How to Copy Files From a Remote System (ftp).
# http://forum.oszone.net/post-2962057.html - Powershell. Копировать файлы в новую dir, созданную с именем текущей даты.
# https://windowsnotes.ru/powershell-2/zapusk-powershell-skripta-po-raspisaniyu/ - Способ 1.
# https://stackoverflow.com/questions/18180060/how-to-zip-a-file-using-cmd-line - How to zip a file using cmd line?
# https://learn.microsoft.com/ru-ru/windows-server/administration/windows-commands/del - del.
# https://www.dmosk.ru/miniinstruktions.php?mini=7zip-cmd - Резервное копирование с помощью 7-Zip.
Task:
Реализация бэкап файлов на языке Python.
# Разработка бэкап файлов.
Decision:
PS C:\Windows\system32> vim .\tpy.py
PS C:\Windows\system32> cat .\tpy.py
from ftplib import FTP
ftp = FTP("tipftp")
ftp.login(user="tuser", passwd="tpassword")
local_file = '/Users/tuser/Documents/py/test/testfile.txt'
#files = ftp.nlst('/Backups/tdir/*.xml')
files = ftp.nlst('/Backups/tdir/MRBTS881364.xml')
#print(files)
for file in files:
print(file)
file_list=[]
file_list.append(file)
file_list1=str(file_list)
print(file_list1)
with open(local_file, 'wb') as tempfile:
  #print("Работа с файлом testfile")
  #ftp.retrbinary('RETR' + file_list1, tempfile.write)
  ftp.retrbinary('retr ' + file, tempfile.write)
ftp.quit()
Source:
# https://sky.pro/media/rabota-s-fajlami-v-python-kak-poluchit-spisok-vseh-fajlov-v-direktorii/
# https://docs.python.org/3/library/ftplib.html
# https://sky.pro/media/kak-ispolzovat-python-dlya-raboty-s-ftp/
# https://pythonworld.ru/tipy-dannyx-v-python/spiski-list-funkcii-i-metody-spiskov.html
# https://dvmn.org/encyclopedia/python_intermediate/python_lists/
# https://metanit.com/python/tutorial/4.1.php
# https://lavrynenko.com/python-ftplib-skachat-fajl/
Task:
Реализовать программу на Python.
# Разработка бэкап файлов.
Decision:
PS C:\Users\tuser\Documents\py> vim .\mrbtsbackup.py
PS C:\Users\tuser\Documents\py> cat .\mrbtsbackup.py
from ftplib import FTP
import os
import zipfile
import datetime
ftp = FTP("tipftp")
ftp.login(user="tuser", passwd="tpassword")
files = ftp.nlst('/Backups/tdir/')
local_dir = '//tdomain.ru/tdir/MRBTS/'
xml_files_ftp = [file for file in files if file.endswith('.xml')]
#print(xml_files_ftp)
'''
for file in xml_files_ftp:
  #print(file)
  local_file = os.path.join(local_dir, os.path.basename(file))
  #print(local_file)
  with open(local_file, 'wb') as tempfile:
    ftp.retrbinary(f'RETR {file}', tempfile.write)    
ftp.quit()
'''
'''
archive = zipfile.ZipFile('//tdomain.ru/tdir/MRBTS/IRK{}_{}.zip'.format('', datetime.date.today().strftime("%d-%m-%y")), 'w')
for loc_dir, loc_subdir, files_local in os.walk(local_dir):
  #print(loc_dir)
  for all_files_local in files_local:
    #print(all_files_local)
    if all_files_local.endswith('.xml'):
      xml_files_local=os.path.join(loc_dir, all_files_local)
      archive.write(xml_files_local, os.path.relpath(xml_files_local, local_dir), compress_type = zipfile.ZIP_DEFLATED)      
      #print(xml_files_local)
      #os.remove(xml_files_local)
archive.close()
'''
archive = zipfile.ZipFile('//tdomain.ru/tdir/MRBTS/IRK{}_{}.zip'.format('', datetime.date.today().strftime("%d-%m-%y")), 'w')
for file in xml_files_ftp:
  local_file = os.path.join(local_dir, os.path.basename(file))
  with open(local_file, 'wb') as tempfile:
    ftp.retrbinary(f'RETR {file}', tempfile.write)
  for loc_dir, loc_subdir, files_local in os.walk(local_dir):
    #print(loc_dir)
    for all_files_local in files_local:
      #print(all_files_local)
      if all_files_local.endswith('.xml'):
        xml_files_local=os.path.join(loc_dir, all_files_local)
        archive.write(xml_files_local, os.path.relpath(xml_files_local, local_dir), compress_type = zipfile.ZIP_DEFLATED)
        #print(xml_files_local)
        os.remove(xml_files_local)
archive.close()
ftp.quit()
PS C:\Users\tuser\Documents\py> python .\mrbtsbackup.py
Source:
# https://sky.pro/media/rabota-s-fajlami-v-python-kak-poluchit-spisok-vseh-fajlov-v-direktorii/ - Получение списка файлов в директории
# https://docs.python.org/3/library/ftplib.html
# https://sky.pro/media/kak-ispolzovat-python-dlya-raboty-s-ftp/
# https://pythonworld.ru/tipy-dannyx-v-python/spiski-list-funkcii-i-metody-spiskov.html - Таблица "методы списков".
# https://dvmn.org/encyclopedia/python_intermediate/python_lists/ - Как добавить элемент в список.
# https://metanit.com/python/tutorial/4.1.php - Открытие и закрытие файла.
# https://lavrynenko.com/python-ftplib-skachat-fajl/ - Python ftplib скачать файл.
# https://sky.pro/media/sliyanie-spiskov-v-python-metod-join/ - Простой пример использования join().
# https://sky.pro/media/zapis-stroki-s-peremennoj-v-tekstovyj-fajl-v-python/ - Запись строки с переменной в текстовый файл в Python.
# https://ru.stackoverflow.com/questions/1178149/%D0%9A%D0%B0%D0%BA-%D0%B4%D0%BE%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C-%D1%84%D0%B0%D0%B9%D0%BB-%D0%B2-python - Как дозаписать файл в python.
# https://metanit.com/python/tutorial/4.7.php - Запись файлов в архив.
# https://code.tutsplus.com/ru/compressing-and-extracting-files-in-python--cms-26816t - Сжатие Нескольких Файлов.
# https://sky.pro/media/udalenie-fajla-ili-papki-v-python/ - Удаление файла.
# https://qna.habr.com/q/896993 - Как создавать файлы с датой в название?
# https://habr.com/ru/articles/537774/ - Скачивание файла.