12.5 Izvietošana mākonī un drošība
Stundas uzdevums: Izvietot lietotni publiski un saprast pamata drošības principus produkcijas vidē.
70 min plāns: Teorija un paraugs (~10 min) · 1. uzdevums (~15 min) - izņem noslēpumus no koda · 2. uzdevums (~25 min) - sagatavo projektu izvietošanai · 3. uzdevums (~20 min) - izvieto mākonī un pārbaudi. Papildu uzdevumu sāc tikai tad, ja pārējie trīs ir gatavi.
Pirms sāc: atver savu Flask projektu un GitHub krātuvi. Brīdinājums: paroli vai atslēgu, kas vienreiz nonākusi GitHub, uzskata par nopludinātu - tā jāmaina.
Teorija: No localhost uz internetu
Lokālā lietotne, kas darbojas uz localhost:5000, nav pieejama citiem. Lai pasaule to redzētu, jāizvieto mākonī.
Bezmaksas mākoņi (2026):
- Render (render.com) - bezmaksas Python apps, viegls process.
- Railway (railway.app) - bezmaksas $5 kredīts mēnesī.
- Fly.io - bezmaksas mazām aplikācijām.
- Vercel/Netlify - tikai statiskām lapām un serverless funkcijām.
Drošības pamati:
- Vides mainīgie - paroles, API atslēgas NEKAD kodā. Lieto
.env. - HTTPS - vienmēr produkcijā (Render to dod automātiski).
- Validē visus ievadus - pārlūkā IR un servera pusē.
- Lietotāja paroles - bcrypt hash, NEKAD plain text.
- SQL injekcija - vienmēr parametrizēti vaicājumi.
import os
from dotenv import load_dotenv
load_dotenv() # nolasa .env failu
DATABASE_URL = os.getenv("DATABASE_URL")
SECRET_KEY = os.getenv("SECRET_KEY")
Praktiskie uzdevumi
1. uzdevums -
Izņem noslēpumus no koda
Beigās tavā kodā vairs nebūs nevienas paroles.
- Atrodi kodā visas vietas, kur ierakstīta parole vai atslēga.
- Aizstāj katru ar
os.environ.get("DATABASE_URL"). - Pievieno faila augšā
import os. - Izveido failu
.envar īstajām vērtībām. - Pievieno
.gitignorefailam rindu.env. - Izpildi
git statusun pārliecinies, ka.envtur nav.
Gatavs, kad: git status nerāda .env, un kodā nav neviena ierakstīta paroles teksta.
2. uzdevums -
Sagatavo projektu izvietošanai
Beigās serveris varēs palaist tavu lietotni bez tavas palīdzības.
- Izpildi
pip freeze > requirements.txt. - Pievieno
pip install gunicornun atjaunorequirements.txt. - Nomaini
app.run(debug=True)uzapp.run(debug=False). - Pieraksti, kāpēc
debug=Truepubliskā serverī ir bīstams. - Izveido failu
Procfilear rinduweb: gunicorn app:app. - Veic commit un push uz GitHub.
Gatavs, kad: krātuvē ir requirements.txt un Procfile, bet nav .env.
3. uzdevums -
Izvieto mākonī un pārbaudi
Beigās tava lietotne būs pieejama no jebkura datora.
- Izveido kontu vietnē
render.com. - Izvēlies New -> Web Service un pieslēdz savu GitHub krātuvi.
- Ievadi vides mainīgos Render sadaļā Environment.
- Palaid izvietošanu un vēro žurnālu, līdz parādās
Live. - Atver iegūto adresi telefonā, nevis savā datorā.
- Pārbaudi, ka API atgriež datus arī no turienes.
- Pieraksti vienu secinājumu: ar ko
localhostatšķiras no publiskas adreses.
Gatavs, kad: lapa atveras telefonā pa mobilo internetu, nevis tikai tavā datorā.
Papildu uzdevums - Pievieno pieprasījumu ierobežojumu
Ja pamatdarbs ir gatavs, pasargā API no pārslodzes.
- Instalē
pip install flask-limiter. - Pievieno ierobežojumu, piemēram 30 pieprasījumi minūtē.
- Izsauc API ciklā vairāk reižu, nekā atļauts.
- Pieraksti, kāds statusa kods atnāca.
- Pieraksti, kāpēc šāds ierobežojums ir vajadzīgs.
Gatavs, kad: pārsniedzot ierobežojumu, serveris atgriež kodu 429, nevis apstājas.
Biežākās kļūdas
- .env failā ielikts GitHub: Slepenās atslēgas tagad publiski. Atsauc tās un izveido jaunas. Nekad neielādē
.env! - App nedarbojas mākonī, bet darbojās lokāli: Pārbaudi vides mainīgos un Python versiju (
runtime.txt). - DB savienojuma kļūdas: Pārliecinies, ka
DATABASE_URLizmanto SSL un pareizu psycopg2 sintakse (Render dod URL sākot arpostgres://- pielāgo uzpostgresql://).
Koda piemērs
# app.py - produkcijai gatavs
import os
from flask import Flask, jsonify, request
import psycopg2
app = Flask(__name__)
DATABASE_URL = os.environ.get("DATABASE_URL")
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-key")
def get_db():
return psycopg2.connect(DATABASE_URL, sslmode="require")
@app.route("/api/rezultati")
def saraksts():
with get_db() as conn, conn.cursor() as cur:
cur.execute("SELECT vards, punkti FROM rezultati ORDER BY punkti DESC LIMIT 10")
return jsonify([dict(zip(["vards","punkti"], r)) for r in cur.fetchall()])
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
API atbild ar JSON;
Datu bāze pieslēgta.