{"id":452399,"date":"2025-03-19T21:00:38","date_gmt":"2025-03-19T21:00:38","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=452399"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=452399","title":{"rendered":"<span>\u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0439 CI\/CD \u0438\u043b\u0438 \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435 Feature \u0441\u0442\u0435\u043d\u0434\u044b<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041c\u043d\u043e\u0433\u0438\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0434 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435\u043c \u0432 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b: <strong>prod<\/strong>, <strong>dev <\/strong>\u0438\u043b\u0438 <strong>staging<\/strong>. \u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0443\u043c \u2014 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432. \u041e\u0434\u043d\u0430\u043a\u043e, \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430, \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0447\u0430\u0441\u0442\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442. \u0412 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043b\u043e\u0433\u0438\u0447\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u0435\u043f\u043b\u043e\u044f \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0432\u0435\u0442\u043e\u043a \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0434 \u043c\u0435\u0440\u0436\u0435\u043c. \u0425\u043e\u0442\u044f \u044d\u0442\u0430 \u0438\u0434\u0435\u044f \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u0432\u044f\u0437\u0430\u043d\u0430 \u0441 \u0440\u044f\u0434\u043e\u043c \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0438\u0437\u0432\u043d\u0435, \u0447\u0442\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 DNS-\u0437\u0430\u043f\u0438\u0441\u0435\u0439. \u041a\u0430\u043a \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e?<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0447\u0438\u0441\u0442\u043a\u0443 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0438\u0445 \u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0445\u043b\u0430\u043c\u043b\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b?<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441 \u0434\u0440\u0443\u0433\u0438\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u044b?<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u043c\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c \u0438 \u0442.\u0434.)?<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043e\u0442\u0432\u0435\u0447\u0443 \u043d\u0430 \u044d\u0442\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0443 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 Feature \u0441\u0442\u0435\u043d\u0434\u043e\u0432 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 CI\/CD.<\/p>\n<h3>\u0412\u0432\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435<\/h3>\n<p>\u0414\u043b\u044f \u0446\u0435\u043b\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0443\u0441\u043b\u043e\u0432\u0438\u0439: <\/p>\n<p>\u0415\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0435 \u043d\u0430 C# (\u043f\u043e \u043c\u043d\u0435\u043d\u0438\u044e \u0430\u0432\u0442\u043e\u0440\u0430, \u043b\u0443\u0447\u0448\u0438\u0439 \u044f\u0437\u044b\u043a \u0432 \u043c\u0438\u0440\u0435 =)).<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 URL Shortener.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f PostgreSQL.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u043d\u0430 WebAssembly \u0438 API.<\/p>\n<\/li>\n<li>\n<p>\u0423 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043d\u0430 \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c API.<\/p>\n<\/li>\n<li>\n<p>\u041c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u043c\u0435\u0441\u0442\u043e \u0441\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0436\u0438\u0437\u043d\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f CI\/CD \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f GitLab \u0432\u0435\u0440\u0441\u0438\u0438 17.<\/p>\n<ul>\n<li>\n<p>\u0421\u0431\u043e\u0440\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 Docker Runner, \u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f GitLab.<\/p>\n<\/li>\n<li>\n<p>\u0414\u0435\u043f\u043b\u043e\u0439 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Kubernetes \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Helm.<\/p>\n<\/li>\n<li>\n<p>DNS-\u0437\u0430\u043f\u0438\u0441\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Cloudflare. \u041e\u0434\u043d\u0430\u043a\u043e \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c External DNS, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c\u0438 DNS-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430\u043c\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u0442\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u0432\u0430\u0448 \u0432\u044b\u0431\u043e\u0440 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e feature-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439, \u043d\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043d\u0443\u0436\u043d\u044b dev \u0438 prod \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<h4>\u0427\u0443\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438<\/h4>\n<p>\u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437:<\/p>\n<ul>\n<li>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u043d\u0430 Blazor Wasm 8 (C#).<\/p>\n<\/li>\n<li>\n<p>\u0411\u0435\u043a\u0435\u043d\u0434\u0430 \u043d\u0430 <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a> Core 8 (C#).<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 (\u043f\u043e\u0441\u043b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438).<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0444\u0430\u0439\u043b <code>appsettings.json<\/code>, \u0433\u0434\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c\u044b API \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d \u0432 \u043f\u0430\u043f\u043a\u0435 <code>BlazorWasmUrlShortener<\/code>, \u0442\u0430\u043c \u0436\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f Dockerfile.<\/p>\n<\/li>\n<\/ul>\n<p>\u0411\u0435\u043a\u0435\u043d\u0434:<\/p>\n<ul>\n<li>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 PostgreSQL \u0447\u0435\u0440\u0435\u0437 ORM Entity Framework.<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0430\u0439\u043b\u0430 <code>appsettings.json<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0440\u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u0430\u0437\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 <code>BlazorWasmUrlShortener.Api.<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0435 <code>BlazorWasmUrlShortener.ApiModels<\/code> \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c API-\u043c\u043e\u0434\u0435\u043b\u0435\u0439.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/b0c\/163\/930\/b0c163930a1e9bc874d26e86fa632a36.jpg\" alt=\"\u0422\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435\" title=\"\u0422\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435\" width=\"1948\" height=\"246\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b0c\/163\/930\/b0c163930a1e9bc874d26e86fa632a36.jpg\" data-blurred=\"true\"\/><\/p>\n<div><figcaption>\u0422\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435<\/figcaption><\/div>\n<\/figure>\n<h3>\u041f\u0435\u0440\u0432\u043e\u0435 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p><strong>\u041d\u0430\u0431\u0440\u043e\u0441\u043e\u043a \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430<\/strong><\/p>\n<p>\u041d\u0430\u0448 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u0447\u0435\u0442\u044b\u0440\u0435\u0445 \u0441\u0442\u0430\u0434\u0438\u0439:<\/p>\n<ol>\n<li>\n<p>Build \u2014 \u0441\u0431\u043e\u0440\u043a\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u043e\u0432 (\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0436\u043e\u0431\u044b \u0434\u043b\u044f \u0431\u0435\u043a\u0435\u043d\u0434\u0430 \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430).<\/p>\n<\/li>\n<li>\n<p>Provision \u2014 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<\/li>\n<li>\n<p>Deploy \u2014 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 Kubernetes (\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0436\u043e\u0431\u044b \u0434\u043b\u044f \u0431\u0435\u043a\u0435\u043d\u0434\u0430 \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430).<\/p>\n<\/li>\n<li>\n<p>Manage \u2014 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u043e\u043f\u0440\u043e\u0441 \u0441 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439 \u0440\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0436\u043e\u0431\u044b, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u043c\u0435\u0440\u0434\u0436\u0430 \u0432\u0435\u0442\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 Merge Request (MR).<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/70b\/39b\/c9e\/70b39bc9ecc11b0d05747c8b94a04b59.png\" alt=\"\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430\" title=\"\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430\" width=\"838\" height=\"314\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/70b\/39b\/c9e\/70b39bc9ecc11b0d05747c8b94a04b59.png\"\/><\/p>\n<div><figcaption>\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430<\/figcaption><\/div>\n<\/figure>\n<h2>\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f<\/h2>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0434\u0432\u0430 \u0444\u0430\u0439\u043b\u0430:<\/p>\n<ol>\n<li>\n<p>.base.gitlab-ci.yml \u2014 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0431\u0449\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0434\u0436\u043e\u0431 \u0434\u043b\u044f \u0441\u0442\u0430\u0434\u0438\u0439 build \u0438 deploy.<\/p>\n<\/li>\n<li>\n<p>.gitlab-ci.yml \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0430\u0439\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0447\u0435\u0440\u0435\u0437 <code>include: .base.gitlab-ci.yml<\/code> \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u0435\u0440\u0432\u044b\u0439 \u0444\u0430\u0439\u043b.<\/p>\n<\/li>\n<\/ol>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 CI, \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u0432 \u043f\u0430\u043f\u043a\u0435 <code>ci<\/code>, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0437\u0430\u0441\u043e\u0440\u044f\u0442\u044c \u043a\u043e\u0440\u0435\u043d\u044c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f. \u0412 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438:<\/p>\n<ul>\n<li>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0443 <code>ci\/core\/helm-charts\/application<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 Helm-\u0447\u0430\u0440\u0442 \u0434\u043b\u044f \u043d\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 (\u043e\u043d \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u0435\u043d, \u0438 \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445).<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u0444\u0430\u0439\u043b\u044b \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0447\u0430\u0440\u0442\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0445 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0430\u0441\u0442\u043e.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b0a\/0b1\/ae6\/b0a0b1ae6997a691080a9e09cfe620f5.png\" alt=\"\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0447\u0430\u0440\u0442\u0430 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0430\u043f\u043a\u0435\" title=\"\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0447\u0430\u0440\u0442\u0430 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0430\u043f\u043a\u0435\" width=\"1311\" height=\"751\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b0a\/0b1\/ae6\/b0a0b1ae6997a691080a9e09cfe620f5.png\"\/><\/p>\n<div><figcaption>\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0447\u0430\u0440\u0442\u0430 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0430\u043f\u043a\u0435<\/figcaption><\/div>\n<\/figure>\n<h2>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b<\/h2>\n<blockquote>\n<p><strong>\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/strong> \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043d\u0438\u0436\u0435, \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043d\u0435 \u0433\u043e\u0434\u044f\u0442\u0441\u044f \u0434\u043b\u044f production-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u042f \u0440\u0435\u0448\u0438\u043b \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0445 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u044b \u043a\u0430\u0440\u0442\u0438\u043d\u044b.<\/p>\n<\/blockquote>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0435\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 Kubernetes-\u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u0430\u0448\u0438\u043d\u0430 \u043d\u0430 \u0431\u0430\u0437\u0435 Ubuntu Server 22.04 \u0441 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u043c IP, 4 \u044f\u0434\u0440\u0430\u043c\u0438, 8 GiB \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u0438 80 GiB \u0434\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430. \u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0430\u0448\u0438\u043d\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u043d\u0435\u0439 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c CRI-O (\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043d\u044b\u0439 \u0440\u0430\u043d\u0442\u0430\u0439\u043c, \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043d\u0430 Docker, \u043d\u043e \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Kubernetes):<\/p>\n<pre><code class=\"cs\">sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common -y export OS=xUbuntu_22.04 export CRIO_VERSION=1.24 echo \"deb https:\/\/download.opensuse.org\/repositories\/devel:\/kubic:\/libcontainers:\/stable\/$OS\/ \/\"| sudo tee \/etc\/apt\/sources.list.d\/devel:kubic:libcontainers:stable.list echo \"deb http:\/\/download.opensuse.org\/repositories\/devel:\/kubic:\/libcontainers:\/stable:\/cri-o:\/$CRIO_VERSION\/$OS\/ \/\"|sudo tee \/etc\/apt\/sources.list.d\/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION.list curl -L https:\/\/download.opensuse.org\/repositories\/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION\/$OS\/Release.key | sudo apt-key add - curl -L https:\/\/download.opensuse.org\/repositories\/devel:\/kubic:\/libcontainers:\/stable\/$OS\/Release.key | sudo apt-key add - sudo apt update sudo apt install cri-o cri-o-runc -y sudo systemctl start crio sudo systemctl enable crio sudo systemctl status crio<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c Kubernetes \u0447\u0435\u0440\u0435\u0437 kubeadm \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c kubectl \u0438 helm:<\/p>\n<pre><code class=\"cs\">sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg sudo mkdir -p -m 755 \/etc\/apt\/keyrings curl -fsSL https:\/\/pkgs.k8s.io\/core:\/stable:\/v1.32\/deb\/Release.key | sudo gpg --dearmor -o \/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg echo 'deb [signed-by=\/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg] https:\/\/pkgs.k8s.io\/core:\/stable:\/v1.32\/deb\/ \/' | sudo tee \/etc\/apt\/sources.list.d\/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl sudo systemctl enable --now kubelet sudo sed -i 's\/#net.ipv4.ip_forward=1\/net.ipv4.ip_forward=1\/' \/etc\/sysctl.conf sudo sysctl -p sudo kubeadm init --pod-network-cidr=10.244.0.0\/16 --cri-socket=unix:\/\/\/var\/run\/crio\/crio.sock --apiserver-advertise-address __PUBLIC_IP__ --node-name master-1 mkdir ~\/.kube cp \/etc\/kubernetes\/admin.conf ~\/.kube\/config curl https:\/\/raw.githubusercontent.com\/helm\/helm\/main\/scripts\/get-helm-3 | bash kubectl taint nodes master-1 node-role.kubernetes.io\/control-plane:NoSchedule- kubectl get po -A<\/code><\/pre>\n<h2>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c Nginx Ingress, PostgreSQL, ExternalDNS \u0438 OpenEBS<\/h2>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c OpenEBS LocalPV<\/h4>\n<p>\u042d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 PersistentVolume \u0434\u043b\u044f PostgreSQL:<\/p>\n<pre><code class=\"cs\">helm repo add openebs https:\/\/openebs.github.io\/openebs helm upgrade --install --atomic --timeout 3m --set engines.replicated.mayastor.enabled=false --namespace openebs --create-namespace openebs openebs\/openebs --version 4.1.1 kubectl patch storageclass openebs-hostpath -p '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io\/is-default-class\":\"true\"}}}'<\/code><\/pre>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c PostgreSQL<\/h4>\n<p>\u0421\u0430\u043c\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0434\u044b \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 31000, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u043a \u043f\u043e\u0440\u0442\u0443 5432 PostgreSQL: <\/p>\n<pre><code class=\"cs\">helm repo add bitnami https:\/\/charts.bitnami.com\/bitnami helm upgrade --install --atomic --timeout 3m --set primary.service.type=NodePort --set primary.service.nodePorts.postgresql=31000 --set global.postgresql.auth.database=app --set global.postgresql.auth.password=__YOUR__ROOT__PASSWORD__ --set global.postgresql.auth.username=app --set global.postgresql.auth.postgresPassword=__YOUR__PROD__APP__DB__PASSWORD --create-namespace --namespace=devops postgresql bitnami\/postgresql --version 16.3.3<\/code><\/pre>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c ExternalDNS<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0432 Cloudflare. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0441 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 \u043d\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 DNS-\u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0434\u043b\u044f \u0434\u043e\u043c\u0435\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u043d\u0430 \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u0435 \u043d\u0438\u0436\u0435.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/156\/441\/00b\/15644100bce0b8f6c44c5a53daf6c730.png\" alt=\"\u0428\u0430\u0433 \u043f\u0435\u0440\u0432\u044b\u0439\" title=\"\u0428\u0430\u0433 \u043f\u0435\u0440\u0432\u044b\u0439\" width=\"1283\" height=\"527\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/156\/441\/00b\/15644100bce0b8f6c44c5a53daf6c730.png\"\/><\/p>\n<div><figcaption>\u0428\u0430\u0433 \u043f\u0435\u0440\u0432\u044b\u0439<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6af\/d82\/fa6\/6afd82fa64558ed8ab1c29cfccf4fa70.png\" alt=\"\u0428\u0430\u0433 \u0432\u0442\u043e\u0440\u043e\u0439\" title=\"\u0428\u0430\u0433 \u0432\u0442\u043e\u0440\u043e\u0439\" width=\"1506\" height=\"685\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6af\/d82\/fa6\/6afd82fa64558ed8ab1c29cfccf4fa70.png\"\/><\/p>\n<div><figcaption>\u0428\u0430\u0433 \u0432\u0442\u043e\u0440\u043e\u0439<\/figcaption><\/div>\n<\/figure>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c ExternalDNS \u0447\u0435\u0440\u0435\u0437 Helm<\/h4>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0435:<\/p>\n<pre><code class=\"cs\">helm repo add bitnami https:\/\/charts.bitnami.com\/bitnami helm upgrade --install --atomic --timeout 3m --set provider=cloudflare --set cloudflare.apiToken=__YOUR__API__TOKEN__ --set cloudflare.proxied=true --create-namespace --namespace=devops external-dns bitnami\/external-dns --version 8.7.1<\/code><\/pre>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c Nginx Ingress \u0447\u0435\u0440\u0435\u0437 Helm<\/h4>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0435:<\/p>\n<pre><code class=\"cs\">helm repo add ingress-nginx https:\/\/kubernetes.github.io\/ingress-nginx helm upgrade --install --set controller.service.enabled=false --set controller.hostNetwork=true --set controller.kind=DaemonSet --create-namespace --namespace devops ingress-nginx ingress-nginx\/ingress-nginx --version 4.11.3<\/code><\/pre>\n<h2>\u0412\u0442\u043e\u0440\u043e\u0435 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u0438\u0435: \u043f\u0438\u0448\u0435\u043c CI\/CD<\/h2>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432 \u0444\u0430\u0439\u043b <code>.base.gitlab-ci.yml<\/code>:<\/p>\n<pre><code class=\"cs\">stages: - build - provision - deploy - manage # Public image that contains Ansible, Kubectl, Helm image: public-docker-repository.avant-it.ru\/public-ci-job:4.4.0 .base:   tags:   # Enter your runner tag here   - avant_gitlab_com<\/code><\/pre>\n<h4>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0441\u0431\u043e\u0440\u043a\u0443<\/h4>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0434\u0436\u043e\u0431\u044b build \u0432 \u0444\u0430\u0439\u043b\u0435 <code>.base.gitlab-ci.yml<\/code>. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Jinja \u0438 Ansible. \u042d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0442\u0438\u043f\u043e\u0432\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0431\u043e\u0440\u043a\u0438 Docker-\u043e\u0431\u0440\u0430\u0437\u0430.<\/p>\n<pre><code class=\"cs\">.build:   extends: .base   stage: build   script:   # If J2_TEMPLATES variable (in yaml string array format) is defined, we template every file name   #   E.g. [\".env.j2\"] will result in templated file .env   #   E.g. [\"src\/.env.j2\"] will result in templated file src\/.env   - |     if [ -n \"${J2_TEMPLATES}\" ];     then       echo \"Templating ${J2_TEMPLATES}\"       ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook ci\/core\/ansible\/template.yaml -v     fi   # Show environment variables for debugging   - printenv   # Building and pushing im   - echo \"$CI_REGISTRY_PASSWORD\" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin   - &gt;     docker build     ${DOCKER_BUILD_EXTRA_ARGUMENTS}     -f \"${CI_PROJECT_DIR}\/${DOCKERFILE_PATH}\"     -t \"${IMAGE_TAG}-${APP_NAME}\"     ${DOCKERFILE_CONTEXT_PATH}   - docker push \"${IMAGE_TAG}-${APP_NAME}\"   variables:     DOCKER_BUILD_EXTRA_ARGUMENTS: \"\"     DOCKERFILE_PATH: Dockerfile     DOCKERFILE_CONTEXT_PATH: \".\"     DOCKER_BUILDKIT: \"1\"     IMAGE_TAG: \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}-${CI_PIPELINE_ID}\"     APP_NAME: \"main\"   rules:   # Do not run Build on MR   - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"     when: never   # Auto start for production Production branch   - if: $CI_COMMIT_REF_SLUG == \"master\"     when: on_success     variables:       ENV_NAME: prod       ENV_NAME_PREFIX: \"\"   # Auto start for production Development branch   - if: $CI_COMMIT_REF_SLUG == \"dev\"     when: on_success     variables:       ENV_NAME: dev       ENV_NAME_PREFIX: dev-   # Manual start for production Preview branches   - when: manual     variables:       ENV_NAME: pre       ENV_NAME_PREFIX: pre-${CI_COMMIT_REF_SLUG}-   tags:   - avant_gitlab_com<\/code><\/pre>\n<h4>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 Ansible Playbook<\/h4>\n<p>Playbook \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u0435\u043c \u043f\u043e \u043f\u0443\u0442\u0438 <code>ci\/core\/ansible\/template.yaml<\/code>. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 J2_TEMPLATES:<\/p>\n<pre><code class=\"cs\">- hosts: localhost   tasks:   - name: Parse J2_TEMPLATES     set_fact:       items: \"{{ lookup('env', 'J2_TEMPLATES') | from_yaml }}\"   - name: Templating items {{ lookup('env', 'J2_TEMPLATES') }}     template:       src: \"{{ lookup('env', 'CI_PROJECT_DIR') }}\/{{ item }}\"       dest: \"{{ lookup('env', 'CI_PROJECT_DIR') }}\/{{ item | regex_replace('.j2$') }}\"     loop: \"{{ items }}\"   - name: Print     debug:       msg: \"{{ lookup('file', lookup('env', 'CI_PROJECT_DIR') + '\/' + item | regex_replace('.j2$')) }}\"     loop: \"{{ items }}\"<\/code><\/pre>\n<h2>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0430\u0439\u043b \u0438 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u0412 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435 \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u0441\u044f \u043e\u0442 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0434\u0436\u043e\u0431\u044b \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0434\u0432\u0435 \u0434\u0436\u043e\u0431\u044b \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u0437 \u043d\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"cs\">Build frontend:   extends: .build   variables:     APP_NAME: \"frontend\"     DOCKERFILE_PATH: BlazorWasmUrlShortener\/Dockerfile     J2_TEMPLATES: \"['BlazorWasmUrlShortener\/wwwroot\/appsettings.json.j2']\" Build backend:   extends: .build   variables:     APP_NAME: \"backend\"     DOCKERFILE_PATH: BlazorWasmUrlShortenerApi\/Dockerfile     J2_TEMPLATES: \"['BlazorWasmUrlShortenerApi\/appsettings.json.j2']\"<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0434\u0436\u043e\u0431\u0435 \u043f\u0440\u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0444\u0430\u0439\u043b\u0430 <code>BlazorWasmUrlShortener\/wwwroot\/appsettings.json.j2<\/code> \u0432 <code>BlazorWasmUrlShortener\/wwwroot\/appsettings.json.<\/code> \u042d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e, \u0447\u0442\u043e\u0431\u044b \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0437\u043d\u0430\u043b, \u043d\u0430 \u043a\u0430\u043a\u043e\u043c \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043e\u043d \u0438 API. \u0412\u043e\u0442 \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0448\u0430\u0431\u043b\u043e\u043d \u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0430:<\/p>\n<pre><code class=\"cs\">{   \"AppUrl\": \"https:\/\/{{ lookup('env', 'ENV_NAME_PREFIX') }}shortener.avant-it.ru\",   \"ApiUrl\": \"https:\/\/{{ lookup('env', 'ENV_NAME_PREFIX') }}sapi.avant-it.ru\" }<\/code><\/pre>\n<p>\u0410 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0448\u0430\u0431\u043b\u043e\u043d \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0431\u0435\u043a\u0435\u043d\u0434\u0430:<\/p>\n<pre><code class=\"cs\">{% if lookup('env', 'ENV_NAME') == 'prod' %} {% set db_user = 'app' %} {% set db_password = lookup('env', 'PROD_DB_APP_PASSWORD') %} {% else %} {% set db_user = 'dev-app' %} {% set db_password = lookup('env', 'DEV_PRE_DB_APP_PASSWORD') %} {% endif %} {   \"Logging\": {     \"LogLevel\": {       \"Default\": \"Information\",       \"Microsoft.AspNetCore\": \"Warning\"     }   },   \"AllowedHosts\": \"*\",   \"ConnectionStrings\": {     \"Database\": \"Host=postgresql.devops:5432;Database={{ lookup('env', 'ENV_NAME_PREFIX') }}app;Username={{ db_user }};Password={{ db_password }};SSL Mode=Disable;Timeout=300;CommandTimeout=300;Include Error Detail=True;\"   } }<\/code><\/pre>\n<p>\u0412 \u043d\u0435\u043c \u0443\u0436\u0435 \u0432\u0438\u0434\u043d\u044b \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0445\u0438\u0442\u0440\u044b\u0435 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438.<\/p>\n<p>\u0421\u0443\u0442\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0432\u0443\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f: \u043e\u0434\u043d\u043e\u0433\u043e \u0434\u043b\u044f prod, \u0430 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438.<\/p>\n<h2>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0430\u0437\u044b \u0438\u0437 dev-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f<\/h2>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u0441\u0451 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u041f\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0442\u0430\u043d\u0443\u0442 \u043f\u043e\u043d\u044f\u0442\u043d\u044b \u043f\u0440\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0436\u043e\u0431\u044b \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>CI_COMMIT_REF_SLUG<\/code> \u0434\u043b\u044f \u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u0430\u0437\u044b. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0430\u043d\u0430\u043b\u043e\u0433 \u044d\u0442\u043e\u0439 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u0442\u0440\u043e\u043a:<\/p>\n<pre><code class=\"cs\"># Since we can not use gitlab's CI_COMMIT_REF_SLUG variable, we create our own - export SRC_BRANCH_SLUG=$(echo \"$CI_COMMIT_BRANCH\" | tr '[:upper:]' '[:lower:]' | sed 's\/[^[:alnum:]]+\/\/g') # To defend against branch names starting from number we add ref- prefix - export SRC_BRANCH_SLUG=ref-${SRC_BRANCH_SLUG}<\/code><\/pre>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043d\u044e\u0430\u043d\u0441: \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0434\u0436\u043e\u0431\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043f\u0440\u0438 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043d\u0443\u0436\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0430 \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u043b\u0430\u0441\u044c \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0431\u0430\u0437\u0443. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u0438:<\/p>\n<pre><code class=\"cs\">- export TARGET_DB_NAME=\"${ENV_NAME_PREFIX}app\" - |   if psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -lqt | cut -d | -f 1 | grep -qw \"${TARGET_DB_NAME}\"; then     echo \"Database already exist, skipping!\"     exit 0   fi<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u044d\u0442\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"cs\"># Dumping dev db - pg_dump -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -j 10 -F directory -f \/tmp\/app ${DEV_DB_NAME} - ls -lah \/tmp\/app # Creating preview env database - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"CREATE DATABASE \"${TARGET_DB_NAME}\"\" - pg_restore -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -j 10 -F directory -d \"${TARGET_DB_NAME}\" \/tmp\/app<\/code><\/pre>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 dev-\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f:<\/p>\n<pre><code class=\"cs\"># Create dev user - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"CREATE ROLE \"${DB_DEV_DB_USER}\" WITH LOGIN CREATEDB CREATEROLE NOREPLICATION ENCRYPTED PASSWORD '${DEV_PRE_DB_APP_PASSWORD}';\" || true - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"ALTER ROLE \"${DB_DEV_DB_USER}\" WITH ENCRYPTED PASSWORD '${DEV_PRE_DB_APP_PASSWORD}';\"<\/code><\/pre>\n<p>\u0418, \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439, \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0440\u0430\u0432\u0430 \u043d\u0430 \u0431\u0430\u0437\u0443:<\/p>\n<pre><code class=\"cs\"># Fixing permisions - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"ALTER DATABASE \"${TARGET_DB_NAME}\" OWNER TO \"${DB_DEV_DB_USER}\";\" - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d ${TARGET_DB_NAME} -c \"GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA \"public\" TO \"${DB_DEV_DB_USER}\";\" - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d ${TARGET_DB_NAME} -c \"REASSIGN OWNED BY app TO \"${DB_DEV_DB_USER}\";\"<\/code><\/pre>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u0430\u043a\u0443\u044e \u0434\u0436\u043e\u0431\u0443:<\/p>\n<pre><code class=\"cs\">Copy Dev Db to Pre Db:   extends: .base   stage: provision   script:   # Since we can not use gitlab's SLUG variable, we create our own   - export SRC_BRANCH_SLUG=$(echo \"$CI_COMMIT_BRANCH\" | tr '[:upper:]' '[:lower:]' | sed 's\/[^[:alnum:]]+\/\/g')   # To defend against branch names starting from number we add ref- prefix   - export SRC_BRANCH_SLUG=ref-${SRC_BRANCH_SLUG}   - echo \"Extracted SRC_BRANCH_SLUG=${SRC_BRANCH_SLUG}\"   - |     if [ $ENV_NAME = \"prod\" ]; then       export ENV_NAME_PREFIX=\"\"     elif [ $ENV_NAME = \"dev\" ]; then       export ENV_NAME_PREFIX=\"dev-\"     else       export ENV_NAME_PREFIX=\"pre-${SRC_BRANCH_SLUG}-\"     fi   - echo \"Extracted ENV_NAME_PREFIX=${ENV_NAME_PREFIX}\"   - export TARGET_DB_NAME=\"${ENV_NAME_PREFIX}app\"   - |     if psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -lqt | cut -d | -f 1 | grep -qw \"${TARGET_DB_NAME}\"; then       echo \"Database already exist, skipping!\"       exit 0     fi   # Dumping dev db   - pg_dump -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -j 10 -F directory -f \/tmp\/app ${DEV_DB_NAME}   - ls -lah \/tmp\/app   # Creating preview env database   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"CREATE DATABASE \"${TARGET_DB_NAME}\"\"   - pg_restore -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -j 10 -F directory -d \"${TARGET_DB_NAME}\" \/tmp\/app   # Create dev user   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"CREATE ROLE \"${DB_DEV_DB_USER}\" WITH LOGIN CREATEDB CREATEROLE NOREPLICATION ENCRYPTED PASSWORD '${DEV_PRE_DB_APP_PASSWORD}';\" || true   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"ALTER ROLE \"${DB_DEV_DB_USER}\" WITH ENCRYPTED PASSWORD '${DEV_PRE_DB_APP_PASSWORD}';\"   # Fixing permisions   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"ALTER DATABASE \"${TARGET_DB_NAME}\" OWNER TO \"${DB_DEV_DB_USER}\";\"   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d ${TARGET_DB_NAME} -c \"GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA \"public\" TO \"${DB_DEV_DB_USER}\";\"   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d ${TARGET_DB_NAME} -c \"REASSIGN OWNED BY app TO \"${DB_DEV_DB_USER}\";\"   rules:   - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"     when: never   - if: $CI_COMMIT_REF_SLUG == \"master\"     when: never   - if: $CI_COMMIT_REF_SLUG == \"dev\"     when: never   - when: on_success   variables:     DB_DEV_DB_USER: \"dev-app\"   resource_group: provision<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0443: <code>resource_group: provision<\/code>. \u041e\u043d\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u0436\u043e\u0431\u0430 \u043d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u043b\u0430\u0441\u044c \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0434\u0432\u0443\u0445 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u043e\u0432.<\/p>\n<h2>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0434\u0435\u043f\u043b\u043e\u0439<\/h2>\n<p>\u0417\u0434\u0435\u0441\u044c \u043a\u043e\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0431\u043e\u043b\u044c\u0448\u0435.<\/p>\n<h4>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0432\u0435\u0440\u0441\u0438\u0439 Helm \u0438 kubectl<\/h4>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0432\u0435\u0440\u0441\u0438\u0438 Helm \u0438 kubectl, \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0435 \u0441 \u043d\u0430\u0448\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 (1.31). \u0418\u043d\u0430\u0447\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u0442\u044c\u0441\u044f \u0441 \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u0431\u0430\u0437\u043e\u0432\u043e\u043c \u043e\u0431\u0440\u0430\u0437\u0435 \u0435\u0441\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 <code>\/root\/<\/code><a href=\"http:\/\/load-kube-version.sh\" rel=\"noopener noreferrer nofollow\"><code>load-kube-version.sh<\/code><\/a>. \u0417\u0430\u0442\u0435\u043c \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c kube-\u043a\u043e\u043d\u0444\u0438\u0433, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u0432\u0438\u0434\u0435 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439, \u0432 <code>\/root\/.kube\/config<\/code>, \u0447\u0442\u043e\u0431\u044b Helm \u043c\u043e\u0433 \u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u0412\u0441\u0435 \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c\u0438 \u0441\u0442\u0440\u043e\u043a\u0430\u043c\u0438:<\/p>\n<pre><code class=\"cs\"># Load helm and kubectl for our kubernetes version - \/root\/load-kube-version.sh 1.31 # Copy kubeconfig from KUBECONFIG !file! variable to well-known location - mkdir \/root\/.kube - cat ${KUBECONFIG} &gt; \/root\/.kube\/config # If J2_TEMPLATES variable (in yaml string array format) is defined, we template every file name #   E.g. [\".env.j2\"] will result in templated file .env #   E.g. [\"src\/.env.j2\"] will result in templated file src\/.env - |     if [ -n \"${J2_TEMPLATES}\" ];     then         echo \"Templating ${J2_TEMPLATES}\"         ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook ci\/core\/ansible\/template.yaml -v     fi<\/code><\/pre>\n<h4>\u041f\u043e\u0434\u0433\u0440\u0443\u0437\u043a\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f Helm-\u0447\u0430\u0440\u0442\u0430<\/h4>\n<p>\u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u0439\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445:<\/p>\n<ul>\n<li>\n<p><code>ci\/values.yaml[.j2]<\/code> \u2014 \u043e\u0431\u0449\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<li>\n<p><code>ci\/APP_NAME.values.yaml[.j2]<\/code> \u2014 \u0447\u0430\u0441\u0442\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p><code>ci\/ENV_NAME\/values.yaml[.j2]<\/code> \u2014 \u0447\u0430\u0441\u0442\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u043d\u0435\u043c).<\/p>\n<\/li>\n<li>\n<p><code>ci\/ENV_NAME\/APP_NAME.values.yaml[.j2]<\/code> \u2014 \u0447\u0430\u0441\u0442\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043b\u044e\u0447\u0435\u0439 \u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442 \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u0449\u0438\u0435. \u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/062\/3cf\/3e0\/0623cf3e03827b2c724b8158c9799a5c.png\" alt=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438\" title=\"\u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438\" width=\"1311\" height=\"751\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/062\/3cf\/3e0\/0623cf3e03827b2c724b8158c9799a5c.png\"\/><\/p>\n<div><figcaption>\u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/figcaption><\/div>\n<\/figure>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b \u043c\u043e\u0436\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 <code>.j2<\/code>, \u0438 \u0442\u043e\u0433\u0434\u0430 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d. \u042d\u0442\u043e \u0443\u0434\u043e\u0431\u043d\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u0432 Helm-\u0447\u0430\u0440\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f Ingress, \u0438 \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c \u0432 \u043d\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0432\u0435\u0442\u043a\u0438. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>ENV_NAME_PREFIX<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0438 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0435\u0451 \u043f\u0435\u0440\u0435\u0434 \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c\u043e\u043c.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 <code>frontend.values.yaml.j2<\/code>:<\/p>\n<pre><code class=\"cs\">nginxIngress:   enable: true   ingresses:   - className: \"nginx\"     hostname: \"{{ lookup('env', 'ENV_NAME_PREFIX') }}shortener.avant-it.ru\"     paths:     - path: \/       pathType: Prefix       servicePort: 5000<\/code><\/pre>\n<p>\u0410 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u043a\u043e\u0434\u0430, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0439 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u044d\u0442\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"cs\"># Sometimes you need to pass some gitlab var to app's env variables. In that case you can user .j2 values files   #   and add somewhere inside them something like: {{ lookup('env', 'CI_COMMIT_BTANCH') }}   - |     if [ -e .\/ci\/values.yaml.j2 ]     then       echo \"Templating .\/ci\/values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/values.yaml.j2 dest=.\/ci\/values.yaml\"     fi   - |     if [ -e .\/ci\/${APP_NAME}.values.yaml.j2 ]     then       echo \"Templating .\/ci\/${APP_NAME}.values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/${APP_NAME}.values.yaml.j2 dest=.\/ci\/${APP_NAME}.values.yaml\" || true     fi   - |     if [ -e .\/ci\/${ENV_NAME}\/values.yaml.j2 ]     then       echo \"Templating .\/ci\/${ENV_NAME}\/values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/${ENV_NAME}\/values.yaml.j2 dest=.\/ci\/${ENV_NAME}\/values.yaml\" || true     fi   - |     if [ -e .\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml.j2 ]     then       echo \"Templating .\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml.j2 dest=.\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml\" || true     fi   - |     helm upgrade      --install      --create-namespace      --namespace=${ENV_NAME}-${SRC_BRANCH_SLUG}      --set image=${IMAGE_TAG}-${APP_NAME}      --set imagePullCredentials.url=${CI_REGISTRY}      --set imagePullCredentials.user=${CI_REGISTRY_USER}      --set imagePullCredentials.password=${CI_REGISTRY_PASSWORD}      --set appName=${APP_NAME}      ${HELM_EXTRA_ARGUMENT}      --wait=${HELM_WAIT}      --timeout=${HELM_TIMEOUT}      --atomic=${HELM_ATOMIC}      ${APP_NAME}-${CI_PROJECT_ID}      ${HELM_CHART_PATH}      -f .\/ci\/values.yaml      -f .\/ci\/${APP_NAME}.values.yaml      -f .\/ci\/${ENV_NAME}\/values.yaml      -f .\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml<\/code><\/pre>\n<h4>\u041a\u043e\u0434 \u0434\u0436\u043e\u0431\u044b<\/h4>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0434\u0436\u043e\u0431\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a: <\/p>\n<pre><code class=\"cs\">.deploy:   extends: .base   stage: deploy   script:   # Since we can not use gitlab's SLUG variable, we create our own   - export SRC_BRANCH_SLUG=$(echo \"$CI_COMMIT_BRANCH\" | tr '[:upper:]' '[:lower:]' | sed 's\/[^[:alnum:]]+\/\/g')   # To defend against branch names starting from number we add ref- prefix   - export SRC_BRANCH_SLUG=ref-${SRC_BRANCH_SLUG}   - echo \"Extracted SRC_BRANCH_SLUG=${SRC_BRANCH_SLUG}\"   - |     if [ $ENV_NAME = \"prod\" ]; then       export ENV_NAME_PREFIX=\"\"     elif [ $ENV_NAME = \"dev\" ]; then       export ENV_NAME_PREFIX=\"dev-\"     else       export ENV_NAME_PREFIX=\"pre-${SRC_BRANCH_SLUG}-\"     fi   - echo \"Extracted ENV_NAME_PREFIX=${ENV_NAME_PREFIX}\"   # Load helm and kubectl for our kubernetes version   - \/root\/load-kube-version.sh 1.31   # Copy kubeconfig from KUBECONFIG !file! variable to well-known location   - mkdir \/root\/.kube   - cat ${KUBECONFIG} &gt; \/root\/.kube\/config   # If J2_TEMPLATES variable (in yaml string array format) is defined, we template every file name   #   E.g. [\".env.j2\"] will result in templated file .env   #   E.g. [\"src\/.env.j2\"] will result in templated file src\/.env   - |     if [ -n \"${J2_TEMPLATES}\" ];     then       echo \"Templating ${J2_TEMPLATES}\"       ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook ci\/core\/ansible\/template.yaml -v     fi   # Sometimes you need to pass some gitlab var to app's env variables. In that case you can user .j2 values files   #   and add somewhere inside them something like: {{ lookup('env', 'CI_COMMIT_BTANCH') }}   - |     if [ -e .\/ci\/values.yaml.j2 ]     then       echo \"Templating .\/ci\/values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/values.yaml.j2 dest=.\/ci\/values.yaml\"     fi   - |     if [ -e .\/ci\/${APP_NAME}.values.yaml.j2 ]     then       echo \"Templating .\/ci\/${APP_NAME}.values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/${APP_NAME}.values.yaml.j2 dest=.\/ci\/${APP_NAME}.values.yaml\" || true     fi   - |     if [ -e .\/ci\/${ENV_NAME}\/values.yaml.j2 ]     then       echo \"Templating .\/ci\/${ENV_NAME}\/values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/${ENV_NAME}\/values.yaml.j2 dest=.\/ci\/${ENV_NAME}\/values.yaml\" || true     fi   - |     if [ -e .\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml.j2 ]     then       echo \"Templating .\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml.j2\"       ansible localhost -m ansible.builtin.template -a \"src=.\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml.j2 dest=.\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml\" || true     fi   - |     helm upgrade      --install      --create-namespace      --namespace=${ENV_NAME}-${SRC_BRANCH_SLUG}      --set image=${IMAGE_TAG}-${APP_NAME}      --set imagePullCredentials.url=${CI_REGISTRY}      --set imagePullCredentials.user=${CI_REGISTRY_USER}      --set imagePullCredentials.password=${CI_REGISTRY_PASSWORD}      --set appName=${APP_NAME}      ${HELM_EXTRA_ARGUMENT}      --wait=${HELM_WAIT}      --timeout=${HELM_TIMEOUT}      --atomic=${HELM_ATOMIC}      ${APP_NAME}-${CI_PROJECT_ID}      ${HELM_CHART_PATH}      -f .\/ci\/values.yaml      -f .\/ci\/${APP_NAME}.values.yaml      -f .\/ci\/${ENV_NAME}\/values.yaml      -f .\/ci\/${ENV_NAME}\/${APP_NAME}.values.yaml   rules:   # Do not run on MR   - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"     when: never   # Auto start after previous stage for Production branch   - if: $CI_COMMIT_REF_SLUG == \"master\"     when: on_success     variables:       ENV_NAME: prod   # Auto start after previous stage for Development branch   - if: $CI_COMMIT_REF_SLUG == \"dev\"     when: on_success     variables:       ENV_NAME: dev   # Auto start after previous stage for Preview branches   - when: on_success     variables:       ENV_NAME: pre   variables:     IMAGE_TAG: \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}-${CI_PIPELINE_ID}\"     APP_NAME: \"main\"     HELM_WAIT: \"true\"     HELM_ATOMIC: \"true\"     HELM_TIMEOUT: \"3m\"     HELM_CHART_PATH: \"ci\/core\/helm-charts\/application\"   environment:     name: ${ENV_NAME}-${CI_COMMIT_REF_SLUG}     on_stop: Destory environment<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0444\u043b\u0430\u0433 <code>--atomic <\/code>\u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u0447\u0435\u0440\u0435\u0437 Helm. \u041e\u043d \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u0442\u043a\u0430\u0442 \u043d\u0430 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u0440\u0430\u0448\u0438\u0442\u0441\u044f \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435.<\/p>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u0434\u0436\u043e\u0431\u044b \u043d\u0430 \u0434\u0435\u043f\u043b\u043e\u0439 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>.gitlab-ci.yml<\/code> \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u043e:<\/p>\n<pre><code class=\"cs\">Deploy frontend:   extends: .deploy   variables:     APP_NAME: \"frontend\" Deploy backend:   extends: .deploy   variables:     APP_NAME: \"backend\"<\/code><\/pre>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043b\u043e\u0433\u0438\u043a\u0443 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439<\/h2>\n<p>\u0421\u0430\u043c\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430: \u043c\u044b \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0432\u0441\u0435 Helm-\u0440\u0435\u043b\u0438\u0437\u044b \u0432 \u043d\u0435\u0439\u043c\u0441\u043f\u0435\u0439\u0441\u0435, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u043c \u0434\u043b\u044f \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0437\u0430\u0442\u0435\u043c \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0430\u043c \u043d\u0435\u0439\u043c\u0441\u043f\u0435\u0439\u0441, \u0430 \u043f\u043e\u0441\u043b\u0435 \u2014 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041e\u0434\u043d\u0430\u043a\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f Merge Request (MR) \u2014 \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430.<\/p>\n<p>\u041f\u0440\u0438 \u043c\u0435\u0440\u0434\u0436\u0435 \u0432\u0435\u0442\u043a\u0438 GitLab \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u043e\u043c\u043c\u0438\u0442, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d, \u043d\u043e \u043e\u043d \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0432\u0435\u0442\u043a\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u043b\u0430 \u0431\u044b, \u0438\u0437 \u043a\u0430\u043a\u043e\u0439 \u0432\u0435\u0442\u043a\u0438 \u0431\u044b\u043b \u0441\u0434\u0435\u043b\u0430\u043d \u043c\u0435\u0440\u0436. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0432\u0435\u0442\u043a\u0438 \u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u0438 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0443 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u043c\u0435\u0440\u0436\u0435 \u0432 \u043c\u0430\u0441\u0442\u0435\u0440 \u0434\u0436\u043e\u0431\u0430 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e \u043d\u0435 \u0443\u0434\u0430\u043b\u0438\u043b\u0430 prod.<\/p>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438, \u0438\u043c\u0435\u043d\u043d\u043e \u0438\u0437-\u0437\u0430 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u044d\u0442\u0430 \u0434\u0436\u043e\u0431\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435 \u0432 \u0442\u043e\u0439 \u0432\u0435\u0442\u043a\u0435, \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u043d\u0430 \u0443\u0434\u0430\u043b\u044f\u0435\u0442, \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>SRC_BRANCH_SLUG<\/code> \u0432 \u044d\u0442\u043e\u0439 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0434\u0436\u043e\u0431\u0430\u0445.<\/p>\n<h4>\u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u043c\u044f \u0432\u0435\u0442\u043a\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f<\/h4>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u043c\u044f \u0432\u0435\u0442\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0431\u0443\u0434\u0435\u043c \u0443\u0434\u0430\u043b\u044f\u0442\u044c:<\/p>\n<pre><code class=\"cs\"># Parse string like: Merge branch 'feature' into 'dev' - export SRC_BRANCH=$(echo $CI_COMMIT_TITLE | grep -oE \"branch.+into\" | tr -d \"'\" | grep -oE \" .+ \" | tr -d \" \") - echo \"Extracted SRC_BRANCH=${SRC_BRANCH}\" # Since we can not use gitlab's SLUG variable, we create our own - export SRC_BRANCH_SLUG=$(echo \"$SRC_BRANCH\" | tr '[:upper:]' '[:lower:]' | sed 's\/[^[:alnum:]]+\/\/g') # To defend against branch names starting from number we add ref- prefix - export SRC_BRANCH_SLUG=ref-${SRC_BRANCH_SLUG}<\/code><\/pre>\n<h4>\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435<\/h4>\n<p>\u0412 \u043a\u043e\u0434\u0435 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c <code>|| true<\/code> \u0432 \u043a\u043e\u043d\u0446\u0435 \u043a\u0430\u0436\u0434\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u0436\u043e\u0431\u0443 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u043d\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439:<\/p>\n<pre><code class=\"cs\"># Delete all helm releases in that namespace in case (useful if helm chart contains cluster scoped resources that will not be deleted on namespace deletion) - (helm ls -a -n pre-${SRC_BRANCH_SLUG} --max 1000 | grep -v \"UPDATED\" | awk '{ print $2 \" \" $1 }' | xargs -n 2 helm uninstall -n) || true # Delete kubernetes namespace - kubectl delete ns pre-${SRC_BRANCH_SLUG} || true # Delete database - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"DROP DATABASE IF EXISTS \"pre-${SRC_BRANCH_SLUG}-app\" WITH (FORCE);\" || true<\/code><\/pre>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434:<\/p>\n<pre><code class=\"cs\">Destory environment:   extends: .base   stage: manage   needs: []   script:   # Parse string like: Merge branch 'feature' into 'dev'   - export SRC_BRANCH=$(echo $CI_COMMIT_TITLE | grep -oE \"branch.+into\" | tr -d \"'\" | grep -oE \" .+ \" | tr -d \" \")   - echo \"Extracted SRC_BRANCH=${SRC_BRANCH}\"   # Since we can not use gitlab's SLUG variable, we create our own   - export SRC_BRANCH_SLUG=$(echo \"$SRC_BRANCH\" | tr '[:upper:]' '[:lower:]' | sed 's\/[^[:alnum:]]+\/\/g')   # To defend against branch names starting from number we add ref- prefix   - export SRC_BRANCH_SLUG=ref-${SRC_BRANCH_SLUG}   - echo \"Extracted SRC_BRANCH_SLUG=${SRC_BRANCH_SLUG}\"   - |     if [ $ENV_NAME = \"prod\" ]; then       export ENV_NAME_PREFIX=\"\"     elif [ $ENV_NAME = \"dev\" ]; then       export ENV_NAME_PREFIX=\"dev-\"     else       export ENV_NAME_PREFIX=\"pre-${SRC_BRANCH_SLUG}-\"     fi   - echo \"Extracted ENV_NAME_PREFIX=${ENV_NAME_PREFIX}\"   # Load helm and kubectl for our kubernetes version   - \/root\/load-kube-version.sh 1.30   - mkdir \/root\/.kube   - cat ${KUBECONFIG} &gt; \/root\/.kube\/config   # Delete all helm releases in that namespace in case (usefull if helm chart contains cluster scoped resources that will not be deleted on namespace deletion)   - (helm ls -a -n pre-${SRC_BRANCH_SLUG} --max 1000 | grep -v \"UPDATED\" | awk '{ print $2 \" \" $1 }' | xargs -n 2 helm uninstall -n) || true   # Delete kubernetes namespace   - kubectl delete ns pre-${SRC_BRANCH_SLUG} || true   # Delete database   - psql -h ${DB_HOST} -U ${PGPASSWORD_USER} -p ${DB_PORT} -d postgres -c \"DROP DATABASE IF EXISTS \"pre-${SRC_BRANCH_SLUG}-app\" WITH (FORCE);\" || true   environment:     name: ${ENV_NAME}-${CI_COMMIT_REF_SLUG}     action: stop   rules:   # Always run on commits that are created after merge in Production branch   - if: '$CI_COMMIT_BRANCH == \"master\" &amp;&amp; $CI_COMMIT_TITLE =~ \/^Merge branch .+ into .master.$\/'     when: always   # Always run on commits that are created after merge in Development branch   - if: '$CI_COMMIT_BRANCH == \"dev\" &amp;&amp; $CI_COMMIT_TITLE =~ \/^Merge branch .+ into .dev.$\/'     when: always   # Never run on MR   - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"     when: never   # Never run on Production and Development branches itself   - if: $CI_COMMIT_REF_SLUG == \"master\"     when: never   - if: $CI_COMMIT_REF_SLUG == \"dev\"     when: never   # Allow manuall run for Preview branches   - when: manual     variables:       # Since we extract branch name from CI_COMMIT_TITLE, we have to send fake CI_COMMIT_TITLE =)       #   That's dirty hack to simplify code =)       CI_COMMIT_TITLE: Merge branch '$CI_COMMIT_BRANCH' into 'nothing'<\/code><\/pre>\n<h2>\u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/h2>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/38e\/580\/8ec\/38e5808ec66aca2a7efc333c5b178788.png\" alt=\"\u041a\u0430\u0440\u0442\u0438\u043d\u0430 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0432\u043a\u0430\u0442\u043a\u0438 feature-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\" title=\"\u041a\u0430\u0440\u0442\u0438\u043d\u0430 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0432\u043a\u0430\u0442\u043a\u0438 feature-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\" width=\"3351\" height=\"1019\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/38e\/580\/8ec\/38e5808ec66aca2a7efc333c5b178788.png\"\/><\/p>\n<div><figcaption>\u041a\u0430\u0440\u0442\u0438\u043d\u0430 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0432\u043a\u0430\u0442\u043a\u0438 feature-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b0d\/acc\/632\/b0dacc6321590a2feb9de74acf58dde1.png\" alt=\"\u041f\u043e\u0441\u043b\u0435 \u0432\u043b\u0438\u0432\u0430\u043d\u0438\u044f \u0432 dev, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 master\" title=\"\u041f\u043e\u0441\u043b\u0435 \u0432\u043b\u0438\u0432\u0430\u043d\u0438\u044f \u0432 dev, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 master\" width=\"3345\" height=\"953\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b0d\/acc\/632\/b0dacc6321590a2feb9de74acf58dde1.png\"\/><\/p>\n<div><figcaption>\u041f\u043e\u0441\u043b\u0435 \u0432\u043b\u0438\u0432\u0430\u043d\u0438\u044f \u0432 dev, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 master<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/02b\/313\/bca\/02b313bca3f90b502e7db4442f320326.png\" alt=\"\u041f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0432 feature-\u0432\u0435\u0442\u043a\u0435\" title=\"\u041f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0432 feature-\u0432\u0435\u0442\u043a\u0435\" width=\"2037\" height=\"441\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/02b\/313\/bca\/02b313bca3f90b502e7db4442f320326.png\"\/><\/p>\n<div><figcaption>\u041f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0432 feature-\u0432\u0435\u0442\u043a\u0435<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a55\/b25\/0fe\/a55b250fec1803f30193c30ba6b58ece.png\" alt=\"\u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 Kubernetes\" title=\"\u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 Kubernetes\" width=\"3528\" height=\"1655\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a55\/b25\/0fe\/a55b250fec1803f30193c30ba6b58ece.png\"\/><\/p>\n<div><figcaption>\u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 Kubernetes<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/582\/e24\/062\/582e24062b8d97f8405c8add98825abb.png\" alt=\"\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445\" title=\"\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445\" width=\"1507\" height=\"1273\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/582\/e24\/062\/582e24062b8d97f8405c8add98825abb.png\"\/><\/p>\n<div><figcaption>\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445<\/figcaption><\/div>\n<\/figure>\n<h2>\u0412\u043c\u0435\u0441\u0442\u043e \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f<\/h2>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443: <a href=\"https:\/\/gitlab.com\/avantit1\/CiCdWithFeatureBranchesP1\" rel=\"noopener noreferrer nofollow\">https:\/\/gitlab.com\/avantit1\/CiCdWithFeatureBranchesP1<\/a><\/p>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u0431\u044b\u0442\u044c, \u0435\u0441\u043b\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\u0445 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0438 \u0431\u0435\u043a\u0435\u043d\u0434).<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/892512\/\"> https:\/\/habr.com\/ru\/articles\/892512\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041c\u043d\u043e\u0433\u0438\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0434 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435\u043c \u0432 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b: <strong>prod<\/strong>, <strong>dev <\/strong>\u0438\u043b\u0438 <strong>staging<\/strong>. \u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0443\u043c \u2014 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432. \u041e\u0434\u043d\u0430\u043a\u043e, \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430, \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0447\u0430\u0441\u0442\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442. \u0412 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043b\u043e\u0433\u0438\u0447\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u0435\u043f\u043b\u043e\u044f \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0432\u0435\u0442\u043e\u043a \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0434 \u043c\u0435\u0440\u0436\u0435\u043c. \u0425\u043e\u0442\u044f \u044d\u0442\u0430 \u0438\u0434\u0435\u044f \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u0432\u044f\u0437\u0430\u043d\u0430 \u0441 \u0440\u044f\u0434\u043e\u043c \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0438\u0437\u0432\u043d\u0435, \u0447\u0442\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 DNS-\u0437\u0430\u043f\u0438\u0441\u0435\u0439. \u041a\u0430\u043a \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e?<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0447\u0438\u0441\u0442\u043a\u0443 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0438\u0445 \u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0445\u043b\u0430\u043c\u043b\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b?<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441 \u0434\u0440\u0443\u0433\u0438\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u044b?<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u043c\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c \u0438 \u0442.\u0434.)?<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043e\u0442\u0432\u0435\u0447\u0443 \u043d\u0430 \u044d\u0442\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0443 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 Feature \u0441\u0442\u0435\u043d\u0434\u043e\u0432 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 CI\/CD.<\/p>\n<h3>\u0412\u0432\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435<\/h3>\n<p>\u0414\u043b\u044f \u0446\u0435\u043b\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0443\u0441\u043b\u043e\u0432\u0438\u0439: <\/p>\n<p>\u0415\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0435 \u043d\u0430 C# (\u043f\u043e \u043c\u043d\u0435\u043d\u0438\u044e \u0430\u0432\u0442\u043e\u0440\u0430, \u043b\u0443\u0447\u0448\u0438\u0439 \u044f\u0437\u044b\u043a \u0432 \u043c\u0438\u0440\u0435 =)).<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 URL Shortener.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f PostgreSQL.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u043d\u0430 WebAssembly \u0438 API.<\/p>\n<\/li>\n<li>\n<p>\u0423 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043d\u0430 \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c API.<\/p>\n<\/li>\n<li>\n<p>\u041c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u043c\u0435\u0441\u0442\u043e \u0441\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0436\u0438\u0437\u043d\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f CI\/CD \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f GitLab \u0432\u0435\u0440\u0441\u0438\u0438 17.<\/p>\n<ul>\n<li>\n<p>\u0421\u0431\u043e\u0440\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 Docker Runner, \u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f GitLab.<\/p>\n<\/li>\n<li>\n<p>\u0414\u0435\u043f\u043b\u043e\u0439 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Kubernetes \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Helm.<\/p>\n<\/li>\n<li>\n<p>DNS-\u0437\u0430\u043f\u0438\u0441\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Cloudflare. \u041e\u0434\u043d\u0430\u043a\u043e \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c External DNS, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c\u0438 DNS-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430\u043c\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u0442\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u0432\u0430\u0448 \u0432\u044b\u0431\u043e\u0440 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e feature-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439, \u043d\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043d\u0443\u0436\u043d\u044b dev \u0438 prod \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<h4>\u0427\u0443\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438<\/h4>\n<p>\u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437:<\/p>\n<ul>\n<li>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u043d\u0430 Blazor Wasm 8 (C#).<\/p>\n<\/li>\n<li>\n<p>\u0411\u0435\u043a\u0435\u043d\u0434\u0430 \u043d\u0430 <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a> Core 8 (C#).<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 (\u043f\u043e\u0441\u043b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438).<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0444\u0430\u0439\u043b <code>appsettings.json<\/code>, \u0433\u0434\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c\u044b API \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d \u0432 \u043f\u0430\u043f\u043a\u0435 <code>BlazorWasmUrlShortener<\/code>, \u0442\u0430\u043c \u0436\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f Dockerfile.<\/p>\n<\/li>\n<\/ul>\n<p>\u0411\u0435\u043a\u0435\u043d\u0434:<\/p>\n<ul>\n<li>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 PostgreSQL \u0447\u0435\u0440\u0435\u0437 ORM Entity Framework.<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0430\u0439\u043b\u0430 <code>appsettings.json<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0440\u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u0430\u0437\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 <code>BlazorWasmUrlShortener.Api.<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0435 <code>BlazorWasmUrlShortener.ApiModels<\/code> \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c API-\u043c\u043e\u0434\u0435\u043b\u0435\u0439.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0422\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435<\/figcaption><\/div>\n<\/figure>\n<h3>\u041f\u0435\u0440\u0432\u043e\u0435 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p><strong>\u041d\u0430\u0431\u0440\u043e\u0441\u043e\u043a \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430<\/strong><\/p>\n<p>\u041d\u0430\u0448 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u0447\u0435\u0442\u044b\u0440\u0435\u0445 \u0441\u0442\u0430\u0434\u0438\u0439:<\/p>\n<ol>\n<li>\n<p>Build \u2014 \u0441\u0431\u043e\u0440\u043a\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u043e\u0432 (\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0436\u043e\u0431\u044b \u0434\u043b\u044f \u0431\u0435\u043a\u0435\u043d\u0434\u0430 \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430).<\/p>\n<\/li>\n<li>\n<p>Provision \u2014 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<\/li>\n<li>\n<p>Deploy \u2014 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 Kubernetes (\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0436\u043e\u0431\u044b \u0434\u043b\u044f \u0431\u0435\u043a\u0435\u043d\u0434\u0430 \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430).<\/p>\n<\/li>\n<li>\n<p>Manage \u2014 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u043e\u043f\u0440\u043e\u0441 \u0441 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439 \u0440\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0436\u043e\u0431\u044b, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u043c\u0435\u0440\u0434\u0436\u0430 \u0432\u0435\u0442\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 Merge Request (MR).<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430<\/figcaption><\/div>\n<\/figure>\n<h2>\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f<\/h2>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0434\u0432\u0430 \u0444\u0430\u0439\u043b\u0430:<\/p>\n<ol>\n<li>\n<p>.base.gitlab-ci.yml \u2014 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0431\u0449\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0434\u0436\u043e\u0431 \u0434\u043b\u044f \u0441\u0442\u0430\u0434\u0438\u0439 build \u0438 deploy.<\/p>\n<\/li>\n<li>\n<p>.gitlab-ci.yml \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0430\u0439\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0447\u0435\u0440\u0435\u0437 <code>include: .base.gitlab-ci.yml<\/code> \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u0435\u0440\u0432\u044b\u0439 \u0444\u0430\u0439\u043b.<\/p>\n<\/li>\n<\/ol>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 CI, \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u0432 \u043f\u0430\u043f\u043a\u0435 <code>ci<\/code>, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0437\u0430\u0441\u043e\u0440\u044f\u0442\u044c \u043a\u043e\u0440\u0435\u043d\u044c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f. \u0412 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438:<\/p>\n<ul>\n<li>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0443 <code>ci\/core\/helm-charts\/application<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 Helm-\u0447\u0430\u0440\u0442 \u0434\u043b\u044f \u043d\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 (\u043e\u043d \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u0435\u043d, \u0438 \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445).<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u0444\u0430\u0439\u043b\u044b \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0447\u0430\u0440\u0442\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0445 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0430\u0441\u0442\u043e.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\">\n<div><figcaption>\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0447\u0430\u0440\u0442\u0430 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0430\u043f\u043a\u0435<\/figcaption><\/div>\n<\/figure>\n<h2>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b<\/h2>\n<blockquote>\n<p><strong>\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/strong> \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043d\u0438\u0436\u0435, \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043d\u0435 \u0433\u043e\u0434\u044f\u0442\u0441\u044f \u0434\u043b\u044f production-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u042f \u0440\u0435\u0448\u0438\u043b \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0445 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u044b \u043a\u0430\u0440\u0442\u0438\u043d\u044b.<\/p>\n<\/blockquote>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0435\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 Kubernetes-\u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u0430\u0448\u0438\u043d\u0430 \u043d\u0430 \u0431\u0430\u0437\u0435 Ubuntu Server 22.04 \u0441 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u043c IP, 4 \u044f\u0434\u0440\u0430\u043c\u0438, 8 GiB \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u0438 80 GiB \u0434\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430. \u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0430\u0448\u0438\u043d\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u043d\u0435\u0439 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c CRI-O (\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043d\u044b\u0439 \u0440\u0430\u043d\u0442\u0430\u0439\u043c, \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043d\u0430 Docker, \u043d\u043e \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Kubernetes):<\/p>\n<pre><code class=\"cs\">sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common -y export OS=xUbuntu_22.04 export CRIO_VERSION=1.24 echo \"deb https:\/\/download.opensuse.org\/repositories\/devel:\/kubic:\/libcontainers:\/stable\/$OS\/ \/\"| sudo tee \/etc\/apt\/sources.list.d\/devel:kubic:libcontainers:stable.list echo \"deb http:\/\/download.opensuse.org\/repositories\/devel:\/kubic:\/libcontainers:\/stable:\/cri-o:\/$CRIO_VERSION\/$OS\/ \/\"|sudo tee \/etc\/apt\/sources.list.d\/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION.list curl -L https:\/\/download.opensuse.org\/repositories\/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION\/$OS\/Release.key | sudo apt-key add - curl -L https:\/\/download.opensuse.org\/repositories\/devel:\/kubic:\/libcontainers:\/stable\/$OS\/Release.key | sudo apt-key add - sudo apt update sudo apt install cri-o cri-o-runc -y sudo systemctl start crio sudo systemctl enable crio sudo systemctl status crio<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c Kubernetes \u0447\u0435\u0440\u0435\u0437 kubeadm \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c kubectl \u0438 helm:<\/p>\n<pre><code class=\"cs\">sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg sudo mkdir -p -m 755 \/etc\/apt\/keyrings curl -fsSL https:\/\/pkgs.k8s.io\/core:\/stable:\/v1.32\/deb\/Release.key | sudo gpg --dearmor -o \/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg echo 'deb [signed-by=\/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg] https:\/\/pkgs.k8s.io\/core:\/stable:\/v1.32\/deb\/ \/' | sudo tee \/etc\/apt\/sources.list.d\/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl sudo systemctl enable --now kubelet sudo sed -i 's\/#net.ipv4.ip_forward=1\/net.ipv4.ip_forward=1\/' \/etc\/sysctl.conf sudo sysctl -p sudo kubeadm init --pod-network-cidr=10.244.0.0\/16 --cri-socket=unix:\/\/\/var\/run\/crio\/crio.sock --apiserver-advertise-address __PUBLIC_IP__ --node-name master-1 mkdir ~\/.kube cp \/etc\/kubernetes\/admin.conf ~\/.kube\/config curl https:\/\/raw.githubusercontent.com\/helm\/helm\/main\/scripts\/get-helm-3 | bash kubectl taint nodes master-1 node-role.kubernetes.io\/control-plane:NoSchedule- kubectl get po -A<\/code><\/pre>\n<h2>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c Nginx Ingress, PostgreSQL, ExternalDNS \u0438 OpenEBS<\/h2>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c OpenEBS LocalPV<\/h4>\n<p>\u042d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 PersistentVolume \u0434\u043b\u044f PostgreSQL:<\/p>\n<pre><code class=\"cs\">helm repo add openebs https:\/\/openebs.github.io\/openebs helm upgrade --install --atomic --timeout 3m --set engines.replicated.mayastor.enabled=false --namespace openebs --create-namespace openebs openebs\/openebs --version 4.1.1 kubectl patch storageclass openebs-hostpath -p '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io\/is-default-class\":\"true\"}}}'<\/code><\/pre>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c PostgreSQL<\/h4>\n<p>\u0421\u0430\u043c\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0434\u044b \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 31000, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u043a \u043f\u043e\u0440\u0442\u0443 5432 PostgreSQL: <\/p>\n<pre><code class=\"cs\">helm repo add bitnami https:\/\/charts.bitnami.com\/bitnami helm upgrade --install --atomic --timeout 3m --set primary.service.type=NodePort --set primary.service.nodePorts.postgresql=31000 --set global.postgresql.auth.database=app --set global.postgresql.auth.password=__YOUR__ROOT__PASSWORD__ --set global.postgresql.auth.username=app --set global.postgresql.auth.postgresPassword=__YOUR__PROD__APP__DB__PASSWORD --create-namespace --namespace=devops postgresql bitnami\/postgresql --version 16.3.3<\/code><\/pre>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c ExternalDNS<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0432 Cloudflare. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0441 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 \u043d\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 DNS-\u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0434\u043b\u044f \u0434\u043e\u043c\u0435\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u043d\u0430 \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u0435 \u043d\u0438\u0436\u0435.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0428\u0430\u0433 \u043f\u0435\u0440\u0432\u044b\u0439<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width\">\n<div><figcaption>\u0428\u0430\u0433 \u0432\u0442\u043e\u0440\u043e\u0439<\/figcaption><\/div>\n<\/figure>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c ExternalDNS \u0447\u0435\u0440\u0435\u0437 Helm<\/h4>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0435:<\/p>\n<pre><code class=\"cs\">helm repo add bitnami https:\/\/charts.bitnami.com\/bitnami helm upgrade --install --atomic --timeout 3m --set provider=cloudflare --set cloudflare.apiToken=__YOUR__API__TOKEN__ --set cloudflare.proxied=true --create-namespace --namespace=devops external-dns bitnami\/external-dns --version 8.7.1<\/code><\/pre>\n<h4>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c Nginx Ingress \u0447\u0435\u0440\u0435\u0437 Helm<\/h4>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0435:<\/p>\n<pre><code class=\"cs\">helm repo add ingress-nginx https:\/\/kubernetes.github.io\/ingress-nginx helm upgrade --install --set controller.service.enabled=false --set controller.hostNetwork=true --set controller.kind=DaemonSet --create-namespace --namespace devops ingress-nginx ingress-nginx\/ingress-nginx --version 4.11.3<\/code><\/pre>\n<h2>\u0412\u0442\u043e\u0440\u043e\u0435 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u0438\u0435: \u043f\u0438\u0448\u0435\u043c CI\/CD<\/h2>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432 \u0444\u0430\u0439\u043b <code>.base.gitlab-ci.yml<\/code>:<\/p>\n<pre><code class=\"cs\">stages: - build - provision - deploy - manage # Public image that contains Ansible, Kubectl, Helm image: public-docker-repository.avant-it.ru\/public-ci-job:4.4.0 .base:   tags:   # Enter your runner tag here   - avant_gitlab_com<\/code><\/pre>\n<h4>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0441\u0431\u043e\u0440\u043a\u0443<\/h4>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0434\u0436\u043e\u0431\u044b build \u0432 \u0444\u0430\u0439\u043b\u0435 <code>.base.gitlab-ci.yml<\/code>. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Jinja \u0438 Ansible. \u042d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0442\u0438\u043f\u043e\u0432\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0431\u043e\u0440\u043a\u0438 Docker-\u043e\u0431\u0440\u0430\u0437\u0430.<\/p>\n<pre><code class=\"cs\">.build:   extends: .base   stage: build   script:   # If J2_TEMPLATES variable (in yaml string array format) is defined, we template every file name   #   E.g. [\".env.j2\"] will result in templated file .env   #   E.g. [\"src\/.env.j2\"] will result in templated file src\/.env   - |     if [ -n \"${J2_TEMPLATES}\" ];     then       echo \"Templating ${J2_TEMPLATES}\"       ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook ci\/core\/ansible\/template.yaml -v     fi   # Show environment variables for debugging   - printenv   # Building and pushing im   - echo \"$CI_REGISTRY_PASSWORD\" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin   - &gt;     docker build     ${DOCKER_BUILD_EXTRA_ARGUMENTS}     -f \"${CI_PROJECT_DIR}\/${DOCKERFILE_PATH}\"     -t \"${IMAGE_TAG}-${APP_NAME}\"     ${DOCKERFILE_CONTEXT_PATH}   - docker push \"${IMAGE_TAG}-${APP_NAME}\"   variables:<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-452399","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/452399","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=452399"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/452399\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=452399"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=452399"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=452399"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}