Хранить деньги в виде виртуальных монет стало модно. Сейчас у многих людей уже есть хоть немного эфириумов, биткоинов или монеро. Большинство людей приходят в индустрию криптовалют не ради того, чтобы заменить банковский депозит на криптокошелек. Их интересует заработок на цифровых валютах.
Классическими стали истории о том, как ранние инвесторы в биткоин и эфириум озолотились. С момента выпуска этих первых и лидирующих ныне виртуальных валют цены на эти токены существенно выросли. По этой модели развиваются все альткоины.
Не относящиеся к флагманским валюты после выпуска либо взлетают в цене в течение месяцев или лет, либо оказываются пустышками. Умение отличить первые от вторых позволяет умным крипто-инвесторам удачно вкладывать средства. Читать →
В современном мире, где конкуренция на рынке очень высока, важно постоянно отслеживать удовлетворенность клиентов своим продуктом или услугой. Опросы удовлетворенности клиентов являются одним из наиболее эффективных способов получения обратной связи от клиентов. Они позволяют узнать, что клиентам нравится в вашем продукте или услуге, а что можно улучшить.
Существует множество различных способов создания опросов удовлетворенности клиентов. Вы можете использовать специальные инструменты, такие как MADTEST или Google Forms, или создать опрос вручную.
Выберите инструмент. Существует множество различных инструментов, которые вы можете использовать для создания опросов удовлетворенности клиентов. Выберите инструмент, который соответствует вашим потребностям и бюджету.
Создайте учетную запись. После выбора инструмента вам необходимо создать учетную запись.
Создайте опрос. В большинстве инструментов для создания опросов есть простой интерфейс, который позволяет создавать опросы без каких-либо специальных навыков.
Отправьте опрос. После создания опроса вы можете отправить его своим клиентам.
Если вы хотите создать опрос удовлетворенности клиентов самостоятельно, вам необходимо выполнить следующие шаги:
Определите цели опроса. Что вы хотите узнать у своих клиентов?
Разработайте вопросы для опроса. Вопросы должны быть четкими, понятными и легко отвечаемыми.
Определите формат опроса. Вы можете использовать открытые, закрытые или смешанные вопросы.
Определите способ доставки опроса. Опрос можно отправить по электронной почте, разместить на веб-сайте или в приложении.
Проведите опрос и анализируйте результаты.
Платформа для создания квиза
Платформа для создания квиза — это инструмент, который позволяет создавать и проводить викторины, тесты и другие интерактивные опросы. Платформы для создания квиз имеют множество преимуществ перед традиционными опросами. Они позволяют создавать более интересные и привлекательные опросы, которые привлекают больше внимания клиентов. Кроме того, платформы для создания квиз позволяют собирать более подробную информацию от клиентов.
При выборе платформы для создания квиза следует учитывать следующие факторы:
Функциональность. Платформа должна иметь все необходимые функции для создания и проведения викторин и тестов.
Простота использования. Платформа должна быть простой в использовании, чтобы вы могли создавать викторины без каких-либо специальных навыков.
Цена. Платформы для создания квиз могут иметь разную стоимость. Выберите платформу, которая соответствует вашему бюджету.
Несколько популярных платформ для создания квиз:
MADTEST
Quizizz
Kahoot!
Socrative
Quizalize
Google Forms
Как создать опрос удовлетворенности клиентов с помощью платформы для создания квиза
Чтобы создать опрос удовлетворенности клиентов с помощью платформы для создания квиза, вам необходимо выполнить следующие шаги:
Выберите платформу. Выберите платформу, которая соответствует вашим потребностям и бюджету.
Создайте учетную запись. После выбора платформы вам необходимо создать учетную запись.
Создайте опрос. В большинстве платформ для создания квиз есть простой интерфейс, который позволяет создавать опросы без каких-либо специальных навыков.
Добавьте вопросы. Добавьте вопросы к опросу. Вопросы должны быть четкими, понятными и легко отвечаемыми.
Настройте опрос. Настройте опрос, добавив изображения, видео и другие элементы.
Отправьте опрос. После создания опроса вы можете отправить его своим клиентам.
Примеры вопросов для опроса удовлетворенности клиентов
Вот несколько примеров вопросов, которые вы можете использовать в опросе удовлетворенности клиентов:
Насколько вы удовлетворены нашим продуктом или услугой?
Что вам больше всего нравится в нашем продукте или услуге?
Что вы бы хотели улучшить в нашем продукте или услуге?
Как часто вы рекомендуете наш продукт или услугу другим?
Насколько вероятно, что вы воспользуетесь нашим продуктом или услугой снова?
Анализ результатов опроса
После того, как вы проведете опрос, важно проанализировать результаты. Анализ результатов позволит вам понять, что думают ваши клиенты о вашем продукте или услуге.
При анализе результатов опроса обратите внимание на следующие моменты:
Общий уровень удовлетворенности клиентов.
Отзывы клиентов о конкретных аспектах вашего продукта или услуги.
Предложения клиентов по улучшению вашего продукта или услуги.
На основе результатов анализа вы можете внести изменения в свой продукт или услугу, чтобы повысить удовлетворенность клиентов.
Instagram может быть платформой для обмена фотографиями, видео и роликами, но иногда именно текст бросается в глаза. Возможно, кто-то поделился ссылкой в своей подписи или комментарии в Instagram, и вы хотите открыть ее. Но, к сожалению, ссылки, которыми поделились в подписях или комментариях Instagram, не кликабельны.
Итак, какова альтернатива? Если вы не хотите вводить все это самостоятельно, и давайте посмотрим правде в глаза, никто этого не делает, единственный вариант — скопировать текст, чтобы получить эту ссылку. Или, может быть, вы наткнулись на красивую подпись, которую хотите скопировать для своих сообщений. Но проблема в том, что приложение Instagram на наших телефонах тоже не позволяет копировать текст. Итак, каково решение? Как обычно, мы собираемся использовать обходные пути для копирования текста из Instagram на iPhone.
Используйте функцию Live Text на iPhone
Apple представила функцию живого текста в iOS 15. На iPhone XS, XR и более поздних версиях функция живого текста может обнаруживать текст на изображениях на вашем iPhone или в Интернете, чтобы вы могли копировать и делиться или переводить текст на фотографиях. Мы будем использовать эту функцию для копирования текста из Instagram, что сделает все это испытание совершенно безболезненным.
Сначала откройте приложение Instagram и перейдите к сообщению с заголовком/комментарием, который вы хотите скопировать. Затем сделайте скриншот текста. Чтобы сделать снимок экрана, одновременно нажимайте кнопки блокировки и увеличения громкости, пока не увидите миниатюру снимка экрана в левом нижнем углу экрана.
Как только миниатюра исчезнет, снимок экрана появится в приложении «Фотографии».
Перейдите в приложение «Фотографии» и откройте только что сделанный снимок экрана. Затем коснитесь и удерживайте слова, которые хотите скопировать, пока не появятся точки захвата. Переместите точки захвата, чтобы включить весь текст, который вы хотите включить, или нажмите «Выбрать все», чтобы выбрать весь текст на снимке экрана.
Затем нажмите кнопку «Копировать» из плавающих вариантов, чтобы скопировать текст. И вставьте его куда угодно.
Это так просто.
Используйте Instagram в браузерном приложении
К сожалению, функция Live Text доступна не на всех моделях iPhone. Итак, что, если на вашем iPhone нет функции живого текста, но вы все равно хотите скопировать что-то из подписи в Instagram? Есть простой обходной путь, который включает использование Instagram в браузере.
Перейдите к сообщению с текстом в заголовке и коснитесь значка «меню из трех точек».
Затем коснитесь параметра «Ссылка» в меню, чтобы скопировать ссылку.
Скопируйте ссылку в браузере, чтобы открыть пост. Если вы ранее не входили в свою учетную запись в браузере, вам необходимо войти сейчас, чтобы получить доступ к полной подписи.
Вы также можете напрямую перейти на instagram.com и перейти к сообщению вручную.
Затем нажмите и удерживайте текст в подписи, пока не появятся точки захвата. Затем переместите их, чтобы включить полный текст, который вы хотите скопировать, и нажмите кнопку «Копировать».
К сожалению, вы не можете копировать текст в комментариях таким образом, за исключением одного или двух комментариев, которые появляются непосредственно в ленте.
То есть, если вы нажмете кнопку «Просмотреть все комментарии» и попытаетесь скопировать комментарий или даже заголовок с этой страницы, вы не сможете это сделать.
Используйте Google Фото в качестве альтернативы
Если вы отчаянно хотите скопировать текст из комментария к публикации в Instagram, вы можете использовать приложение Google Фото. Вы можете использовать функцию «Google Объектив» в приложении Google Фото, чтобы обнаруживать и копировать текст со снимка экрана, независимо от того, поддерживает ли ваш телефон Live Text или нет.
Если у вас его еще нет, установите приложение «Google Фото» из App Store.
Сделайте скриншот раздела комментариев или подписи из приложения Instagram.
Затем перейдите в приложение Google Фото и откройте снимок экрана. Нажмите кнопку «Google Объектив» на панели инструментов внизу.
Переключитесь на опцию «Текст».
Как только текст будет обнаружен, нажмите и удерживайте слова, пока не появятся точки захвата. Затем переместите точки захвата, чтобы выбрать нужный текст, и коснитесь параметра «Копировать текст» внизу.
В следующий раз, когда вы наткнетесь на подпись, которую хотите скопировать для себя, или на ссылку в комментариях Instagram, у вас есть более простые способы, чем копировать ее вручную туда и обратно.
Многие пользователи сообщают, что установка Windows 10 завершается ошибкой без какого-либо кода ошибки. Код ошибки поможет вам понять, с какой ошибкой сталкивается ваше устройство, чтобы вы могли предпринять соответствующие шаги для ее устранения. Но когда код ошибки отсутствует, вы летите вслепую, и это несколько усложняет устранение неполадок.
Тем не менее, это не невозможно, и мы составили список решений, которые помогут вам быстро решить эту проблему и позволят установить Windows 10 на ваше устройство.
Общие исправления
Прежде чем перейти к перечисленным ниже исправлениям, вот несколько общих простых исправлений, которые вы должны попробовать, чтобы устранить ошибку установки:
Удалите все периферийные устройства: если у вас есть какие-либо ненужные внешние периферийные устройства, такие как наушники, USB-устройства, внешние жесткие диски, принтеры, сканеры и т. д., отключите их. Они могут помешать установке. Если ваш ноутбук подключен к док-станции, также отстыкуйте его.
Обновите драйверы: перед установкой Windows убедитесь, что все важные драйверы обновлены.
Удалите сторонний антивирус: если вы используете стороннее антивирусное программное обеспечение, удалите его и используйте только Microsoft Defender, по крайней мере, пока вы пытаетесь обновить Windows. Стороннее антивирусное программное обеспечение часто может вызывать проблемы с обновлениями Windows. После завершения установки вы можете переустановить программное обеспечение. Но перед удалением убедитесь, что у вас есть необходимая информация для активации, чтобы вы могли повторно активировать ее.
Освободите место на диске: на вашем жестком диске должно быть достаточно места для установки Windows. Если он не получит требуемого места, установка завершится ошибкой. Проверьте место на диске в проводнике (или проводнике Windows в Windows 7) и при необходимости освободите место.
Попробуйте обновить Windows после выполнения этих исправлений. Если вы все еще видите код ошибки, перейдите к дополнительным техническим исправлениям, перечисленным ниже.
Способ 1. Разверните команду DISM и SFC
Сканирование обслуживания образов развертывания и управления ими проверит и восстановит существующие файлы операционной системы, установленные на вашем устройстве, вместе с другими системными файлами.
Перейдите в меню «Пуск» и введите «Командная строка», чтобы найти его. Затем нажмите «Запуск от имени администратора» в правой части меню, чтобы продолжить.
Затем, если вы не вошли в систему с учетной записью администратора, введите учетные данные для нее. В противном случае нажмите кнопку «Да», чтобы продолжить.
Затем введите или скопируйте+вставьте указанную ниже команду и нажмите Enter на клавиатуре. Это восстановит текущий установленный образ ОС на вашем устройстве.
DISM/ Online/Cleanup-Image/RestoreHealth
После завершения введите или скопируйте+вставьте следующую команду и нажмите Enter, чтобы проверить и восстановить системные файлы на вашем ПК.
SFC /scannow
Если после сканирования вы получите сообщение об успешном завершении сканирования, попробуйте обновить Windows еще раз или перейдите к следующему исправлению. Но если при сканировании он обнаружит поврежденные файлы, удалите эти файлы, а затем попробуйте обновить компьютер.
Способ 2: восстановить данные конфигурации загрузки (BCD)
Многие пользователи, обычно отвечающие за правильную загрузку вашего компьютера, сообщают, что восстановление их данных BCD помогло решить проблему с установкой Windows 10.
Чтобы восстановить компьютер, вам потребуется доступ к инструменту Advanced Startup Repair в вашей системе. Для этого включите компьютер и при первых признаках загрузки нажмите и удерживайте кнопку «Питание» в течение 10 секунд, чтобы принудительно выключить компьютер. Вы также можете отключить свой компьютер, если вы используете настольную систему.
Повторите этот процесс три раза, а в четвертый раз дайте компьютеру перезагрузиться как обычно. Advanced Startup Repair запустится автоматически, и вас встретит синий экран. Нажмите на плитку «Устранение неполадок», чтобы продолжить.
После этого нажмите на плитку «Дополнительные параметры», чтобы продолжить.
Затем щелкните плитку «Командная строка».
После этого введите или скопируйте+вставьте приведенные ниже команды по одной за раз и нажимайте Enterпосле ввода каждой из них, чтобы выполнить их.
bootrec /FixMbr
bootrec /RebuildBcd
bootrec /ScanOs
bootrec /FixBoot
Выполнив все команды, снова перезагрузите компьютер и проверьте, сохраняется ли проблема.
Способ 3: удалить конфликтующие языковые пакеты
Многие пользователи подтвердили, что удаление дополнительных языковых пакетов из их системы позволило им решить проблему на своем ПК.
Сначала откройте приложение «Настройки» в меню «Пуск». Либо нажмите клавиши Windows+ Iна клавиатуре, чтобы открыть приложение.
После этого выберите плитку «Время и язык», чтобы продолжить.
Затем нажмите на вкладку «Язык» на левой боковой панели. Затем щелкните языковой пакет, который вы хотите удалить, чтобы развернуть плитку. Затем нажмите кнопку «Удалить» в расширенном представлении, чтобы удалить язык из вашей системы.
Повторите вышеупомянутый процесс, если в вашей системе установлено несколько языков. После этого перезагрузите систему и проверьте, решена ли проблема.
Способ 4: запустить установку в чистой загрузке
Часто сторонние службы или программное обеспечение могут мешать процессу установки Windows. Вы можете устранить эту проблему, запустив установку в чистой загрузке, где работают только основные службы Microsoft.
Сначала нажмите клавиши Windows+ Rна клавиатуре, чтобы открыть утилиту «Выполнить команду». Затем введите msconfig и нажмите Enterна клавиатуре, чтобы продолжить.
После этого щелкните параметр «Выборочный запуск», а затем щелкните, чтобы выбрать параметр «Загрузить системные службы».
Затем перейдите на вкладку «Службы» и установите флажок перед параметром «Скрыть все службы Microsoft». Затем нажмите кнопку «Отключить все», чтобы отключить все службы, кроме служб Microsoft.
Затем нажмите кнопку «Применить», чтобы применить изменения, и нажмите кнопку «ОК», чтобы закрыть окно.
Теперь перезагрузите компьютер из меню «Пуск». ПК запустится в режиме чистой загрузки и снова запустит установку.
Ну вот, ребята. С помощью способов, изложенных в данном руководстве, вы сможете легко и быстро устранить ошибку установки Windows 10 на свой ПК.
Для детей, которые без ума от сладостей, развитие кариеса является распространенным явлением. Для предотвращения патологического процесса, способного испортить улыбку, важно следить за состоянием ротовой полости. Эта задача ложится на плечи родителей, и требует соблюдения определенных мер и рекомендаций.
Почему возникает кариес у детей?
Для того чтобы профилактика кариеса оказалась максимально эффективной, важно установить причину, по которой развивается заболевание. Наиболее частыми являются следующие причины:
Отсутствие должной гигиены полости рта, в результате чего образуется скопление остатков пищи вокруг зубов. Это приводит к размножению вредоносных бактерий.
Обилие сладкой пищи в рационе провоцирует нарушение кислотно-щелочного баланса ротовой полости. В результате возникают условия для жизнедеятельности патогенных микроорганизмов.
Остальные причины преимущественно являются врожденными, и передаются от родителей, либо возникают во время утробного развития. В зависимости от природы развития кариеса, требуются определенные меры профилактики. Установить источник проблемы помогут профессионалы. Ознакомиться с перечнем услуг, оказываемых в стоматологии «Апекс» и записаться на прием можно по ссылке https://apex24.ru/services/.
Эффективные меры профилактики кариеса у детей
Для результативной профилактики следует придерживаться ряда рекомендаций:
Забота о зубах должна иметь регулярный характер. Более того, важно побеспокоиться об этом моменте еще во время беременности. Необходимо уделять должное внимание питанию, что позволит избежать проблем в дальнейшем.
Недопустимо пренебрегать профилактическими и лечебными мерами до смены молочных зубов «постоянным» комплектом. «Временные» зубы не меньше нуждаются в уходе.
Особую роль играет укрепление иммунитета ребенка для увеличения способности сопротивляться воздействию агрессивных факторов. Крепкий организм менее подвержен разрушению зубной эмали, приводящему к проникновению инфекции глубоко в ткани зуба.
Важно правильно подобрать зубную пасту и щетку для ребенка, а также своевременно обновлять средства гигиены.
Привить правильные принципы гигиены поможет специалист. Поскольку не все взрослые умеют корректно чистить зубы, стоит обратиться за консультацией стоматолога.
Комплексный подход позволит избежать развития кариеса у ребенка. Взяв за привычку регулярно посещать кабинет стоматолога, удастся предупредить неприятные последствия заболевания.
Сам Хельм в общих чертах рассмотрели в посте Helm: Kubernetes package manager — обзор, начало работы – теперь надо прикрутить его в Jenkins. И не просто прикрутить его вызов – а создать чарт, потому что сейчас приложение деплоится через “голые” манифест-файлы Kubernetes, в котором sed проставляет теги Докер-образа и значения переменных для загрузки на окружение через kubectl apply -f.
Данный пост очередной НЕ-HowTo, а пример знакомства с Helm: возьмём существующий проект, который уже деплоится в Kubernetes-кластер, обновим его манифесты, что бы использовать их с Helm, продумаем – как именно и куда будем деплоить, и создадим Jenkins-джобу.
По ходу дела будем ближе знакомиться с Helm, и его подводными камнями.
Итак, у нас имеется некое веб-приложение, которое надо деплоить в Kubernetes.
Само приложение разбито на две части – фронтенд на React, хостится и деплоится в AWS S3, и бекенд – NodeJS, работает в Docker-контейнере.
Первый вопрос, который появляется в голове – монорепа, или нет?
Т.е. – где хранить код приложения, где – файлы чартов Helm-а, а где – jenkins-файлы?
Хотелось бы монорепу, и всё держать в одном репозитории, но у нас подобных проектов будет не один, и смысла под каждый писать отдельные дженкис-файлы нет. Тем более, что если (когда!) захочется что-то изменить в одном – то придётся делать это во всех по отдельности.
При этом и их файлы манифестов для Kubernetes будут практически одинаковы – и можно было бы заморочится вообще, и используя возможности шаблонизатора Go – использовать один общий чарт на всех.
Но так как их одинаковость пока под вопросом – запускаемые веб-проекты ещё только разрабатываются, да ещё и отдельными группами девелоперов – то чарты будем делать под каждый проект отдельно.
Значит, у нас будет:
отдельный репозиторий с кодом проекта
там же в отдельной папке храним файлы чартов
отдельный репозиторий с файлами для Jenkins-pipeline
Хочется прикрутить ещё и релизы, да ещё и связать их с Github Releases – но это, может быть, потом. В целом – реализуемо, и даже есть helm/chart-releaser.
Jenkins build steps
Дальше.
Билд Docker-образов – и билд/деплой самих чартов – делать одной джобой – или разными?
Сейчас у нас образы собираются в одной джобе, после чего пушатся в DockerHub, после чего девелопер должен сходить в джобу деплоймента, и при запуске в параметрах указать тег (номер билда) + дополнительные параметры:
Это, конечно, ни разу неудобно, поэтому переделаем.
Опционально – удалять с chart uninstall. Про удаление ресурсов вообще поговорим ниже.
Что ещё?
Namespaces
Ооо – нейспейсы жеж!
Что хочется:
иметь возможность деплоить по одной кнопке в какой-то дефолтный namespace, имя которого зависит от проекта/приложения/чарта
деплоить в кастомный namespace – по имени бранча репозитория, из которого запускался билд
деплоить в вообще кастомный namespace – дать возможность указать его при старте джобы
А как их потом удалять?
Можно добавить вебхук из Гитхаба при delete-операциях, но во-первых часть проектов хостятся в Gitlab – хотя он тоже умеет в вебхуки. Но во-вторых – а все ли девелоперы всех команд будут удалять свои закрытые бранчи?
Т.е. тут проблема как раз из-за разных команд и разных проектов, а решение хочется иметь маскимально универсальное.
А удалять НС-ы надо – иначе будет пачка заброшенных ресурсов – подов, ингресов, волюмов, и т.д.
Что делать?…
Окей. А пусть будет какой-то дефолтный неймспейс. Например, чарт/приложение называются bttrm-apps – значит, для Dev-деплой джобы НС будем создавать bttrm-apps-dev-ns.
В джобе добавить параметр, в нём сделать возможность выбора – использовать этот дефолтный НС, создать/использовать НС по имени бранча, или указать свой.
Можно так в Jenkins?
Не факт – идём глянем.
Choise parameter – тут просто задаётся список предефайненных значений – нельзя будет задать своё имя НС-а:
А Multi string parameter?
Вроде как что-то аналогичное Choise parameter?
Смотрим на него:
Нет, тоже не подходит – оно просто из параметра передаст нам в переменную дженкисфайла значение из нескольких строк, а нам нужна одна string.
Значит – или таки обычный String параметр, или Choise parameter с набором уже заданных значений… Но тогда потеряем возможность создать кастомный НС…
Или… Или… Или… Мысль…
А что на счёт Boolien parameter?
Типа “поменять НС”…
Дефолтное значение “не менять” – и используем предефайненное значение bttrm-apps-dev-1-ns…
А если юзер выбирает менять – то используем имя НС как имя бранча – а имя бранча вытянем из репозитория после чекаута…
Мысль? А может быть!
Хотя снова-таки – лишаемся возможности создать кастомный НС…
А что если просто использовать тот же Choise parameter с набором уже заданных значений, но создать три значения:
use default bm-apps-dev-ns
use github-branch-name
set own name
А потом в коде самого пайплайна проверять выбор, и если выбрано set own name – то просто запросить User input, и пользователь задаст имя неймспейса, который хочет создать.
Тоже не самое кошерное решение – но может сработать. Пока остановимся на нём.
Остаётся открытым вопрос про удаление НС-ов – будем думать потом
Хотя потом может быть больно.
В конце-концов – мы спокойно можем раз в месяц удалять весь Dev-кластер, и провиженить заново – дело 20 минут и одного клика в Jenkins, а для Stage/Prod деплоев будут использоваться только жёстко заданные неймспейсы, потому там такой проблемы не будет (но это не точно).
Окей, make sense.
Что дальше?
Дальше локально сделаем:
создадим чарт для приложения – используем реальное, но отбранчуемся, что бы не мешать девелоперам.
настроим kubectl на новый Dev-кластер
выполним helm install
Потом пойдём делать джобу в дженкисе.
Джоба будет клонировать репозиторий с приложением, и делать helm install в нужный namespace.
А релиз-версии нужны судя по всему только если мы будем заливать чарты в виде архивов в свой Helm-репозиторий – в самом Кубере они никак не применяются…
Значит – пока этот вопрос отложим, потом вернёмся к chart-releaser.
Поехали тестить?
Helm test deploy
kubectl config
У меня есть новый тестовый кластер bttrm-eks-dev-1 – создаём для него конфиг.
aws --profile arseniy eks update-kubeconfig --name bttrm-eks-dev-1 --alias arseniy@bttrm-eks-dev-1
Added new context arseniy@bttrm-eks-dev-1 to /home/setevoy/.kube/config
Создаём тестовый чарт:
cd /tmp/
helm create deployment-testing-chart
Creating deployment-testing-chart
Helm: the server has asked for the client to provide credentials
Пробуем залить в default namespace:
helm install deployment-testing-chart --dry-run deployment-testing-chart/
Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials
WTF?
Проверяем контекст:
kk config current-context
arseniy@bttrm-eks-dev-1
(kk – алиас для kubectl в ~/.bashrc: alias kk="kubectl")
Всё верно…
А доступ есть?
kk get pod
error: You must be logged in to the server (Unauthorized)
kk config delete-context arseniy@bttrm-eks-dev-1
warning: this removed your active context, use "kubectl config use-context" to select a different one
deleted context arseniy@bttrm-eks-dev-1 from /home/setevoy/.kube/config
helm install deployment-testing-chart deployment-testing-chart/ --namespace deployment-testing-chart-ns
Error: create: failed to create: namespaces "deployment-testing-chart-ns" not found
This is a known limitation of Helm. Helm will not do cross-namespace management, and we do not recommend setting namespace: directly in a template. If you want to install resources in a namespace other than the default namespace, helm install –namespace=foo will install the resources in that namespace. If the namespace does not exist, it will create it.
Эээ? Так мы ж так и делали…
Читаем ниже:
For Helm 3, the namespace creation doesn’t happen during helm install any more, so you’ll have to create that namespace ahead of time as you do today.
For Helm 3, the namespace creation doesn’t happen during helm install any more, so you’ll have to create that namespace ahead of time as you do today.Right now, use kubectl create namespace foo prior to helm install. When Helm 3.2.0 is released, use helm install … –create-namespace.
А у нас какая?
helm version
version.BuildInfo{Version:"v3.2.0", ...
kk get ns
NAME STATUS AGE
bttrm-apps-dev-1-ns Active 28h
bttrm-workouts-dev-1-ns Active 28h
default Active 28h
deployment-testing-chart-ns Active 19s
...
Няшка.
Сервисы:
kk -n deployment-testing-chart-ns get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
deployment-testing-chart ClusterIP 172.20.227.114 <none> 80/TCP 2m1s
Окей, вроде работает – можно думать за Helm chart для самого приложения.
Хотя тут можно начать наоборот с Jenkins-джобы и скрипта, и использовать тестовый чарт, а потом создавать чарт реального приложения.
Но давайте сначала всё-таки приведём в рабочий вид приложение.
Helm – создание чарта
Сейчас у нас файлы для Kubernetes лежат в отдельном репозитории devops, а код приложения – в репозитории самого приложения.
Что надо сделать – это создать чарт в репозитории с приложением – в этом репозитории будет и Dockerfile, там же будем хранить Chart.yaml.
Скрипты для Jenkins (jekinsfiles) хранятся в ещё одном отдельном репозитории для общего доступа из различных, но однотипных джоб.
What about RBAC?
Кстати. Задумался я о том, как мы будем деплоить с Jenkins и о правах доступа.
Сам Jenkins использует свою EC2 Instance Profile, которая даёт ему полный доступ к кластерам. Ту же роль использую сейчас я при AssumeRole, которую мы настроили выше в ~/.aws/config.
А что с девелоперами?
У них-то создаются отдельные RoleBinding из определённых неймспейсов, в которые им разрешается доступ, а не глобальный рутовый доступ, как у меня.
Значит – эти RoleBinding надо включить в чарт, и создавать их в тех неймспейсах, которые будут создавать при деплое чарта из Jenkins в – напомню – том числе и кастомные неймспейсы.
Как быть? Делать RBAC сабчартом?
Вопрос… Да и для самого Helm, по-хорошему, надо создавать отдельный Kubernetes ServiceAccount при создании кластера.
Хотя – зачем нам выносить доступы юзеров в отдельный чарт или сабчарт?
Там всего-то создать одну RoleBinding в нужном неймпсейсе. А “глобальная” ClusterRole создаётся при провижене самого кластера из Ansible (потом надо будет переписать на Helm, и все RBAC-ресурсы через него деплоить).
Собственно, как говорилось в начале – все КАПСы вырезаются в Jenkins с помощью sed, и меняются на реальные значения, полученные из параметров джобы, а потом выполняется kubectl apply -f.
spec: replicas: DEPLOY_REPLICAS_NUM вот – тут уже пора в values.yaml идти, добавляем в нём replicaCount: 2 а в деплойменте используем replicas: {{ .Values.replicaCount }}, см. пример ниже
selector: matchLabels: app: bttrm-apps-EKS_ENV меняем на application: {{ .Chart.Name }}
Сначала думал в имя деплоймента включить .Chart.Version или .Chart.AppVersion – но тут у Хельма всё оказалось через… Печаль какую-то, т.к. задать их значения во время helm install нельзя – только во время package, см. https://github.com/helm/helm/issues/3555 (с 2018 обсуждают!).
Ладно – оставим на совести разработчиков – жираф большой, ему виднее. Пока в имени оставлю {{ .Chart.Name }} – по ходу дела посмотрим. Да и не факт, что использовать версии в имени хорошая идея.
Получится строка типа bttrm/bttrm-apps:TAG, а TAG думаю формировать при сборке образа из $BUILD_NUMBER + $GIT_COMMIT, т.е. получим что-то вроде bttrm/bttrm-apps:123.67554433.
Значения для плейсхолдеров типа CLIENT_HOST_VAL в основном будут задаваться из параметров джобы Дженкинса, но добавляем их в values тоже. Где можно – задаём дефолтные значения, где нет – просто “”.
Пока, кстати, вообще все значения можно задать в values – до Jenkins-джобы ещё будем тестить локально.
Кавычки
А что там с кавычками, кстати?
Тоже мрак)
С одной стороны – документация по созданию чарта (тыц>>>) и по переменным (тыц>>>) говорит использовать quote-функцию, которая будет “оборачивать” наши значения в кавычки.
При этом, если посмотреть файлы, сгенерированные при helm create – то там нет ни quote, ни кавычек – в большинстве случаев. Но не везде
YAML’s type coercion rules are sometimes counterintuitive. For example, foo: false is not the same as foo: “false”. Large integers like foo: 12345678 will get converted to scientific notation in some cases.The easiest way to avoid type conversion errors is to be explicit about strings, and implicit about everything else. Or, in short, quote all strings.
Мрак!
Ладно – в values.yaml стринги заключаем в кавычки, целочисленные – без кавычек.
В файле шаблонов… Я не знаю
Использовать quote – тогда само значение в переменных будет с кавычками. Посмотрел, как приложение запущено сейчас – без кавычек:
kk -n bttrm-apps-stage-ns describe pod bttrm-apps-stage-7c5cfd9698-tkmjp | grep CLIENT_HOST
CLIENT_HOST: https://test.example.com
Значит пока без quote, и без явного указания “” в шаблоне вообще.
helm install --debug bttrm-apps-backend-release bttrm-apps-backend
install.go:159: [debug] Original chart version: ""
install.go:176: [debug] CHART PATH: /home/setevoy/Work/landing-backend/k8s/bttrm-apps-backend
client.go:108: [debug] creating 6 resource(s)
Error: Deployment in version "v1" cannot be handled as a Deployment:
v1.Deployment.Spec: v1.DeploymentSpec.Template: v1.PodTemplateSpec.Spec: v1.PodSpec.Containers: []v1.Container: v1.Container.Env: []v1.EnvVar: v1.EnvVar.Value:
ReadString: expects " or n, but found 3, error found in #10 byte of ...|,"value":3001}
Ах ты ж…
Вот честно – я тут очень матерился.
Может я не догоняю чего-то, но ты же мне везде в документации говоришь, что Integer без кавычек кушаешь нормально?
Что ж ты теперь ругаешься на цифры?
Про то, как Go вообще ошибки выводит промолчим – в них часто сложно что-то понять.
Но тут в принципе видно значение, вызвавшее ошибку – 3001:
v1.PodSpec.Containers: []v1.Container: v1.Container.Env: []v1.EnvVar: v1.EnvVar.Value: ReadString: expects ” or n, but found 3, error found in #10 byte of …|,”value”:3001}
Непонятно, конечно, где именно – но так как порты в деплойменте встречаются не так часто – то просто добавляем кавычки для всех, меняем:
При этом в ports: - containerPort: на кавычки он ругается.
А в livenessProbe: port: при --dry-run не ругается. А при install – ругается.
Рукалицо. Ладно.
Деплоим ещё раз:
helm install --debug bttrm-apps-backend-release bttrm-apps-backend/
install.go:159: [debug] Original chart version: ""
install.go:176: [debug] CHART PATH: /home/setevoy/Work/landing-backend/k8s/bttrm-apps-backend
client.go:108: [debug] creating 6 resource(s)
NAME: bttrm-apps-backend-release
LAST DEPLOYED: Sun May 10 14:35:37 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
...
Йай!
Проверяем:
helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
bttrm-apps-backend-release default 1 2020-05-10 14:35:37.364401788 +0300 EEST deployed bttrm-apps-backend-0.1.0 1.16.0
Ну – бимба!
Оно 100% не работает, но нам важен сам файкт, что оно задеплоилось:
kk get pod
NAME READY STATUS RESTARTS AGE
bttrm-apps-backend-c4d8f8c5d-98vd2 1/1 Running 0 54s
bttrm-apps-backend-c4d8f8c5d-v6slt 1/1 Running 0 54s
...
helm install bttrm-apps-backend-release bttrm-apps-backend/ --namespace bttrm-apps-dev-1-ns --create-namespace
NAME: bttrm-apps-backend-release
LAST DEPLOYED: Sun May 10 14:45:23 2020
NAMESPACE: bttrm-apps-dev-1-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None
Проверяем:
kk -n bttrm-apps-dev-1-ns get po
NAME READY STATUS RESTARTS AGE
bttrm-apps-backend-6c54d5c676-5j6mc 0/1 Pending 0 47s
bttrm-apps-backend-6c54d5c676-vchvh 0/1 Pending 0 47s
Супер, но почему Pending?
Что-то с реквестами, наверно?
Глянем events:
kk -n bttrm-apps-dev-1-ns get event
LAST SEEN TYPE REASON OBJECT MESSAGE
7s Warning FailedScheduling pod/bttrm-apps-backend-6c54d5c676-5j6mc 0/8 nodes are available: 8 Insufficient cpu.
10s Normal NotTriggerScaleUp pod/bttrm-apps-backend-6c54d5c676-5j6mc pod didn't trigger scale-up
(it wouldn't fit if a new node is added): 4 Insufficient cpu
А почему?
В values.yaml500 CPU requests, WorkerNodes у нас на ЕС2 t3.medium – у каждого 2000 CPU юнитов…
Проверим реквесты:
kk -n bttrm-apps-dev-1-ns describe pod bttrm-apps-backend-6c54d5c676-5j6mc
Name: bttrm-apps-backend-6c54d5c676-5j6mc
Namespace: bttrm-apps-dev-1-ns
...
Host Port: 0/TCP
Requests:
cpu: 500
...
helm upgrade bttrm-apps-backend-release bttrm-apps-backend/ --namespace bttrm-apps-dev-1-ns
Release "bttrm-apps-backend-release" has been upgraded. Happy Helming!
NAME: bttrm-apps-backend-release
LAST DEPLOYED: Sun May 10 14:54:57 2020
NAMESPACE: bttrm-apps-dev-1-ns
STATUS: deployed
REVISION: 2
TEST SUITE: None
Евенты:
kk -n bttrm-apps-dev-1-ns get event
LAST SEEN TYPE REASON OBJECT MESSAGE
47s Normal Scheduled pod/bttrm-apps-backend-69d74849df-nvtt2 Successfully assigned bttrm-apps-dev-1-ns/bttrm-apps-backend-69d74849df-nvtt2 to ip-10-3-33-123.us-east-2.compute.internal
15s Warning FailedMount pod/bttrm-apps-backend-69d74849df-nvtt2 MountVolume.SetUp failed for volume "apple-keys" : secret "apple-keys" not found
15s Warning FailedMount pod/bttrm-apps-backend-69d74849df-nvtt2 MountVolume.SetUp failed for volume "apple-certificates" : secret "apple-certificates" not found
47s Normal SuccessfulCreate replicaset/bttrm-apps-backend-69d74849df Created pod: bttrm-apps-backend-69d74849df-nvtt2
...
Ага!
Всё создалось.
Теперь поды не стартуют, потому что не файлов сертификатов для Apple – но это уже детали, сейчас их создадим.
Helm secrets from files
Добавим файлы сертификата – в Jenkins они будут маунтиться из Credentials типа Secret file, тут добавляем в текущий репозиторий:
ll bttrm-apps-be/secrets/
total 12
-rw-r--r-- 1 setevoy setevoy 258 May 10 17:32 AppleAuth.key.p8
-rw-r--r-- 1 setevoy setevoy 3088 May 10 17:27 ApplePay.crt.pem
-rw-r--r-- 1 setevoy setevoy 2006 May 10 17:28 ApplePay.key.pem
Добавляем в ../.gitignore репозитория и в .helmignore чарта:
helm upgrade bttrm-apps-be-release bttrm-apps-be/ --namespace bttrm-apps-dev-1-ns
Release "bttrm-apps-be-release" has been upgraded. Happy Helming!
NAME: bttrm-apps-be-release
LAST DEPLOYED: Sun May 10 17:36:47 2020
NAMESPACE: bttrm-apps-dev-1-ns
STATUS: deployed
REVISION: 4
TEST SUITE: None
Проверяем секреты:
kk -n bttrm-apps-dev-1-ns get secret
NAME TYPE DATA AGE
apple-certificates Opaque 2 37s
apple-key Opaque 1 37s
Но поды серты не увидели, и не запустилиись, так и будут висеть в FailedMount.
Пересоздаём их – вызываем upgrade с --recreate-pods:
helm upgrade bttrm-apps-be-release bttrm-apps-be/ --namespace bttrm-apps-dev-1-ns --recreate-pods
Flag --recreate-pods has been deprecated, functionality will no longer be updated. Consult the documentation for other methods to recreate pods
Release "bttrm-apps-be-release" has been upgraded. Happy Helming!
NAME: bttrm-apps-be-release
LAST DEPLOYED: Sun May 10 17:39:39 2020
NAMESPACE: bttrm-apps-dev-1-ns
STATUS: deployed
REVISION: 5
TEST SUITE: None
Проверяем:
helm -n bttrm-apps-dev-1-ns ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
bttrm-apps-be-release bttrm-apps-dev-1-ns 2 2020-05-10 17:44:56.505478002 +0300 EEST deployed bttrm-apps-be-0.1.0 1.16.0
kk -n bttrm-apps-dev-1-ns get pod
NAME READY STATUS RESTARTS AGE
bttrm-apps-be-dp-6f86d5c7d7-k4rjm 1/1 Running 0 2m
bttrm-apps-be-dp-6f86d5c7d7-ztcvp 1/1 Running 0 83s
Тут готово.
.dockerconfigjson
Сейчас в секретах файла bttrm-apps-secrets.yaml значение для .dockerconfigjson задан хардкодом:
неймспейс с именем бранча – APP_NAME + CLUSTER_ENV + GIT_BRANCH_NAME + “NS”, т.е. bttrm-apps-dev-1-WLIOS-5848-Projects-deployments
кастомный неймспейс, задаётся юзером
Есть сомнения по второму пункту – bttrm-apps-dev-1-WLIOS-5848-Projects-deployments – не слишком ли длинно?
Загуглим “kubernetes namespace best practices”, но лучшее, что нашлось – это вот эта запись в блоге, в которой говорится:
An easy to grasp anti-pattern for Kubernetes namespaces is versioning. You should not use Namespaces as a way to disambiguate versions of your Kubernetes resources
Namespace names are DNS1123 Labels: maximum 63 characters … You may want to populate generateName with a prefix, and let kubernetes make a unique suffix.
Хорошо – раз лимит в 63 символа – мы можем в дженкисфайле добавить хендлер, который будет проверять имя неймспейса, переданного из Jenkins-парамтеров, и при необходимости обрезать его.
Весь билд будет выполняться нашим кастомным Docker-образом kubectl-aws, собирается из такого Dockerfile:
FROM bitnami/minideb:stretch
RUN apt update && install_packages ca-certificates wget
RUN install_packages curl python-pip python-setuptools jq git
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl
RUN mv ./kubectl /usr/local/bin/kubectl
WORKDIR /tmp
RUN curl --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
RUN mv /tmp/eksctl /usr/local/bin
RUN pip install ansible boto3 awscli
WORKDIR /tmp
RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
RUN /bin/bash get_helm.sh
USER root
Пока все значения тпиа Github URL и имени бранча в скрипте билда хардкодим – потом вынесем в параметры джобы.
Запускаем, проверяем – всё работает.
Пошли дальше.
Docker build
Далее нам надо собрать Docker-образ, и запушить его в репозиторий.
Добавлять ли тег latest? Вопрос.
Наверно нет – билдов может быть много, из разных бранчей – перезаписывать каждый раз latest смысла нет, тем более деплоится в Dev окружение всё будет автоматом при создании пул-реквестов, см. Jenkins: Github Pull-Request Builder плагин.
Значит – делаем только docker build с тегом из переменной $DOCKER_TAG.
Создаём данные доступа для DockerHub:
Добавляем новый стейдж “Docker build”, в нём с помощью Docker-плагина для Jenkins собираем образ, для логина в DockerHub через credentialsId передаём созданные выше данные:
Дальше генерируем пакет. Пока надо только для того, что бы можно было задать release verion и app-version. Позже хочется добавить их пуш куда-то в репозиторий – или Github, или S3-backended.
добавить создание неймспейса по имени бранча и кастомный НС
проверка длины неймспейсов
Но пока, думаю, хватит.
helm upgrade – resource cannot be imported into the current release
P.S. Когда добавлял RBAC-роль – то при апдейте получил ошибку:
helm upgrade --install --namespace bttrm-apps-dev-1-ns --create-namespace --atomic bttrm-apps-backend bttrm-apps-backend-17.tgz
Error: UPGRADE FAILED: rendered manifests contain a resource that already exists. Unable to continue with update:
RoleBinding "rbac-bttrm-web-apps-ro-role-binding" in namespace "bttrm-apps-dev-1-ns" exists and cannot be imported into the current release:
invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by":
must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "bttrm-apps-backend";
annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "bttrm-apps-dev-1-ns"
Собственно, это к тому, что Helm задаёт аннотации и лейблы сам – но есть смысл добавлять их в шаблоны манифестов: