Миллиондаған алдыңғы қатарлы қолданбалар қоршаған ортаға қатысты құрылымдарды басқарады. Әрбір орта үшін — әзірлеу, кезеңдік кезең немесе өндіріс болсын — фронтендік қолданбаның бөлек құрылымы жасалып, дұрыс орта айнымалы мәндері орнатылуы керек. Бірнеше қолданбалар қатысса, құрастырулар саны көбейеді, бұл көңілсіздікті арттырады. Бұл ұзақ уақыт бойы жиі кездесетін мәселе болды, бірақ ортаның айнымалы мәндерін өңдеудің жақсы жолы бар. Мен бұл процесті оңтайландырудың жолын таптым және осы мақалада мен сізге құру уақытын қысқартатын және жобаларыңыздағы орталар арасындағы сәйкестікті қамтамасыз етуге көмектесетін тиімді процесті жасау үшін қадамдық нұсқаулық беремін.
Бастамас бұрын, менің ойымша, біз қайталау жасауымыз керек. Веб-қосымшалар әрдайым дерлік "орта айнымалылары " деп аталатын айнымалыларға сүйенеді, олар көбінесе ішкі жүйенің соңғы нүктелерін, интеграциялық жүйелерді, төлем жүйесінің кілттерін, шығарылым нөмірлерін және т.б. Әрине, бұл айнымалы мәндердің мәндері қолданба орналастырылған ортаға байланысты әр түрлі болады.
Мысалы, төлем шлюзімен әрекеттесетін қолданбаны елестетіңіз. Әзірлеу ортасында төлем шлюзінің URL мекенжайы тестілеуге арналған құм жәшігін (https://sandbox.paymentgateway.com) көрсетуі мүмкін, ал өндіріс ортасында ол тікелей қызметке (https://live.paymentgateway.com) нұсқайды. ). Сол сияқты, деректер қауіпсіздігін қамтамасыз ету және орталарды араластырмау үшін әр орта үшін әртүрлі API кілттері немесе кез келген басқа ортаға арнайы параметр пайдаланылады.
Backend қолданбаларын құру кезінде бұл мәселе емес. Бұл айнымалы мәндерді қолданба кодында жариялау жеткілікті, себебі бұл айнымалы мәндердің мәндері серверлік орта орналастырылған сервер ортасында сақталады. Осылайша, сервер қолданбасы іске қосу кезінде оларға қол жеткізеді.
Дегенмен, алдыңғы қатарлы қосымшалармен жұмыс біршама күрделене түседі. Олар пайдаланушы шолғышында іске қосылғандықтан, олардың нақты орта айнымалы мәндеріне қатынасы жоқ. Мұны шешу үшін, бұл айнымалы мәндердің мәндері әдетте құрастыру уақытында фронтендік қолданбаға «пісірілген». Осылайша, қолданба пайдаланушының шолғышында іске қосылғанда, барлық қажетті мәндер алдыңғы бағдарламаға ендірілген.
Бұл тәсіл, көптеген басқалар сияқты, ескертумен бірге келеді: әрбір құрастыруда сәйкес мәндер болуы үшін әрбір орта үшін бірдей фронтондық қолданбаның бөлек құрастыруын жасау керек.
Мысалы , бізде үш орта бар делік:
ішкі тестілеуге арналған әзірлеу;
интеграциялық тестілеу кезеңі;
және тұтынушылар үшін өндіріс.
Жұмысыңызды сынаққа жіберу үшін қолданбаны құрастырып, оны әзірлеу ортасына орналастырасыз. Ішкі тестілеу аяқталғаннан кейін, оны кезеңге орналастыру үшін қолданбаны қайта құру керек, содан кейін өндіріске орналастыру үшін оны қайтадан жасау керек. Егер жобада біреуден көп қосымшалар болса, мұндай құрастырулардың саны айтарлықтай артады. Сонымен қатар, осы құрастырмалар арасында кодтық база өзгермейді — екінші және үшінші құрастырмалар бірдей бастапқы кодқа негізделген.
Мұның бәрі босату процесін көлемді, баяу және қымбат етеді, сонымен қатар сапаны қамтамасыз ету қаупін тудырады. Мүмкін құрастыру әзірлеу ортасында жақсы сыналған болуы мүмкін, бірақ кезең құрастыру техникалық тұрғыдан жаңа, яғни қате үшін жаңа әлеует бар.
Мысал: Сізде X және Y секунд құрастыру уақыты бар екі қолданба бар. Осы үш орта үшін екі қолданба да құрастыру уақытында 3X + 3Y алады. Дегенмен, әрбір қолданбаны бір рет құрастырып, сол құрастыруды барлық орталарда пайдалана алсаңыз, жалпы уақыт X + Y секундына дейін қысқарып, құрастыру уақытын үш есе қысқартады.
Бұл ресурстар шектеулі және құрастыру уақыттары бірнеше минуттан бір сағатқа дейін ауытқитын фронталды құбыр желілерінде үлкен өзгеріс тудыруы мүмкін. Мәселе жаһандық барлық дерлік интерфейс қолданбаларында бар және жиі оны шешудің ешқандай жолы жоқ. Дегенмен, бұл, әсіресе бизнес тұрғысынан алғанда, күрделі мәселе.
Үш бөлек құрылымды жасаудың орнына жай ғана біреуін жасап, оны барлық орталарда орналастырсаңыз тамаша емес пе? Мен дәл осылай жасаудың жолын таптым.
Ортаның айнымалы мәндерін орнату
Алдымен, қажетті ортаның айнымалы мәндері тізімделетін алдыңғы жобаның репозиторийінде файл жасау керек. Оларды әзірлеуші жергілікті жерде пайдаланады. Әдетте, бұл файл .env.local
деп аталады, оны көптеген заманауи фронттер жақтаулары оқи алады. Міне, осындай файлдың мысалы:
CLIENT_ID='frontend-development' API_URL=/api/v1' PUBLIC_URL='/' COMMIT_SHA=''
Ескертпе: әртүрлі шеңберлер орта айнымалылары үшін әртүрлі атау конвенцияларын талап етеді. Мысалы, React бағдарламасында айнымалы атауларының алдына REACT_APP_
қосу керек. Бұл файл қолданбаға тікелей әсер ететін айнымалы мәндерді міндетті түрде қамтуы қажет емес; ол сондай-ақ жөндеуге арналған пайдалы ақпаратты қамтуы мүмкін. Мен COMMIT_SHA
айнымалы мәнін қостым, оны кейінірек осы құрастыруға негізделген тапсырманы қадағалау үшін құрастыру тапсырмасынан шығарамыз.
Әрі қарай, ортаның айнымалы мәндерін анықтауға болатын environment.js
деп аталатын файлды жасаңыз. Фронт интерфейсі бұларды сізге енгізеді. Мысалы, React үшін олар process.env
нысанында сақталады:
const ORIGIN_ENVIRONMENTS = window.ORIGIN_ENVIRONMENTS = { CLIENT_ID: process.env.CLIENT_ID, API_URL: process.env.API_URL, PUBLIC_URL: process.env.PUBLIC_URL, COMMIT_SHA: process.env.COMMIT_SHA }; export const ENVIRONMENT = { clientId: ORIGIN_ENVIRONMENTS.CLIENT_ID, apiUrl: ORIGIN_ENVIRONMENTS.API_URL, publicUrl: ORIGIN_ENVIRONMENTS.PUBLIC_URL ?? "/", commitSha: ORIGIN_ENVIRONMENTS.COMMIT_SHA, };
Мұнда сіз window.ORIGIN_ENVIRONMENTS
айнымалылар үшін барлық бастапқы мәндерді шығарып аласыз. ORIGIN_ENVIRONMENTS нысаны, оларды браузер консолінде көруге мүмкіндік береді. Оған қоса, оларды ENVIRONMENT
нысанына көшіру керек, мұнда кейбір әдепкі мәндерді де орнатуға болады, мысалы: publicUrl
әдепкі бойынша / деп есептейміз. Қолданбада осы айнымалылар қажет болған жерде ENVIRONMENT
нысанын пайдаланыңыз.
Осы кезеңде сіз жергілікті дамудың барлық қажеттіліктерін орындадыңыз. Бірақ мақсат - әртүрлі орталарды өңдеу.
Ол үшін келесі мазмұны бар .env
файлын жасаңыз:
CLIENT_ID='<client_id>' API_URL='<api_url>' PUBLIC_URL='<public_url>' COMMIT_SHA=$COMMIT_SHA
Бұл файлда ортаға тәуелді айнымалылар үшін толтырғыштарды көрсеткіңіз келеді. Олар бірегей және бастапқы кодыңызбен ешбір жағдайда сәйкес келмейтін болса, сіз қалаған кез келген нәрсе болуы мүмкін. Қосымша сенімділік үшін сіз тіпті пайдалана аласыз
Орталар бойынша өзгермейтін айнымалылар үшін (мысалы, орындау хэші) нақты мәндерді тікелей жаза аласыз немесе құрастыру тапсырмасы кезінде қолжетімді мәндерді пайдалана аласыз (мысалы, $COMMIT_SHA ). Фронт құрылымы осы толтырғыштарды құрастыру процесі кезінде нақты мәндермен ауыстырады:
Файл
Енді сіз толтырғыштардың орнына нақты мәндерді қою мүмкіндігіне ие боласыз. Бұл әрекетті орындау үшін inject.py
файлын жасаңыз (мен Python таңдадым, бірақ сіз бұл мақсат үшін кез келген құралды пайдалана аласыз), ол алдымен толтырғыштарды айнымалы атауларға салыстыруды қамтуы керек:
replacement_map = { "<client_id>": "CLIENT_ID", "<api_url>": "API_URL", "<public_url>": "PUBLIC_URL", "%3Cpublic_url%3E": "PUBLIC_URL" }
public_url
екі рет тізімделгенін және екінші жазбада жақшалардан құтылғанын ескеріңіз. Бұл сізге CSS және HTML файлдарында қолданылатын барлық айнымалылар үшін қажет.
base_path = 'usr/share/nginx/html' target_files = [ f'{base_path}/static/js/main.*.js', f'{base_path}/static/js/chunk.*.js', f'{base_path}/static/css/main.*.css', f'{base_path}/static/css/chunk.*.css', f'{base_path}/index.html' ]
injector.py
файлын жасаймыз, онда біз құрастыру артефакті файлдарының (мысалы, JS, HTML және CSS файлдары) кескіні мен тізімін аламыз және толтырғыштарды ағымдағы ортадағы айнымалы мәндермен ауыстырамыз:
import os import glob def inject_envs(filename, replacement_map): with open(filename) as r: lines = r.read() for key, value in replacement_map.items(): lines = lines.replace(key, os.environ.get(value) or '') with open(filename, "w") as w: w.write(lines) def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map)
Содан кейін, inject.py
файлында осы жолды қосыңыз ( injector.py
импорттауды ұмытпаңыз):
injector.inject(target_files, replacement_map, base_path)
inject.py
сценарийінің тек орналастыру кезінде жұмыс істейтініне көз жеткізуіміз керек. Python орнатып, барлық артефактілерді көшіргеннен кейін оны CMD
пәрменіндегі Dockerfile
файлына қосуға болады: RUN apk add python3 COPY nginx/default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/ci /ci COPY --from=build /app/build /usr/share/nginx/html CMD ["/bin/sh", "-c", "python3 ./ci/inject.py && nginx -g 'daemon off;'"]That's it! This way, during each deployment, the pre-built files will be used, with variables specific to the deployment environment injected into them.
Міне бітті! Осылайша, әрбір орналастыру кезінде, оларға енгізу ортасына тән айнымалы мәндермен алдын ала құрастырылған файлдар пайдаланылады.
Файл:
Бір нәрсе – егер құрастыру артефактілеріңіз файл атауларында мазмұн хэшін қамтыса, бұл инъекция файл атауларына әсер етпейді және бұл шолғышты кэштеу кезінде қиындықтар тудыруы мүмкін. Бұны түзету үшін, енгізілген айнымалылары бар файлдарды өзгерткеннен кейін сізге қажет:
Мұны жүзеге асыру үшін inject.py
файлына хэш кітапханасының импортын ( import hashlib
) және келесі функцияларды қосыңыз.
def sha256sum(filename): h = hashlib.sha256() b = bytearray(128 * 1024) mv = memoryview(b) with open(filename, 'rb', buffering=0) as f: while n := f.readinto(mv): h.update(mv[:n]) return h.hexdigest() def replace_filename_imports(filename, new_filename, base_path): allowed_extensions = ('.html', '.js', '.css') for path, dirc, files in os.walk(base_path): for name in files: current_filename = os.path.join(path, name) if current_filename.endswith(allowed_extensions): with open(current_filename) as f: s = f.read() s = s.replace(filename, new_filename) with open(current_filename, "w") as f: f.write(s) def rename_file(fullfilename): dirname = os.path.dirname(fullfilename) filename, ext = os.path.splitext(os.path.basename(fullfilename)) digest = sha256sum(fullfilename) new_filename = f'{filename}.{digest[:8]}' new_fullfilename = f'{dirname}/{new_filename}{ext}' os.rename(fullfilename, new_fullfilename) return filename, new_filename
Дегенмен, барлық файлдардың атын өзгерту қажет емес. Мысалы, index.html
файл атауы өзгеріссіз қалуы керек және оған қол жеткізу үшін атын өзгерту қажет екенін көрсететін жалаушаны сақтайтын TargetFile
сыныбын жасаңыз:
class TargetFile: def __init__(self, glob, should_be_renamed = True): self.glob = glob self.should_be_renamed = should_be_renamed
Енді inject.py
ішіндегі файл жолдарының массивін TargetFile
сынып нысандарының массивімен ауыстыру керек:
target_files = [ injector.TargetFile(f'{base_path}/static/js/main.*.js'), injector.TargetFile(f'{base_path}/static/js/chunk.*.js'), injector.TargetFile(f'{base_path}/static/css/main.*.css'), injector.TargetFile(f'{base_path}/static/css/chunk.*.css'), injector.TargetFile(f'{base_path}/index.html', False) ]
Жалау орнатылған болса, файлдың атын өзгертуді қосу үшін injector.py
ішіндегі inject
функциясын жаңартыңыз:
def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map) if target_file.should_be_renamed: filename, new_filename = rename_file(filename) replace_filename_imports(filename, new_filename, base_path)
Нәтижесінде артефакт файлдары мына атау пішімін сақтайды: <origin-file-name>
. <injection-hash>
. <extension>
.
Инъекция алдында файл атауы:
Инъекциядан кейінгі файл атауы:
Бірдей орта айнымалылары пайдаланушы шолғышына файлды дұрыс кэштеуге мүмкіндік беретін бірдей файл атауын береді. Енді осы айнымалы мәндердің дұрыс мәндері шолғыш кэшінде сақталатынына кепілдік бар, нәтижесінде клиент үшін жақсы өнімділік.
Әрбір орта үшін жеке құрылымдарды жасаудың дәстүрлі тәсілі шектеулі ресурстары бар командалар үшін мәселе болуы мүмкін бірнеше маңызды тиімсіздіктерге әкелді.
Енді сізде ұзартылған орналастыру уақыттарын, шамадан тыс құрастыруларды және алдыңғы қосымшалар үшін сапаны қамтамасыз етудегі жоғары тәуекелдерді шеше алатын шығарылым процесінің жоспары бар - мұның барлығы. Барлық орталарда кепілдік берілген консистенцияның жаңа деңгейін енгізу кезінде.
N құрастыру қажет емес, сізге тек біреуі керек. Алдағы шығарылым үшін сынақтан өткен құрастыруды жай ғана орналастыруға болады, ол сонымен қатар ықтимал қате мәселелерін шешуге көмектеседі, өйткені бірдей құрастыру барлық орталарда пайдаланылады. Сонымен қатар, бұл сценарийдің орындалу жылдамдығы тіпті ең оңтайландырылған құрылымға қарағанда салыстыруға келмейтін жылдамырақ. Мысалы, MacBook 14 PRO, M1, 32 ГБ жергілікті эталондары келесідей:
Менің көзқарасым шығару процесін жеңілдетеді, тиімді кэштеу стратегияларына мүмкіндік беру арқылы қолданбаның өнімділігін қолдайды және құрастыруға қатысты қателер оны орталарға енгізбеуін қамтамасыз етеді. Бұған қоса, бұрын жалықтыратын құрастыру тапсырмаларына жұмсалған барлық уақыт пен күш енді одан да жақсырақ пайдаланушы тәжірибесін жасауға бағытталуы мүмкін. Нені сүюге болмайды?
Біз құрастыруға қатысты қателердің басқа орталар үшін қолданбаға еніп кетпеуін қамтамасыз етеміз. Құрастыру жүйелеріндегі кемшіліктерге байланысты пайда болатын фантомдық қателер болуы мүмкін. Мүмкіндіктер аз, бірақ олар бар.