Финальный проект курса «Сетевые системы и приложения»
Практическое задание: развернуть и сопровождать Ubuntu Server
как Linux System Administrator в компании TechCorp
| Цель | Развернуть и сопровождать production-like сервер TechCorp |
| Результат | Ubuntu Server + TCP Echo + Docker + Nginx + Flask + UFW + cron |
| Отчёт | 8 скриншотов + PDF с пояснениями |
| Срок | 1 неделя |
- Создайте VM Ubuntu Server 24.04 в VirtualBox (NAT + Host-only).
- Выполняйте Части 1 → 8 по порядку, отмечая прогресс ниже.
| Метка | Назначение |
|---|---|
| ТЕОРИЯ | Зачем это нужно sysadmin-у |
| ШАГ N.M | Конкретное действие |
| ФАЙЛ | Код (нажмите, чтобы развернуть) |
| ПОДСКАЗКА | Типичные ошибки |
| ПРОВЕРКА | Чеклист перед переходом дальше |
| СКРИНШОТ | Что сдать в отчёт |
Выполняйте части 1 → 8 строго по порядку.
Ошибка? → Устранение неполадок
- Часть 1 Сеть и SSH · Скриншот №1
- Часть 2 Пользователи и права · Скриншот №2
- Часть 3 TCP Echo Server · Скриншот №3
- Часть 4 Мониторинг · Скриншот №4
- Часть 5 Docker · Скриншот №5
- Часть 6 Nginx и Flask · Скриншот №6
- Часть 7 Безопасность · Скриншот №7
- Часть 8 Логи и cron · Скриншот №8
flowchart LR
subgraph pc [Windows PC]
SSH_Client[SSH Client]
end
subgraph vm [Ubuntu 192.168.56.10]
SSH[":22 SSH"]
NGX[Nginx]
DKR[Docker]
TCP["tcp-server :5000"]
WEB["flask-server :5002"]
UFW[UFW Firewall]
end
SSH_Client -->|SSH| SSH
SSH_Client -->|TCP :5001| UFW
SSH_Client -->|HTTP :8080| UFW
UFW --> NGX
NGX -->|stream| TCP
NGX -->|http| WEB
DKR --- TCP
DKR --- WEB
| Порт | Сервис | Снаружи |
|---|---|---|
| 22 | SSH | да |
| 5000 | TCP Echo (Docker) | нет |
| 5001 | Nginx → TCP Echo | да |
| 5002 | Flask (Docker) | нет |
| 8080 | Nginx → Flask | да |
На порту 5000 только один процесс (Python или Docker).
/opt/tcp_echo/
├── Server_Socket/server.py
├── Client_Socket/client.py
├── flask_app/app.py
├── flask_app/requirements.txt
├── scripts/status.sh, backup.sh
├── logs/ config/ backups/
├── .env
├── Dockerfile
└── Dockerfile.flask
| Параметр | Значение |
|---|---|
| RAM | 4 GB · CPU 2 · Disk 200 GB |
| Сеть 1 | NAT (интернет) |
| Сеть 2 | Host-only (Windows ↔ VM) |
| Hostname | techcorp-srv01 |
| Пользователь | admin |
При установке Ubuntu включите OpenSSH server.
# Задать имя сервера (видно в логах и status.sh)
sudo hostnamectl set-hostname techcorp-srv01
# Проверить версии инструментов
python3 --version # нужен Python 3.10+
git --versionСкриншот №1
ip addr show enp0s8+systemctl status ssh
| Компонент | Назначение |
|---|---|
enp0s3 (NAT) |
Интернет обновления, Docker pull |
enp0s8 (Host-only) |
Статический IP 192.168.56.10 доступ с Windows |
| SSH (:22) | Удалённое администрирование |
# link состояние интерфейса (UP/DOWN)
ip link show
# addr назначенные IP-адреса
ip addr show# Host-only адаптер должен быть UP до применения netplan
sudo ip link set enp0s8 up
sudo nano /etc/netplan/00-installer-config.yamlФАЙЛ · /etc/netplan/00-installer-config.yaml
network:
version: 2
renderer: networkd
ethernets:
enp0s8:
dhcp4: no # статический IP, не DHCP
addresses: [192.168.56.10/24] # /24 = маска 255.255.255.0sudo netplan apply
# Проверка: IP 192.168.56.10, state UP
ip addr show enp0s8
# Связь с Windows-хостом VirtualBox
ping -c 3 192.168.56.1
# Интернет через NAT-адаптер enp0s3
ping -c 3 8.8.8.8sudo apt update && sudo apt upgrade -y
sudo apt install openssh-server -y
# enable автозапуск SSH при загрузке
sudo systemctl enable ssh
sudo systemctl status ssh# Подключение с Windows PowerShell
ssh admin@192.168.56.10ПОДСКАЗКА
enp0s8DOWN →sudo ip link set enp0s8 upдо netplan apply- ping fail → VirtualBox → Host Network Manager
- IP Windows →
ipconfig(обычно192.168.56.1)
- IP
192.168.56.10, интерфейс UP - ping хоста и
8.8.8.8успешен - SSH active, вход с Windows работает
Скриншот №2
groups alex+ls -la /opt/tcp_echo
| Понятие | Смысл |
|---|---|
| User | Личный аккаунт сотрудника |
| Group | Общие права (devops, developers) |
chmod 750 |
rwx для владельца и группы, остальным нет |
chmod 700 |
Только владелец (логи, секреты) |
| sudo | Выполнение от root с ограничениями |
sudo groupadd devops
sudo groupadd developers
# -m создаёт /home, -G добавляет в группу, -s задаёт shell
sudo useradd -m -G devops -s /bin/bash alex
sudo useradd -m -G developers -s /bin/bash maria
sudo useradd -m -s /bin/bash john
sudo passwd alex
sudo passwd maria
sudo passwd john
groups alex # ожидаем: alex devops
groups maria # ожидаем: maria developers# visudo проверяет синтаксис безопаснее прямого редактирования
sudo visudo -f /etc/sudoers.d/devopsФАЙЛ · /etc/sudoers.d/devops
# Группа devops: перезапуск служб и скрипты без пароля root
%devops ALL=(ALL) NOPASSWD: /bin/systemctl restart tcp-echo, /bin/systemctl status tcp-echo, /opt/tcp_echo/scripts/*
sudo mkdir -p /opt/tcp_echo
sudo chown alex:devops /opt/tcp_echo
sudo chmod 750 /opt/tcp_echo
sudo su - alex
cd /opt/tcp_echo
mkdir -p Server_Socket Client_Socket flask_app logs config scripts backups
chmod 755 Server_Socket Client_Socket # приложение: все могут читать/входить
chmod 700 logs config # логи и конфиг: только владелец
chmod 750 scripts backups # скрипты: владелец + группа
ls -la
# 1-й столбец = права, 3-й = владелец, 4-й = группаПОДСКАЗКА дальше работайте как пользователь alex.
- 4 пользователя: admin, alex, maria, john
- alex ∈ devops;
/opt/tcp_echo= 750,logs/= 700
Скриншот №3 server + client терминалы
Клиент ──connect()──► Сервер
◄──Echo: msg── bind → listen → accept → thread
nano Server_Socket/server.pyФАЙЛ · Server_Socket/server.py
#!/usr/bin/env python3
"""
TechCorp TCP Echo Server.
Многопоточный сервер: каждый клиент отдельный поток.
"""
import os
import socket
import threading
# PORT из переменной окружения (.env) или 5000 по умолчанию
PORT = int(os.environ.get("SERVER_PORT", 5000))
HOST = "0.0.0.0" # слушать на всех интерфейсах (NAT + host-only)
clients = []
clients_lock = threading.Lock() # защита списка clients от race condition
def handle_client(conn, addr):
"""Обработка одного TCP-клиента."""
with clients_lock:
clients.append(conn)
print(f"[NEW] {addr} connected")
try:
while True:
msg = conn.recv(1024).decode("utf-8")
if not msg:
break # клиент закрыл соединение
print(f"[{addr}] {msg}")
# Команда graceful shutdown (для тестирования)
if msg.lower().strip() == "baka":
conn.send("Bye! See you soon.\n".encode())
break
conn.send(f"Echo: {msg}".encode())
finally:
with clients_lock:
if conn in clients:
clients.remove(conn)
conn.close()
print(f"[CLOSED] {addr}")
# --- Инициализация сервера ---
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # быстрый restart порта
server.bind((HOST, PORT))
server.listen(10) # очередь до 10 pending-подключений
print(f"[STARTING] Server on {HOST}:{PORT}")
while True:
conn, addr = server.accept()
# daemon=True поток завершится при остановке main
threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()ФАЙЛ · Client_Socket/client.py
#!/usr/bin/env python3
"""TechCorp TCP Echo Client."""
import socket
import sys
# IP из аргумента или сервер по умолчанию
SERVER_IP = sys.argv[1] if len(sys.argv) > 1 else "192.168.56.10"
SERVER_PORT = 5000
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((SERVER_IP, SERVER_PORT))
print(f"[CONNECTED] to {SERVER_IP}:{SERVER_PORT}")
while True:
msg = input("Send: ").strip()
if not msg:
continue
client.send(msg.encode())
reply = client.recv(1024).decode()
print(f"Server: {reply}")
if msg.lower() == "baka":
break
client.close()
print("[CLOSED]")chmod +x Server_Socket/server.py Client_Socket/client.py
# Терминал 1 запуск сервера
python3 Server_Socket/server.py
# Терминал 2 клиент (с VM или Windows)
python3 Client_Socket/client.py 192.168.56.10Ожидаемый результат:
Send: hello → Server: Echo: hello
Send: baka → Server: Bye! See you soon.
- Echo работает;
bakaзакрывает сессию
Скриншот №4
free -h+ss -tlnp | grep 5000
| Вопрос | Команда |
|---|---|
| Сервис жив? | ps, pgrep, ss |
| Хватает RAM? | free -h |
| Хватает disk? | df -h |
| Нагрузка CPU? | uptime |
uptime # load average: 1/5/15 мин
free -h # total/used/available RAM
df -h /opt # занятость диска (тревога если >80%)
ps aux | grep server.py # процесс в списке всех процессов
pgrep -af server.py # PID + полная командная строка
# -t TCP, -l LISTEN, -n numeric, -p process name
ss -tlnp | grep 5000ПОДСКАЗКА
ssзаменяет устаревшийnetstat. Без-pиспользуйтеsudo.
- Процесс найден; порт 5000 в состоянии LISTEN
Скриншот №5
docker ps+docker logs tcp-server
Docker изолирует приложение с зависимостями.
--restart unless-stopped контейнер стартует после reboot сервера.
pkill -f server.py
ss -tlnp | grep 5000 # вывод пуст порт свободенsudo apt install docker.io -y
sudo usermod -aG docker alex # alex может запускать docker без sudo
sudo systemctl enable docker
sudo systemctl start docker
# Перелогин обязателен для применения группы docker
exit && exit
ssh [email protected]
sudo su - alexФАЙЛ · Dockerfile
# Базовый образ с Python (минимальный размер)
FROM python:3.11-slim
WORKDIR /app
# Копируем только server.py зависимостей нет (stdlib)
COPY Server_Socket/server.py .
ENV SERVER_PORT=5000
EXPOSE 5000
CMD ["python3", "server.py"]cd /opt/tcp_echo
docker build -t tcp-echo .
docker run -d \
--name tcp-server \
--restart unless-stopped \
-p 5000:5000 \
tcp-echo
docker ps
docker logs tcp-server
# Тест: клиент подключается к localhost (контейнер на той же VM)
python3 Client_Socket/client.py 127.0.0.1- STATUS Up; клиент подключается
Скриншот №6
nc 127.0.0.1 5001+curl :8080/hello
Клиент → Nginx :5001 → tcp-server :5000 (TCP stream)
Клиент → Nginx :8080 → flask-server :5002 (HTTP)
Backend не exposed наружу только через Nginx.
sudo apt install nginx netcat-openbsd -y
sudo systemctl enable nginx
sudo nano /etc/nginx/nginx.confФАЙЛ · блок в /etc/nginx/nginx.conf (вне http)
# TCP proxy (L4) проксирует сырой TCP-трафик
stream {
upstream tcp_echo_backend {
server 127.0.0.1:5000; # Docker tcp-server
}
server {
listen 5001; # внешний порт для клиентов
proxy_pass tcp_echo_backend;
proxy_timeout 30s;
}
}sudo nginx -t # проверка синтаксиса всегда перед reload!
sudo systemctl reload nginx
echo "test" | nc -w 3 127.0.0.1 5001
# Ожидаем: Echo: testФАЙЛ · flask_app/app.py
#!/usr/bin/env python3
"""TechCorp Flask status app."""
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "<h1>TechCorp Flask App</h1>"
@app.route("/hello")
def hello():
return "Hello from Flask!"
@app.route("/api/status")
def status():
# JSON для мониторинга (curl / health-check)
return '{"status":"running","service":"techcorp-flask"}'
if __name__ == "__main__":
# 0.0.0.0 доступ из контейнера и через Nginx
app.run(host="0.0.0.0", port=5000)ФАЙЛ · flask_app/requirements.txt
flask==3.0.0
ФАЙЛ · Dockerfile.flask
FROM python:3.11-slim
WORKDIR /app
COPY flask_app/app.py flask_app/requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["python3", "app.py"]ФАЙЛ · /etc/nginx/conf.d/flask.conf
server {
listen 8080; # внешний HTTP-порт
location / {
proxy_pass http://127.0.0.1:5002; # flask-server контейнер
proxy_set_header Host $host;
}
}docker build -f Dockerfile.flask -t flask-app .
docker run -d \
--name flask-server \
--restart unless-stopped \
-p 5002:5000 \
flask-app
sudo nginx -t && sudo systemctl reload nginx
curl http://127.0.0.1:8080/hello
curl http://127.0.0.1:8080/api/status
docker ps- nc :5001 → Echo; curl :8080 →
Hello from Flask!
Скриншот №7
ufw status verbose+./scripts/status.sh
| Уровень | Инструмент |
|---|---|
| Секреты | .env с правами 600 |
| Сеть | UFW deny all, allow нужные порты |
| Операции | bash-скрипт status.sh |
cd /opt/tcp_echo
nano .envФАЙЛ · .env
# Конфигурация TCP Echo (не коммитить в git с секретами!)
SERVER_PORT=5000
LOG_LEVEL=INFO
ADMIN_EMAIL=[email protected]chmod 600 .env # только владелец читает/пишет
ls -la .env # ожидаем: -rw-------ФАЙЛ · scripts/status.sh
#!/bin/bash
# TechCorp ежедневный health-check сервера
echo "========================================"
echo " Host: $(hostname) | $(date)"
echo "========================================"
echo ""
echo "=== Uptime & Load ==="
uptime
echo ""
echo "=== Memory ==="
free -h
echo ""
echo "=== Disk ==="
df -h /opt /var/log
echo ""
echo "=== Ports (22 SSH, 5000/5001 TCP, 8080 HTTP) ==="
ss -tlnp | grep -E '22|5000|5001|8080' || echo "No matching ports"
echo ""
echo "=== Docker containers ==="
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null \
|| echo "Docker not available"
echo ""
echo "=== systemd services ==="
for svc in ssh nginx docker; do
printf " %-10s %s\n" "$svc" "$(systemctl is-active $svc 2>/dev/null || echo unknown)"
donechmod +x scripts/status.sh
./scripts/status.shsudo apt install ufw -y
sudo ufw default deny incoming # блокировать все входящие
sudo ufw default allow outgoing # исходящие разрешить
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 5001/tcp comment 'TCP Echo via Nginx'
sudo ufw allow 8080/tcp comment 'Flask via Nginx'
sudo ufw enable
sudo ufw status verboseВнимание:
allow 22доenableиначе потеряете SSH.
Дополнительно · systemd unit (альтернатива Docker для TCP)
Используйте либо Docker, либо systemd на порту 5000 не оба.
docker stop tcp-server
sudo nano /etc/systemd/system/tcp-echo.service[Unit]
Description=TechCorp TCP Echo Server
After=network.target
[Service]
Type=simple
User=alex
WorkingDirectory=/opt/tcp_echo
EnvironmentFile=/opt/tcp_echo/.env
ExecStart=/usr/bin/python3 /opt/tcp_echo/Server_Socket/server.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now tcp-echo
sudo systemctl status tcp-echo-
.env= 600; status.sh OK; UFW allow 22/5001/8080
Скриншот №8
crontab -l+ финальная проверка
| Инструмент | Назначение |
|---|---|
docker logs |
Логи контейнера |
journalctl |
Системный журнал systemd |
| cron | Задачи по расписанию |
| backup.sh | Резервное копирование конфигурации |
docker logs tcp-server --tail 20
docker logs flask-server --tail 10
# Логи nginx и ssh из systemd journal
sudo journalctl -u nginx --no-pager -n 15
sudo journalctl -u ssh --no-pager -n 10ФАЙЛ · scripts/backup.sh
#!/bin/bash
# TechCorp резервная копия конфигурации и кода
BACKUP_DIR="/opt/tcp_echo/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/backup_${DATE}.tar.gz" \
-C /opt/tcp_echo \
Server_Socket Client_Socket scripts .env config \
2>/dev/null
# Удалить архивы старше 7 дней (ротация)
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
echo "[OK] Backup created: backup_${DATE}.tar.gz"
ls -lh "$BACKUP_DIR" | tail -3chmod +x scripts/backup.sh
./scripts/backup.shcrontab -eФАЙЛ · crontab пользователя alex
# Health-check каждые 15 минут
*/15 * * * * /opt/tcp_echo/scripts/status.sh >> /opt/tcp_echo/logs/health.log 2>&1
# Backup каждый день в 02:00
0 2 * * * /opt/tcp_echo/scripts/backup.sh >> /opt/tcp_echo/logs/backup.log 2>&1crontab -lecho "========== FINAL CHECK =========="
./scripts/status.sh
ping -c 2 192.168.56.1
echo "ok" | nc -w 3 127.0.0.1 5001
curl -sf http://127.0.0.1:8080/hello
docker ps
sudo ufw status | head -10
crontab -l
echo "========== ALL DONE =========="- Backup создан; cron 2 задачи; все команды OK
| № | Тема | Содержание |
|---|---|---|
| 1 | Сеть + SSH | ip addr show enp0s8 · systemctl status ssh |
| 2 | Users + права | groups alex · ls -la /opt/tcp_echo |
| 3 | TCP Echo | server + client терминалы |
| 4 | Мониторинг | free -h · ss -tlnp | grep 5000 |
| 5 | Docker | docker ps · docker logs tcp-server |
| 6 | Nginx + Flask | nc 127.0.0.1 5001 · curl :8080/hello |
| 7 | UFW + status | ufw status verbose · ./scripts/status.sh |
| 8 | Cron + финал | crontab -l · FINAL CHECK |
| # | Артефакт |
|---|---|
| 1 | PDF-отчёт с 8 скриншотами |
| 2 | 2–3 предложения к каждому скриншоту |
| 3 | Теория: модель клиент–сервер и роль systemd (5–7 предложений) |
| 4 | Одна проблема при выполнении и её решение |
Сеть и SSH
| Симптом | Решение |
|---|---|
| enp0s8 DOWN | sudo ip link set enp0s8 up && sudo netplan apply |
| ping fail | VirtualBox → Host Network Manager |
| SSH refused | sudo systemctl start ssh |
Порты и сервисы
| Симптом | Решение |
|---|---|
| Connection refused | docker start tcp-server или python3 Server_Socket/server.py |
| Address in use | pkill -f server.py или docker stop tcp-server |
| nginx error | sudo nginx -t → исправить → sudo systemctl reload nginx |
| nc not found | sudo apt install netcat-openbsd |
Docker и UFW
| Симптом | Решение |
|---|---|
| permission denied | sudo usermod -aG docker alex → перелогин |
| UFW locked out | Консоль VM: sudo ufw allow 22 |
# === Утро: health-check ===
ssh [email protected]
/opt/tcp_echo/scripts/status.sh
# === Инцидент: диагностика ===
sudo journalctl -u nginx -n 30
docker logs tcp-server --tail 30
ss -tlnp | grep -E '5000|5001|8080'
# === Перезапуск сервисов ===
docker restart tcp-server flask-server
sudo systemctl restart nginx- Ubuntu Server: Netplan, SSH, users, sudo, UFW
- Мониторинг: ps, ss, free, df, journalctl, docker logs
- Docker: build, run, restart policies
- Nginx: stream (TCP) + HTTP reverse proxy
- Автоматизация: bash, cron, backup
- Python: TCP sockets, threading
Сетевые системы и приложения
М. Коротеев