Π Π΄Π°Π½Π½ΠΎΠΉ ΡΡΠ°ΡΡΠ΅ Ρ
ΠΎΡΡ ΠΏΠΎΠ΄Π΅Π»ΠΈΡΡΡ ΠΎΠΏΡΡΠΎΠΌ ΡΠ°Π±ΠΎΡΡ Ρ NJS, ΠΈΠ½ΡΠ΅ΡΠΏΡΠ΅ΡΠ°ΡΠΎΡΠ° JavaScript Π΄Π»Ρ Nginx ΡΠ°Π·ΡΠ°Π±Π°ΡΡΠ²Π°Π΅ΠΌΠΎΠ³ΠΎ Π² ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Nginx Inc, ΠΎΠΏΠΈΡΠ°Π² Π½Π° ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ Π΅Π³ΠΎ ΠΎΡΠ½ΠΎΠ²Π½ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ. NJS ΡΡΠΎ ΠΏΠΎΠ΄ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ Π―Π JavaScript, ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΡΠ°ΡΡΠΈΡΠΈΡΡ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡ Nginx. ΠΠ° Π²ΠΎΠΏΡΠΎΡ
A long time ago…
ΠΠ° ΠΏΡΠΎΡΠ»ΠΎΠΉ ΡΠ°Π±ΠΎΡΠ΅ Π² Π½Π°ΡΠ»Π΅Π΄ΡΡΠ²ΠΎ ΠΌΠ½Π΅ Π΄ΠΎΡΡΠ°Π»ΡΡ gitlab Ρ Π½Π΅ΠΊΠΎΡΠΎΡΡΠΌ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎΠΌ ΡΠ°Π·Π½ΠΎΡΠ΅ΡΡΡΡΡ
CI/CD-ΠΏΠ°ΠΉΠΏΠ»Π°ΠΉΠ½ΠΎΠ² Ρ docker-compose, dind ΠΈ ΠΏΡΠΎΡΠΈΠΌΠΈ ΠΏΡΠ΅Π»Π΅ΡΡΡΠΌΠΈ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ»ΠΈ ΠΏΠ΅ΡΠ΅Π²Π΅Π΄Π΅Π½Ρ Π½Π° ΡΠ΅Π»ΡΡΡ kaniko. ΠΠ±ΡΠ°Π·Ρ ΠΆΠ΅, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π»ΠΈΡΡ ΡΠ°Π½Π΅Π΅ Π² CI, ΠΏΠ΅ΡΠ΅Π΅Ρ
Π°Π»ΠΈ Π² ΠΏΠ΅ΡΠ²ΠΎΠ·Π΄Π°Π½Π½ΠΎΠΌ Π²ΠΈΠ΄Π΅. Π Π°Π±ΠΎΡΠ°Π»ΠΈ ΠΎΠ½ΠΈ ΠΈΡΠΏΡΠ°Π²Π½ΠΎ Π΄ΠΎ ΡΠΎΠ³ΠΎ Π΄Π½Ρ, ΠΏΠΎΠΊΠ° Ρ Π½Π°ΡΠ΅Π³ΠΎ gitlab Π½Π΅ ΡΠΌΠ΅Π½ΠΈΠ»ΡΡ IP ΠΈ CI ΠΏΡΠ΅Π²ΡΠ°ΡΠΈΠ»ΡΡ Π² ΡΡΠΊΠ²Ρ. ΠΡΠΎΠ±Π»Π΅ΠΌΠ° Π±ΡΠ»Π° Π² ΡΠΎΠΌ, ΡΡΠΎ Π² ΠΎΠ΄Π½ΠΎΠΌ ΠΈΠ· docker-ΠΎΠ±ΡΠ°Π·ΠΎΠ², ΡΡΠ°ΡΡΠ²ΠΎΠ²Π°Π²ΡΠ΅Π³ΠΎ Π² CI, Π±ΡΠ» git, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎ ssh ΡΡΠ½ΡΠ» Python-ΠΌΠΎΠ΄ΡΠ»ΠΈ. ΠΠ»Ρ ssh Π½ΡΠΆΠ΅Π½ ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠΉ ΠΊΠ»ΡΡ ΠΈ … ΠΎΠ½ Π±ΡΠ» Π² ΠΎΠ±ΡΠ°Π·Π΅ Π²ΠΌΠ΅ΡΡΠ΅ Ρ known_hosts. Π Π»ΡΠ±ΠΎΠΉ CI Π·Π°Π²Π΅ΡΡΠ°Π»ΡΡ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ ΠΊΠ»ΡΡΠ° ΠΈΠ·-Π·Π° Π½Π΅ΡΠΎΠ²ΠΏΠ°Π΄Π΅Π½ΠΈΡ ΡΠ΅Π°Π»ΡΠ½ΠΎΠ³ΠΎ IP ΠΈ ΡΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ Π² known_hosts. ΠΠ· ΠΈΠΌΠ΅ΡΡΠΈΡ
ΡΡ Dockfile-ΠΎΠ² Π±ΡΠ» Π±ΡΡΡΡΠΎ ΡΠΎΠ±ΡΠ°Π½ Π½ΠΎΠ²ΡΠΉ ΠΎΠ±ΡΠ°Π· ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π° ΠΎΠΏΡΠΈΡ StrictHostKeyChecking no
. ΠΠΎ Π½Π΅ΠΏΡΠΈΡΡΠ½ΡΠΉ ΠΏΡΠΈΠ²ΠΊΡΡ ΠΎΡΡΠ°Π»ΡΡ ΠΈ ΠΏΠΎΡΠ²ΠΈΠ»ΠΎΡΡ ΠΆΠ΅Π»Π°Π½ΠΈΠ΅ ΠΏΠ΅ΡΠ΅Π½Π΅ΡΡΠΈ Π»ΠΈΠ±Ρ Π² ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠΉ PyPI-ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΉ. ΠΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΠΌ Π±ΠΎΠ½ΡΡΠΎΠΌ, ΠΏΠΎΡΠ»Π΅ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄Π° Π½Π° ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠΉ PyPI, ΡΡΠ°Π½ΠΎΠ²ΠΈΠ»ΡΡ Π±ΠΎΠ»Π΅Π΅ ΠΏΡΠΎΡΡΠΎΠΉ ΠΏΠ°ΠΉΠΏΠ»Π°ΠΉΠ½ ΠΈ Π½ΠΎΡΠΌΠ°Π»ΡΠ½ΠΎΠ΅ ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ requirements.txt
ΠΡΠ±ΠΎΡ ΡΠ΄Π΅Π»Π°Π½, ΠΠΎΡΠΏΠΎΠ΄Π°!
ΠΡ Π²ΡΡ ΠΊΡΡΡΠΈΠΌ Π² ΠΎΠ±Π»Π°ΠΊΠ°Ρ ΠΈ Kubernetes ΠΈ Π² ΠΈΡΠΎΠ³Π΅ Ρ ΠΎΡΠ΅Π»ΠΎΡΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ Π½Π΅Π±ΠΎΠ»ΡΡΠΎΠΉ ΡΠ΅ΡΠ²ΠΈΡ ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΠ» ΠΈΠ· ΡΠ΅Π±Ρ stateless-ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅Ρ Ρ Π²Π½Π΅ΡΠ½Π΅ΠΌ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ΠΌ. ΠΡ Π° ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ S3, ΡΠΎ ΠΈ ΠΏΡΠΈΠΎΡΠΈΡΠ΅Ρ Π±ΡΠ» Π·Π° Π½ΠΈΠΌ. Π ΠΏΠΎ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ Ρ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠ΅ΠΉ Π² gitlab (ΠΌΠΎΠΆΠ½ΠΎ ΠΈ ΡΠ°ΠΌΠΎΠΌΡ Π΄ΠΎΠΏΠΈΡΠ°ΡΡ ΠΏΠΎ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ).
ΠΠ΅Π³Π»ΡΠΉ ΠΏΠΎΠΈΡΠΊ Π΄Π°Π» Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² s3pypi, pypicloud ΠΈ Π²Π°ΡΠΈΠ°Π½Ρ Ρ Β«ΡΡΡΠ½ΡΠΌΒ» ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ΠΌ html-ΡΠ°ΠΉΠ»ΠΎΠ² Π΄Π»Ρ ΡΠ΅ΠΏΡ. ΠΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ Π²Π°ΡΠΈΠ°Π½Ρ ΠΎΡΠΏΠ°Π» ΡΠ°ΠΌ-ΡΠΎΠ±ΠΎΠΉ.
s3pypi: ΠΡΠΎ cli Π΄Π»Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ Ρ ΠΎΡΡΠΈΠ½Π³Π° Π½Π° S3. ΠΡΠΊΠ»Π°Π΄ΡΠ²Π°Π΅ΠΌ ΡΠ°ΠΉΠ»Ρ, Π³Π΅Π½Π΅ΡΠΈΠΌ html ΠΈ Π·Π°Π»ΠΈΠ²Π°Π΅ΠΌ Π² ΡΠΎΡ ΠΆΠ΅ Π±Π°ΠΊΠ΅Ρ. ΠΠ»Ρ Π΄ΠΎΠΌΠ°ΡΠ½Π΅Π³ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΏΠΎΠ΄ΠΎΠΉΠ΄Π΅Ρ.
pypicloud: ΠΠ°Π·Π°Π»ΡΡ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΡΠΌ ΠΏΡΠΎΠ΅ΠΊΡΠΎΠΌ, Π½ΠΎ ΠΏΠΎΡΠ»Π΅ ΠΏΡΠΎΡΡΠ΅Π½ΠΈΡ Π΄ΠΎΠΊΠΈ ΠΏΡΠΈΡΠ»ΠΎ ΡΠ°Π·ΠΎΡΠ°ΡΠΎΠ²Π°Π½ΠΈΠ΅. ΠΠ΅ΡΠΌΠΎΡΡΡ Π½Π° Ρ ΠΎΡΠΎΡΡΡ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΈ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ ΠΏΠΎΠ΄ ΡΠ²ΠΎΠΈ Π·Π°Π΄Π°ΡΠΈ, Π½Π° Π΄Π΅Π»Π΅ ΠΎΠΊΠ°Π·Π°Π»ΡΡ ΠΈΠ·Π±ΡΡΠΎΡΠ΅Π½ ΠΈ ΡΠ»ΠΎΠΆΠ½ΡΠΉ Π² Π½Π°ΡΡΡΠΎΠΉΠΊΠ΅. ΠΠΎΠΏΡΠ°Π²ΠΈΡΡ ΠΊΠΎΠ΄ ΠΏΠΎΠ΄ ΡΠ²ΠΎΠΈ Π·Π°Π΄Π°ΡΠΈ, ΠΏΠΎ ΡΠΎΠ³Π΄Π°ΡΠ½ΠΈΠΌ ΠΏΡΠΈΠΊΠΈΠ΄ΠΊΠ°ΠΌ, Π·Π°Π½ΡΠ»ΠΎ Π±Ρ 3-5 Π΄Π½Π΅ΠΉ. Π’Π°ΠΊ ΠΆΠ΅ ΡΠ΅ΡΠ²ΠΈΡΡ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠ° ΠΠ. ΠΡΡΠ°Π²ΠΈΠ»ΠΈ Π΅Π³ΠΎ Π½Π° ΡΠ»ΡΡΠ°ΠΉ, Π΅ΡΠ»ΠΈ Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΡΠ΅Π³ΠΎ Π½Π΅ Π½Π°ΠΉΠ΄Π΅ΠΌ.
ΠΠΎΠ»Π΅Π΅ ΡΠ³Π»ΡΠ±Π»Π΅Π½Π½ΡΠΉ ΠΏΠΎΠΈΡΠΊ Π΄Π°Π» ΠΌΠΎΠ΄ΡΠ»Ρ Π΄Π»Ρ Nginx, ngx_aws_auth. Π Π΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠΌ Π΅Π³ΠΎ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΡΠ°Π» XML ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ°Π΅ΠΌΡΠΉ Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅, ΠΏΠΎ ΠΊΠΎΡΠΎΡΠΎΠΌΡ Π±ΡΠ»ΠΎ Π²ΠΈΠ΄Π½ΠΎ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ΅ Π±Π°ΠΊΠ΅ΡΠ° S3. ΠΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ ΠΊΠΎΠΌΠΌΠΈΡ, Π½Π° ΠΌΠΎΠΌΠ΅Π½Ρ ΠΏΠΎΠΈΡΠΊΠ°, Π±ΡΠ» Π³ΠΎΠ΄ Π½Π°Π·Π°Π΄. Π Π΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΉ Π²ΡΠ³Π»ΡΠ΄Π΅Π» Π·Π°Π±ΡΠΎΡΠ΅Π½Π½ΡΠΌ.
ΠΠ±ΡΠ°ΡΠΈΠ²ΡΠΈΡΡ ΠΊ ΠΏΠ΅ΡΠ²ΠΎΠΈΡΡΠΎΡΠ½ΠΈΠΊΡ ΠΈ ΠΏΡΠΎΡΠΈΡΠ°Π²
ΠΠ·ΡΠ² Π·Π° ΠΎΡΠ½ΠΎΠ²Ρ ΡΡΠΎΡ ΠΏΡΠΈΠΌΠ΅Ρ, ΡΠ΅ΡΠ΅Π· ΡΠ°Ρ Π½Π°Π±Π»ΡΠ΄Π°Π» Π² ΡΠ²ΠΎΠ΅ΠΌ Π±ΡΠ°ΡΠ·Π΅ΡΠ΅ ΡΠΎΡ ΠΆΠ΅ XML, ΡΡΠΎ ΠΈ ΠΏΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ ΠΌΠΎΠ΄ΡΠ»Ρ ngx_aws_auth, Π½ΠΎ Π½Π°ΠΏΠΈΡΠ°Π½ΠΎ ΡΠΆΠ΅ Π²ΡΠ΅ Π±ΡΠ»ΠΎ Π½Π° JS.
Π Π΅ΡΠ΅Π½ΠΈΠ΅ Π½Π° nginx ΠΌΠ½Π΅ ΠΎΡΠ΅Π½Ρ Π½ΡΠ°Π²ΠΈΠ»ΠΎΡΡ. ΠΠΎ-ΠΏΠ΅ΡΠ²ΡΡ Ρ ΠΎΡΠΎΡΠ°Ρ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΈ ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ ΠΏΡΠΈΠΌΠ΅ΡΠΎΠ², Π²ΠΎ-Π²ΡΠΎΡΡΡ ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ Π²ΡΠ΅ ΠΏΠ»ΡΡΠΊΠΈ Nginx ΠΏΠΎ ΡΠ°Π±ΠΎΡΠ΅ Ρ ΡΠ°ΠΉΠ»Π°ΠΌΠΈ (ΠΈΠ· ΠΊΠΎΡΠΎΠ±ΠΊΠΈ), Π²-ΡΡΠ΅ΡΡΠΈΡ Π»ΡΠ±ΠΎΠΉ ΡΠ΅Π»ΠΎΠ²Π΅ΠΊ ΡΠΌΠ΅ΡΡΠΈΠΉ ΠΏΠΈΡΠ°ΡΡ ΠΊΠΎΠ½ΡΠΈΠ³ΠΈ Π΄Π»Ρ Nginx, ΡΠΌΠΎΠΆΠ΅Ρ ΡΠ°Π·ΠΎΠ±ΡΠ°ΡΡΡΡ ΡΡΠΎ ΠΊ ΡΠ΅ΠΌΡ. Π’Π°ΠΊ ΠΆΠ΅ ΠΏΠ»ΡΡΠΎΠΌ Π΄Π»Ρ ΠΌΠ΅Π½Ρ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΠΈΠ·ΠΌ, ΠΏΠΎ ΡΡΠ°Π²Π½Π΅Π½ΠΈΡ Ρ Python ΠΈΠ»ΠΈ Go (Π΅ΡΠ»ΠΈ ΠΏΠΈΡΠ°ΡΡ Ρ Π½ΡΠ»Ρ), Π½Π΅ Π³ΠΎΠ²ΠΎΡΡ ΡΠΆ ΠΎΠ± nexus.
TL;DR Π§Π΅ΡΠ΅Π· 2 Π΄Π½Ρ ΡΠ΅ΡΡΠΎΠ²Π°Ρ Π²Π΅ΡΡΠΈΡ PyPi ΡΠΆΠ΅ Π±ΡΠ»Π° ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½Π° Π² CI.
ΠΠ°ΠΊ ΡΡΠΎ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ?
Π Nginx ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°Π΅ΡΡΡ ΠΌΠΎΠ΄ΡΠ»Ρ ngx_http_js_module
, Π²ΠΊΠ»ΡΡΠ΅Π½ Π² ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½ΡΠΉ docker-ΠΎΠ±ΡΠ°Π·. ΠΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΠΌ Π½Π°Ρ ΡΠΊΡΠΈΠΏΡ c ΠΏΠΎΠΌΠΎΡΡΡ Π΄ΠΈΡΠ΅ΠΊΡΠΈΠ²Ρ js_import
Π² ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ Nginx. ΠΡΠ·ΠΎΠ² ΡΡΠ½ΠΊΡΠΈΠΈ ΠΎΡΡΡΠ΅ΡΡΠ²Π»ΡΠ΅ΡΡΡ Π΄ΠΈΡΠ΅ΠΊΡΠΈΠ²ΠΎΠΉ js_content
. ΠΠ»Ρ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ
ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π΄ΠΈΡΠ΅ΠΊΡΠΈΠ²Π° js_set
, ΠΊΠΎΡΠΎΡΠ°Ρ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΠΌ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ ΡΡΠ½ΠΊΡΠΈΡ ΠΎΠΏΠΈΡΠ°Π½Π½ΡΡ Π² ΡΠΊΡΠΈΠΏΡΠ΅. Π Π²ΠΎΡ Π²ΡΠΏΠΎΠ»Π½ΡΡΡ ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡΡ Π² NJS ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠΎΠ»ΡΠΊΠΎ Ρ ΠΏΠΎΠΌΠΎΡΡΡ Nginx, Π½ΠΈ ΠΊΠ°ΠΊΠΈΡ
ΠΠ°ΠΌ ΡΠ°ΠΌ XMLHttpRequest. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ Π² ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ Nginx Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠΉ Π»ΠΎΠΊΠ΅ΠΉΡΠ½. Π Π² ΡΠΊΡΠΈΠΏΡΠ΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ ΠΎΠΏΠΈΡΠ°Π½ ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡ (subrequest) ΠΊ ΡΡΠΎΠΌΡ Π»ΠΎΠΊΠ΅ΠΉΡΠ΅Π½Ρ. Π§ΡΠΎΠ±Ρ ΠΈΠΌΠ΅ΡΡ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ ΠΎΠ±ΡΠ°ΡΠΈΡΡΡΡ ΠΊ ΡΡΠ½ΠΊΡΠΈΠΈ ΠΈΠ· ΠΊΠΎΠ½ΡΠΈΠ³Π° Nginx, Π² ΡΠ°ΠΌΠΎΠΌ ΡΠΊΡΠΈΠΏΡΠ΅ ΠΈΠΌΡ ΡΡΠ½ΠΊΡΠΈΠΈ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΊΡΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ export default
.
nginx.conf
load_module modules/ngx_http_js_module.so;
http {
js_import imported_name from script.js;
server {
listen 8080;
...
location = /sub-query {
internal;
proxy_pass http://upstream;
}
location / {
js_content imported_name.request;
}
}
script.js
function request(r) {
function call_back(resp) {
// handler's code
r.return(resp.status, resp.responseBody);
}
r.subrequest('/sub-query', { method: r.method }, call_back);
}
export default {request}
ΠΡΠΈ Π·Π°ΠΏΡΠΎΡΠ΅ Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅ http://localhost:8080/
ΠΌΡ ΠΏΠΎΠΏΠ°Π΄Π°Π΅ΠΌ Π² location /
Π² ΠΊΠΎΡΠΎΡΠΎΠΌ Π΄ΠΈΡΠ΅ΠΊΡΠΈΠ²Π° js_content
Π²ΡΠ·ΡΠ²Π°Π΅Ρ ΡΡΠ½ΠΊΡΠΈΡ request
ΠΎΠΏΠΈΡΠ°Π½Π½ΡΡ Π² Π½Π°ΡΠ΅ΠΌ ΡΠΊΡΠΈΠΏΡΠ΅ script.js
. Π ΡΠ²ΠΎΡ ΠΎΡΠ΅ΡΠ΅Π΄Ρ Π² ΡΡΠ½ΠΊΡΠΈΠΈ request
ΠΎΡΡΡΠ΅ΡΡΠ²Π»ΡΠ΅ΡΡΡ ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡ ΠΊ location = /sub-query
, Ρ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠΌ (Π² ΡΠ΅ΠΊΡΡΠ΅ΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ GET) ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΌ ΠΈΠ· Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠ° (r)
, Π½Π΅ΡΠ²Π½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°Π΅ΠΌΡΠΌ ΠΏΡΠΈ Π²ΡΠ·ΠΎΠ²Π΅ ΡΡΠΎΠΉ ΡΡΠ½ΠΊΡΠΈΠΈ. ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΎΡΠ²Π΅ΡΠ° ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡΠ° Π±ΡΠ΄Π΅Ρ ΠΎΡΡΡΠ΅ΡΡΠ²Π»Π΅Π½Π° Π² ΡΡΠ½ΠΊΡΠΈΠΈ call_back
.
ΠΡΠΎΠ±ΡΠ΅ΠΌ S3
Π§ΡΠΎΠ±Ρ ΡΠ΄Π΅Π»Π°ΡΡ Π·Π°ΠΏΡΠΎΡ ΠΊ ΠΏΡΠΈΠ²Π°ΡΠ½ΠΎΠΌΡ S3-Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΡ, Π½Π°ΠΌ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡ:
ACCESS_KEY
SECRET_KEY
S3_BUCKET
ΠΠ· ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΠΎΠ³ΠΎ http-ΠΌΠ΅ΡΠΎΠ΄Π°, ΡΠ΅ΠΊΡΡΠ°Ρ Π΄Π°ΡΠ°/Π²ΡΠ΅ΠΌΡ, S3_NAME ΠΈ URI Π³Π΅Π½Π΅ΡΠΈΡΡΠ΅ΡΡΡ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠ³ΠΎ Π²ΠΈΠ΄Π° ΡΡΡΠΎΠΊΠ°, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅ΡΡΡ (HMAC_SHA1) Ρ ΠΏΠΎΠΌΠΎΡΡΡ SECRET_KEY. ΠΠ°Π»Π΅Π΅ ΡΡΡΠΎΠΊΡ, Π²ΠΈΠ΄Π° AWS $ACCESS_KEY:$HASH
, ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π² Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ΅ Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ. Π’Π° ΠΆΠ΅ Π΄Π°ΡΠ°/Π²ΡΠ΅ΠΌΡ, ΡΡΠΎ Π±ΡΠ»Π° ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½Π° Π΄Π»Ρ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ ΡΡΡΠΎΠΊΠΈ Π½Π° ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠ΅ΠΌ ΡΠ°Π³Π΅, Π΄ΠΎΠ»ΠΆΠ½Π° Π±ΡΡΡ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π° Π² Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ X-amz-date
. Π ΠΊΠΎΠ΄Π΅ ΡΡΠΎ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΡΠ°ΠΊ:
nginx.conf
load_module modules/ngx_http_js_module.so;
http {
js_import s3 from s3.js;
js_set $s3_datetime s3.date_now;
js_set $s3_auth s3.s3_sign;
server {
listen 8080;
...
location ~* /s3-query/(?<s3_path>.*) {
internal;
proxy_set_header X-amz-date $s3_datetime;
proxy_set_header Authorization $s3_auth;
proxy_pass $s3_endpoint/$s3_path;
}
location ~ "^/(?<prefix>[w-]*)[/]?(?<postfix>[w-.]*)$" {
js_content s3.request;
}
}
s3.js
(ΠΏΡΠΈΠΌΠ΅Ρ Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ AWS Sign v2, ΠΏΠ΅ΡΠ΅Π²Π΅Π΄Π΅Π½Π° Π² ΡΡΠ°ΡΡΡ deprecated)
var crypt = require('crypto');
var s3_bucket = process.env.S3_BUCKET;
var s3_access_key = process.env.S3_ACCESS_KEY;
var s3_secret_key = process.env.S3_SECRET_KEY;
var _datetime = new Date().toISOString().replace(/[:-]|.d{3}/g, '');
function date_now() {
return _datetime
}
function s3_sign(r) {
var s2s = r.method + 'nnnn';
s2s += `x-amz-date:${date_now()}n`;
s2s += '/' + s3_bucket;
s2s += r.uri.endsWith('/') ? '/' : r.variables.s3_path;
return `AWS ${s3_access_key}:${crypt.createHmac('sha1', s3_secret_key).update(s2s).digest('base64')}`;
}
function request(r) {
var v = r.variables;
function call_back(resp) {
r.return(resp.status, resp.responseBody);
}
var _subrequest_uri = r.uri;
if (r.uri === '/') {
// root
_subrequest_uri = '/?delimiter=/';
} else if (v.prefix !== '' && v.postfix === '') {
// directory
var slash = v.prefix.endsWith('/') ? '' : '/';
_subrequest_uri = '/?prefix=' + v.prefix + slash;
}
r.subrequest(`/s3-query${_subrequest_uri}`, { method: r.method }, call_back);
}
export default {request, s3_sign, date_now}
ΠΠ΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ½Π΅Π½ΠΈΡ ΠΏΡΠΎ _subrequest_uri
: ΡΡΠΎ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ ΠΊΠΎΡΠΎΡΠ°Ρ Π² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΠΈΠ·Π½Π°ΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ uri ΡΠΎΡΠΌΠΈΡΡΠ΅Ρ Π·Π°ΠΏΡΠΎΡ ΠΊ S3. ΠΡΠ»ΠΈ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ΅ Β«ΠΊΠΎΡΠ½ΡΒ», Π² ΡΠ°ΠΊΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ ΡΡΠΎΡΠΌΠΈΡΠΎΠ²Π°ΡΡ uri-Π·Π°ΠΏΡΠΎΡ Ρ ΡΠΊΠ°Π·Π°Π½ΠΈΠ΅ΠΌ ΡΠ°Π·Π΄Π΅Π»ΠΈΡΠ΅Π»Ρ delimiter
, ΠΊΠΎΡΠΎΡΡΠΉ Π²Π΅ΡΠ½Π΅Ρ ΡΠΏΠΈΡΠΎΠΊ Π²ΡΠ΅Ρ
xml-ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² CommonPrefixes, ΡΡΠΎ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΠ΅Ρ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡΠΌ (Π² ΡΠ»ΡΡΠ°Π΅ Ρ PyPI, ΡΠΏΠΈΡΠΎΠΊ Π²ΡΠ΅Ρ
ΠΏΠ°ΠΊΠ΅ΡΠΎΠ²). ΠΡΠ»ΠΈ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠΏΠΈΡΠΎΠΊ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ³ΠΎ Π² ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ (ΡΠΏΠΈΡΠΎΠΊ Π²ΡΠ΅Ρ
Π²Π΅ΡΡΠΈΠΉ ΠΏΠ°ΠΊΠ΅ΡΠΎΠ²), ΡΠΎΠ³Π΄Π° uri-Π·Π°ΠΏΡΠΎΡ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ ΠΏΠΎΠ»Π΅ prefix Ρ ΠΈΠΌΠ΅Π½Π΅ΠΌ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ (ΠΏΠ°ΠΊΠ΅ΡΠ°) ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ Π·Π°ΠΊΠ°Π½ΡΠΈΠ²Π°ΡΡΠΈΠΉΡΡ Π½Π° ΡΠ»ΡΡ /. Π ΠΏΡΠΎΡΠΈΠ²Π½ΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ ΠΊΠΎΠ»Π»ΠΈΠ·ΠΈΠΈ ΠΏΡΠΈ Π·Π°ΠΏΡΠΎΡΠ΅ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ³ΠΎ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ. ΠΡΡΡ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ aiohttp-request ΠΈ aiohttp-requests ΠΈ Π΅ΡΠ»ΠΈ Π² Π·Π°ΠΏΡΠΎΡΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠΊΠ°Π·Π°Π½ΠΎ /?prefix=aiohttp-request
, ΡΠΎΠ³Π΄Π° Π² ΠΎΡΠ²Π΅ΡΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ΅ ΠΎΠ±Π΅ΠΈΡ
Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΉ. ΠΡΠ»ΠΈ ΠΆΠ΅ Π½Π° ΠΊΠΎΠ½ΡΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠ»ΡΡ, /?prefix=aiohttp-request/
, ΡΠΎ Π² ΠΎΡΠ²Π΅ΡΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ Π½ΡΠΆΠ½Π°Ρ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡ. Π Π΅ΡΠ»ΠΈ ΠΌΡ Π·Π°ΠΏΡΠ°ΡΠΈΠ²Π°Π΅ΠΌ ΡΠ°ΠΉΠ», ΡΠΎ ΡΠ΅Π·ΡΠ»ΡΡΠΈΡΡΡΡΠΈΠΉ uri Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΡΠ»ΠΈΡΠ°ΡΡ ΠΎΡ ΠΈΠ·Π½Π°ΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ.
Π‘ΠΎΡ ΡΠ°Π½ΡΠ΅ΠΌ, ΠΏΠ΅ΡΠ΅Π·Π°ΠΏΡΡΠΊΠ°Π΅ΠΌ Nginx. Π Π±ΡΠ°ΡΠ·Π΅ΡΠ΅ Π²Π²ΠΎΠ΄ΠΈΠΌ Π°Π΄ΡΠ΅Ρ Π½Π°ΡΠ΅Π³ΠΎ Nginx, ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠΌ ΡΠ°Π±ΠΎΡΡ Π·Π°ΠΏΡΠΎΡΠ° Π±ΡΠ΄Π΅Ρ XML, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ:
Π‘ΠΏΠΈΡΠΎΠΊ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΉ
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>myback-space</Name>
<Prefix></Prefix>
<Marker></Marker>
<MaxKeys>10000</MaxKeys>
<Delimiter>/</Delimiter>
<IsTruncated>false</IsTruncated>
<CommonPrefixes>
<Prefix>new/</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>old/</Prefix>
</CommonPrefixes>
</ListBucketResult>
ΠΠ· ΡΠΏΠΈΡΠΊΠ° Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΉ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ CommonPrefixes
.
ΠΠΎΠ±Π°Π²ΠΈΠ², Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅, ΠΊ Π½Π°ΡΠ΅ΠΌΡ Π°Π΄ΡΠ΅ΡΡ Π½ΡΠΆΠ½ΡΡ Π½Π°ΠΌ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡ, ΠΏΠΎΠ»ΡΡΠΈΠΌ Π΅Π΅ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ΅ ΡΠ°ΠΊ ΠΆΠ΅ Π² Π²ΠΈΠ΄Π΅ XML:
Π‘ΠΏΠΈΡΠΎΠΊ ΡΠ°ΠΉΠ»ΠΎΠ² Π² Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name> myback-space</Name>
<Prefix>old/</Prefix>
<Marker></Marker>
<MaxKeys>10000</MaxKeys>
<Delimiter></Delimiter>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>old/giphy.mp4</Key>
<LastModified>2020-08-21T20:27:46.000Z</LastModified>
<ETag>"00000000000000000000000000000000-1"</ETag>
<Size>1350084</Size>
<Owner>
<ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
<DisplayName></DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>old/hsd-k8s.jpg</Key>
<LastModified>2020-08-31T16:40:01.000Z</LastModified>
<ETag>"b2d76df4aeb4493c5456366748218093"</ETag>
<Size>93183</Size>
<Owner>
<ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
<DisplayName></DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
ΠΠ· ΡΠΏΠΈΡΠΊΠ° ΡΠ°ΠΉΠ»ΠΎΠ² Π²ΠΎΠ·ΡΠΌΠ΅ΠΌ ΡΠΎΠ»ΡΠΊΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ Key
.
ΠΡΡΠ°Π΅ΡΡΡ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ XML ΡΠ°ΡΠΏΠ°ΡΡΠΈΡΡ ΠΈ ΠΎΡΠ΄Π°ΡΡ Π² Π²ΠΈΠ΄Π΅ HTML, ΠΏΡΠ΅Π΄Π²Π°ΡΠΈΡΠ΅Π»ΡΠ½ΠΎ Π·Π°ΠΌΠ΅Π½ΠΈΠ² Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Content-Type Π½Π° text/html.
function request(r) {
var v = r.variables;
function call_back(resp) {
var body = resp.responseBody;
if (r.method !== 'PUT' && resp.status < 400 && v.postfix === '') {
r.headersOut['Content-Type'] = "text/html; charset=utf-8";
body = toHTML(body);
}
r.return(resp.status, body);
}
var _subrequest_uri = r.uri;
...
}
function toHTML(xml_str) {
var keysMap = {
'CommonPrefixes': 'Prefix',
'Contents': 'Key',
};
var pattern = `<k>(?<v>.*?)</k>`;
var out = [];
for(var group_key in keysMap) {
var reS;
var reGroup = new RegExp(pattern.replace(/k/g, group_key), 'g');
while(reS = reGroup.exec(xml_str)) {
var data = new RegExp(pattern.replace(/k/g, keysMap[group_key]), 'g');
var reValue = data.exec(reS);
var a_text = '';
if (group_key === 'CommonPrefixes') {
a_text = reValue.groups.v.replace(///g, '');
} else {
a_text = reValue.groups.v.split('/').slice(-1);
}
out.push(`<a href="/${reValue.groups.v}">${a_text}</a>`);
}
}
return '<html><body>n' + out.join('</br>n') + 'n</html></body>'
}
ΠΡΠΎΠ±ΡΠ΅ΠΌ PyPI
ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ, ΡΡΠΎ Π½ΠΈΠ³Π΄Π΅ ΠΈ Π½ΠΈΡΠ΅Π³ΠΎ Π½Π΅ Π»ΠΎΠΌΠ°Π΅ΡΡΡ Π½Π° Π·Π°Π²Π΅Π΄ΠΎΠΌΠΎ ΡΠ°Π±ΠΎΡΠΈΡ ΠΏΠ°ΠΊΠ΅ΡΠ°Ρ .
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄Π»Ρ ΡΠ΅ΡΡΠΎΠ² Π½ΠΎΠ²ΠΎΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅
python3 -m venv venv
. ./venv/bin/activate
# Π‘ΠΊΠ°ΡΠΈΠ²Π°Π΅ΠΌ ΡΠ°Π±ΠΎΡΠΈΠ΅ ΠΏΠ°ΠΊΠ΅ΡΡ.
pip download aiohttp
# ΠΠ°Π³ΡΡΠΆΠ°Π΅ΠΌ Π² ΠΏΡΠΈΠ²Π°ΡΠ½ΡΡ ΡΠ΅ΠΏΡ
for wheel in *.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done
rm -f *.whl
# Π£ΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΠΈΠ· ΠΏΡΠΈΠ²Π°ΡΠ½ΠΎΠΉ ΡΠ΅ΠΏΡ
pip install aiohttp -i http://localhost:8080
ΠΠΎΠ²ΡΠΎΡΡΠ΅ΠΌ Ρ Π½Π°ΡΠΈΠΌΠΈ Π»ΠΈΠ±Π°ΠΌΠΈ.
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄Π»Ρ ΡΠ΅ΡΡΠΎΠ² Π½ΠΎΠ²ΠΎΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅
python3 -m venv venv
. ./venv/bin/activate
pip install setuptools wheel
python setup.py bdist_wheel
for wheel in dist/*.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done
pip install our_pkg --extra-index-url http://localhost:8080
Π CI, ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΠΏΠ°ΠΊΠ΅ΡΠ° Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΡΠ°ΠΊ:
pip install setuptools wheel
python setup.py bdist_wheel
curl -sSfT dist/*.whl -u "gitlab-ci-token:${CI_JOB_TOKEN}" "https://pypi.our-domain.com/${CI_PROJECT_NAME}"
ΠΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ
Π Gitlab Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ JWT Π΄Π»Ρ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ/Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ Π²Π½Π΅ΡΠ½ΠΈΡ ΡΠ΅ΡΠ²ΠΈΡΠΎΠ². ΠΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π²ΡΠΈΡΡ Π΄ΠΈΡΠ΅ΠΊΡΠΈΠ²ΠΎΠΉ auth_request Π² Nginx, ΠΏΠ΅ΡΠ΅Π½Π°ΠΏΡΠ°Π²ΠΈΠΌ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΎΠ½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ Π² ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠΈΠΉ Π²ΡΠ·ΠΎΠ² ΡΡΠ½ΠΊΡΠΈΠΈ Π² ΡΠΊΡΠΈΠΏΡΠ΅. Π ΡΠΊΡΠΈΠΏΡΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠ΄Π΅Π»Π°Π½ Π΅ΡΡ ΠΎΠ΄ΠΈΠ½ ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡ Π½Π° url Gitlab-Π° ΠΈ Π΅ΡΠ»ΠΈ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΎΠ½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ ΡΠΊΠ°Π·Π°Π½Ρ Π±ΡΠ»ΠΈ Π²Π΅ΡΠ½ΠΎ, ΡΠΎ Gitlab Π²Π΅ΡΠ½Π΅Ρ ΠΊΠΎΠ΄ 200 ΠΈ Π±ΡΠ΄Π΅Ρ ΡΠ°Π·ΡΠ΅ΡΠ΅Π½Π° Π·Π°Π³ΡΡΠ·ΠΊΠ°/ΡΠΊΠ°ΡΠΈΠ²Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΊΠ΅ΡΠ°. ΠΠΎΡΠ΅ΠΌΡ Π½Π΅ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΠΎΠ΄Π½ΠΈΠΌ ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡΠΎΠΌ ΠΈ ΡΡΠ°Π·Ρ Π½Π΅ ΠΎΡΠΏΡΠ°Π²ΠΈΡΡ Π΄Π°Π½Π½ΡΠ΅ Π² Gitlab? ΠΠΎΡΠΎΠΌΡ, ΡΡΠΎ ΠΏΡΠΈΠ΄Π΅ΡΡΡ ΡΠΎΠ³Π΄Π° ΠΏΡΠ°Π²ΠΈΡΡ ΡΠ°ΠΉΠ» ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ Nginx ΠΊΠ°ΠΆΠ΄ΡΠΉ ΡΠ°Π·, ΠΊΠ°ΠΊ Ρ Π½Π°Ρ Π±ΡΠ΄ΡΡ ΠΊΠ°ΠΊΠΈΠ΅-ΡΠΎ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π² Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ, Π° ΡΡΠΎ Π΄ΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ ΠΌΡΡΠΎΡΠ½ΠΎΠ΅ Π·Π°Π½ΡΡΠΈΠ΅. Π’Π°ΠΊ ΠΆΠ΅, Π΅ΡΠ»ΠΈ Π² Kubernetes ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΏΠΎΠ»ΠΈΡΠΈΠΊΠ° read-only root filesystem, ΡΠΎ ΡΡΠΎ Π΅ΡΡ Π±ΠΎΠ»ΡΡΠ΅ Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅Ρ ΡΠ»ΠΎΠΆΠ½ΠΎΡΡΠ΅ΠΉ ΠΏΡΠΈ ΠΏΠΎΠ΄ΠΌΠ΅Π½Π΅ nginx.conf ΡΠ΅ΡΠ΅Π· configmap. Π ΡΡΠ°Π½ΠΎΠ²ΠΈΡΡΡ Π°Π±ΡΠΎΠ»ΡΡΠ½ΠΎ Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Π° ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ Nginx ΡΠ΅ΡΠ΅Π· configmap ΠΏΡΠΈ ΠΎΠ΄Π½ΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ ΠΏΠΎΠ»ΠΈΡΠΈΠΊ Π·Π°ΠΏΡΠ΅ΡΠ°ΡΡΠΈΡ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΠΎΠΌΠΎΠ² (pvc) ΠΈ read-only root filesystem (ΡΠ°ΠΊΠΎΠ΅ ΡΠΎΠΆΠ΅ Π±ΡΠ²Π°Π΅Ρ).
ΠΡΠΏΠΎΠ»ΡΠ·ΡΡ ΠΏΡΠΎΠΌΠ΅ΠΆΡΡΠΎΡΠ½ΡΠΌ Π·Π²Π΅Π½ΠΎΠΌ NJS, ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ ΠΌΠ΅Π½ΡΡΡ ΡΠΊΠ°Π·Π°Π½Π½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ Π² ΠΊΠΎΠ½ΡΠΈΠ³Π΅ nginx Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΠΈ Π΄Π΅Π»Π°ΡΡ ΠΊΠ°ΠΊΠΈΠ΅-Π½ΠΈΠ±ΡΠ΄Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ Π² ΡΠΊΡΠΈΠΏΡΠ΅ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, Π½Π΅Π²Π΅ΡΠ½ΠΎ ΡΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ URL).
nginx.conf
location = /auth-provider {
internal;
proxy_pass $auth_url;
}
location = /auth {
internal;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
js_content auth.auth;
}
location ~ "^/(?<prefix>[w-]*)[/]?(?<postfix>[w-.]*)$" {
auth_request /auth;
js_content s3.request;
}
s3.js
var env = process.env;
var env_bool = new RegExp(/[Tt]rue|[Yy]es|[Oo]n|[TtYy]|1/);
var auth_disabled = env_bool.test(env.DISABLE_AUTH);
var gitlab_url = env.AUTH_URL;
function url() {
return `${gitlab_url}/jwt/auth?service=container_registry`
}
function auth(r) {
if (auth_disabled) {
r.return(202, '{"auth": "disabled"}');
return null
}
r.subrequest('/auth-provider',
{method: 'GET', body: ''},
function(res) {
r.return(res.status, "");
});
}
export default {auth, url}
Π‘ΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ Π½Π°Π·ΡΠ΅Π²Π°Π΅Ρ Π²ΠΎΠΏΡΠΎΡ: -Π ΠΏΠΎΡΠ΅ΠΌΡ Π±Ρ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π³ΠΎΡΠΎΠ²ΡΠ΅ ΠΌΠΎΠ΄ΡΠ»ΠΈ? Π’Π°ΠΌ Π²Π΅Π΄Ρ Π²ΡΡ ΡΠΆΠ΅ ΡΠ΄Π΅Π»Π°Π½ΠΎ! ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, var AWS = require(‘aws-sdk’) ΠΈ Π½Π΅ Π½Π°Π΄ΠΎ ΠΏΠΈΡΠ°ΡΡ «Π²Π΅Π»ΠΎΡΠΈΠΏΠ΅Π΄» Ρ S3-Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠ΅ΠΉ!
ΠΠ΅ΡΠ΅ΠΉΠ΄Π΅ΠΌ ΠΊ ΠΌΠΈΠ½ΡΡΠ°ΠΌ
ΠΠ»Ρ ΠΌΠ΅Π½Ρ, Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π²Π½Π΅ΡΠ½ΠΈΠ΅ JS-ΠΌΠΎΠ΄ΡΠ»Π΅ΠΉ, ΡΡΠ°Π»ΠΎ Π½Π΅ΠΏΡΠΈΡΡΠ½ΠΎΠΉ, Π½ΠΎ ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΠΎΠΉ ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎΡΡΡΡ. ΠΠΏΠΈΡΠ°Π½Π½ΡΠΉ Π² ΠΏΡΠΈΠΌΠ΅ΡΠ΅ Π²ΡΡΠ΅ require(‘crypto’), ΡΡΠΎ
Π’Π°ΠΊ ΠΆΠ΅ Π΄Π»Ρ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ ΠΏΡΠΎΠ΅ΠΊΡΠ° Π² Nginx Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±ΡΡΡ ΠΎΡΠΊΠ»ΡΡΠ΅Π½ΠΎ ΡΠΆΠ°ΡΠΈΠ΅ gzip off;
ΠΠΎΡΠΎΠΌΡ, ΡΡΠΎ Π½Π΅Ρ gzip-ΠΌΠΎΠ΄ΡΠ»Ρ Π² NJS ΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ Π΅Π³ΠΎ Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, ΡΠΎΠΎΡΠ²Π΅ΡΡΠ²Π΅Π½Π½ΠΎ Π½Π΅Ρ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ ΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ ΡΠΆΠ°ΡΡΠΌΠΈ Π΄Π°Π½Π½ΡΠΌΠΈ. ΠΡΠ°Π²Π΄Π°, Π½Π΅ ΠΎΡΠΎΠ±ΠΎ ΡΡΠΎ ΠΈ ΠΌΠΈΠ½ΡΡ Π΄Π»Ρ Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΊΠ΅ΠΉΡΠ°. Π’Π΅ΠΊΡΡΠ° Π½Π΅ ΠΌΠ½ΠΎΠ³ΠΎ, Π° ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°Π΅ΠΌΡΠ΅ ΡΠ°ΠΉΠ»Ρ ΡΠΆΠ΅ ΡΠΆΠ°ΡΡΠ΅ ΠΈ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΠΎΠ΅ ΡΠΆΠ°ΡΠΈΠ΅ ΠΈΠΌ Π½Π΅ ΠΎΡΠΎΠ±ΠΎ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ. Π’Π°ΠΊ ΠΆΠ΅ ΡΡΠΎ Π½Π΅ Π½Π° ΡΡΠΎΠ»ΡΠΊΠΎ Π½Π°Π³ΡΡΠΆΠ΅Π½Π½ΡΠΉ ΠΈΠ»ΠΈ ΠΊΡΠΈΡΠΈΡΠ½ΡΠΉ ΡΠ΅ΡΠ²ΠΈΡ, ΡΡΠΎΠ±Ρ Π·Π°ΠΌΠΎΡΠ°ΡΠΈΠ²Π°ΡΡΡΡ Ρ ΠΎΡΠ΄Π°ΡΠ΅ΠΉ ΠΊΠΎΠ½ΡΠ΅Π½ΡΠ° Π½Π° Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΠΌΠΈΠ»Π»ΠΈΡΠ΅ΠΊΡΠ½Π΄ Π±ΡΡΡΡΠ΅Π΅.
ΠΡΠ»Π°Π΄ΠΊΠ° ΡΠΊΡΠΈΠΏΡΠ° Π΄ΠΎΠ»Π³Π°Ρ ΠΈ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Π° ΡΠΎΠ»ΡΠΊΠΎ ΡΠ΅ΡΠ΅Π· Β«ΠΏΡΠΈΠ½ΡΡΒ» Π² error.log. Π Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ Π²ΡΡΡΠ°Π²Π»Π΅Π½Π½ΠΎΠ³ΠΎ ΡΡΠΎΠ²Π½Ρ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ info, warn ΠΈΠ»ΠΈ error Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ 3 ΠΌΠ΅ΡΠΎΠ΄Π° r.log, r.warn, r.error ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎ. ΠΠ΅ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠΊΡΠΈΠΏΡΡ ΠΏΡΡΠ°ΡΡΡ ΠΎΡΠ»Π°ΠΆΠΈΠ²Π°ΡΡ Π² Chrome (v8) ΠΈΠ»ΠΈ ΠΊΠΎΠ½ΡΠΎΠ»ΡΠ½ΠΎΠΉ ΡΡΠ»Π·Π΅ njs, Π½ΠΎ Π½Π΅ Π²ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΡΠ°ΠΌ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ. ΠΡΠΈ ΠΎΡΠ»Π°Π΄ΠΊΠ΅ ΠΊΠΎΠ΄Π°, Π°ΠΊΠ° ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΠ΅ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅, history Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΠΏΡΠΈΠΌΠ΅ΡΠ½ΠΎ ΡΠ°ΠΊ:
docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginx
ΠΈ ΡΠ°ΠΊΠΈΡ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°ΡΠ΅Π»ΡΠ½ΠΎΡΡΠ΅ΠΉ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΡΠΎΡΠ½ΠΈ.
ΠΠ°ΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π° Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΠ΄Π·Π°ΠΏΡΠΎΡΠΎΠ² ΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ Π΄Π»Ρ Π½ΠΈΡ , ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅ΡΡΡ Π² Π·Π°ΠΏΡΡΠ°Π½Π½ΡΠΉ ΠΊΠ»ΡΠ±ΠΎΠΊ. ΠΠ½ΠΎΠ³Π΄Π° Π½Π°ΡΠΈΠ½Π°Π΅ΡΡ ΠΌΠ΅ΡΠ°ΡΡΡΡ ΠΏΠΎ ΡΠ°Π·Π½ΡΠΌ ΠΎΠΊΠ½Π°ΠΌ IDE ΠΏΡΡΠ°ΡΡΡ ΡΠ°Π·ΠΎΠ±ΡΠ°ΡΡΡΡ Π² ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°ΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ Π΄Π΅ΠΉΡΡΠ²ΠΈΠΉ ΡΠ²ΠΎΠ΅Π³ΠΎ ΠΊΠΎΠ΄Π°. ΠΡΠΎ Π½Π΅ ΡΠ»ΠΎΠΆΠ½ΠΎ, Π½ΠΎ ΠΈΠ½ΠΎΠ³Π΄Π° ΡΠΈΠ»ΡΠ½ΠΎ Π½Π°ΠΏΡΡΠ³Π°Π΅Ρ.
ΠΠ΅Ρ ΠΏΠΎΠ»Π½ΠΎΡΠ΅Π½Π½ΠΎΠΉ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΈ ES6.
ΠΠΎΠΆΠ΅Ρ Π΅ΡΡΡ ΠΈ Π΅ΡΡ ΠΊΠ°ΠΊΠΈΠ΅-ΡΠΎ Π½Π΅Π΄ΠΎΡΡΠ°ΡΠΊΠΈ, Π½ΠΎ Π½ΠΈ Ρ ΡΠ΅ΠΌ Ρ Π±ΠΎΠ»Π΅Π΅ Π½Π΅ ΡΡΠ°Π»ΠΊΠΈΠ²Π°Π»ΡΡ. ΠΠΎΠ΄Π΅Π»ΠΈΡΠ΅ΡΡ ΠΈΠ½ΡΠΎΠΉ Π΅ΡΠ»ΠΈ Ρ ΠΠ°Ρ Π΅ΡΡΡ ΠΎΡΡΠΈΡΠ°ΡΠ΅Π»ΡΠ½ΡΠΉ ΠΎΠΏΡΡ ΡΠΊΡΠΏΠ»ΡΠ°ΡΠ°ΡΠΈΠΈ NJS.
ΠΠ°ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅
NJS — Π»Π΅Π³ΠΊΠΎΠ²Π΅ΡΠ½ΡΠΉ open-source ΠΈΠ½ΡΠ΅ΡΠΏΡΠ΅ΡΠ°ΡΠΎΡ, ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡΡΠΈΠΉ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ Π² Nginx ΡΠ°Π·Π½ΡΠ΅ ΡΡΠ΅Π½Π°ΡΠΈΠΈ Π½Π° Π―Π JavaScript. ΠΡΠΈ Π΅Π³ΠΎ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ΅ Π±ΡΠ»ΠΎ ΡΠ΄Π΅Π»Π΅Π½ΠΎ Π±ΠΎΠ»ΡΡΠΎΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ. ΠΠΎΠ½Π΅ΡΠ½ΠΎ ΠΌΠ½ΠΎΠ³ΠΎ ΡΠ΅Π³ΠΎ Π² Π½ΡΠΌ Π΅ΡΡ Π½Π΅ Ρ Π²Π°ΡΠ°Π΅Ρ, Π½ΠΎ ΠΏΡΠΎΠ΅ΠΊΡ ΡΠ°Π·Π²ΠΈΠ²Π°Π΅ΡΡΡ ΡΠΈΠ»Π°ΠΌΠΈ Π½Π΅Π±ΠΎΠ»ΡΡΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ ΠΈ ΠΎΠ½ΠΈ Π°ΠΊΡΠΈΠ²Π½ΠΎ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ Π½ΠΎΠ²ΡΠ΅ ΡΠΈΡΠΈ ΠΈ ΡΠΈΠΊΡΡΡ Π±Π°Π³ΠΈ. Π― ΠΆΠ΅ Π½Π°Π΄Π΅ΡΡΡ, ΡΡΠΎ ΠΊΠΎΠ³Π΄Π°-Π½ΠΈΠ±ΡΠ΄Ρ NJS ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ°ΡΡ Π²Π½Π΅ΡΠ½ΠΈΠ΅ ΠΌΠΎΠ΄ΡΠ»ΠΈ, ΡΡΠΎ ΡΠ΄Π΅Π»Π°Π΅Ρ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π» Nginx ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠΈ Π½Π΅ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½Π½ΡΠΌ. ΠΠΎ Π΅ΡΡΡ NGINX Plus ΠΈ ΠΊΠ°ΠΊΠΈΡ -ΡΠΎ ΡΠΈΡ ΡΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ Π½Π΅ Π±ΡΠ΄Π΅Ρ!
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com