Архив автора: admin

Helm: пошаговое создание чарта и деплоймента из Jenkins



Сам Хельм в общих чертах рассмотрели в посте Helm: Kubernetes package manager — обзор, начало работы – теперь надо прикрутить его в Jenkins. И не просто прикрутить его вызов – а создать чарт, потому что сейчас приложение деплоится через “голые” манифест-файлы Kubernetes, в котором sed проставляет теги Докер-образа и значения переменных для загрузки на окружение через kubectl apply -f.




Данный пост очередной НЕ-HowTo, а пример знакомства с Helm: возьмём существующий проект, который уже деплоится в Kubernetes-кластер, обновим его манифесты, что бы использовать их с Helm, продумаем – как именно и куда будем деплоить, и создадим Jenkins-джобу.




По ходу дела будем ближе знакомиться с Helm, и его подводными камнями.




В общем, ещё один пример того, как очередная задача из смутной идеи в голове превращается в рабочий сервис – длиннопост в вольном стиле, аналог AWS Elastic Kubernetes Service: автоматизация создания кластера, часть 1 — CloudFormation и AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl, где шаг за шагом реализуем свою задумку.




Планирование




Репозитории




Итак, у нас имеется некое веб-приложение, которое надо деплоить в Kubernetes.




Само приложение разбито на две части – фронтенд на React, хостится и деплоится в AWS S3, и бекенд – NodeJS, работает в Docker-контейнере.




Первый вопрос, который появляется в голове – монорепа, или нет?




Т.е. – где хранить код приложения, где – файлы чартов Helm-а, а где – jenkins-файлы?




Хотелось бы монорепу, и всё держать в одном репозитории, но у нас подобных проектов будет не один, и смысла под каждый писать отдельные дженкис-файлы нет. Тем более, что если (когда!) захочется что-то изменить в одном – то придётся делать это во всех по отдельности.




При этом и их файлы манифестов для Kubernetes будут практически одинаковы – и можно было бы заморочится вообще, и используя возможности шаблонизатора Go – использовать один общий чарт на всех.




Но так как их одинаковость пока под вопросом – запускаемые веб-проекты ещё только разрабатываются, да ещё и отдельными группами девелоперов – то чарты будем делать под каждый проект отдельно.




Значит, у нас будет:




  • отдельный репозиторий с кодом проекта
    • там же в отдельной папке храним файлы чартов
  • отдельный репозиторий с файлами для Jenkins-pipeline




Хочется прикрутить ещё и релизы, да ещё и связать их с Github Releases – но это, может быть, потом. В целом – реализуемо, и даже есть helm/chart-releaser.




Jenkins build steps




Дальше.




Билд Docker-образов – и билд/деплой самих чартов – делать одной джобой – или разными?




Сейчас у нас образы собираются в одной джобе, после чего пушатся в DockerHub, после чего девелопер должен сходить в джобу деплоймента, и при запуске в параметрах указать тег (номер билда) + дополнительные параметры:





Это, конечно, ни разу неудобно, поэтому переделаем.




Итак, будет один пайплайн, в котором нам надо:




  1. чекаут кода проекта
  2. линт чарта
  3. сгенерить kubeconfig
  4. helm install example-chart --dry-run --debug example-chart/
  5. helm install example-chart example-chart/
  6. тест деплоя – helm get manifest example-chart




Опционально – удалять с 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.








Что дальше?




Дальше локально сделаем:




  1. создадим чарт для приложения – используем реальное, но отбранчуемся, что бы не мешать девелоперам.
  2. настроим kubectl на новый Dev-кластер
  3. выполним helm install




Потом пойдём делать джобу в дженкисе.




Джоба будет клонировать репозиторий с приложением, и делать helm install в нужный namespace.




А что с релизами-то? Го, почитаем Charts and Versioning.




А релиз-версии нужны судя по всему только если мы будем заливать чарты в виде архивов в свой Helm-репозиторий – в самом Кубере они никак не применяются…




Значит – пока этот вопрос отложим, потом вернёмся к chart-releaser.




Поехали тестить?




Helm test deploy




kubectl config




У меня есть новый тестовый кластер bttrm-eks-dev-1 – создаём для него конфиг.




Генерим конфиг, создаём новый контекст с именем arseniy@bttrm-eks-dev-1, см. Kubernetes: kubectl и kubeconfig — обзор файла, добавление кластера, пользователя и контекста:




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 в ~/.bashrcalias kk="kubectl")




Всё верно…




А доступ есть?




kk get pod
error: You must be logged in to the server (Unauthorized)




Нет.




В автоматизации создания кластера не добавлен мой IAM user…




Что там вообще? Читаем пост AWS Elastic Kubernetes Service: RBAC-авторизация через AWS IAM и RBAC группы, вспоминаем – делал через роли, отдельных пользователей в aws-auth ConfigMap уже не добавляю.




Надо локально настроить AssumeRole.




Надеюсь я прописал в полиси этой роли доступ для моего юзера…




Пробуем – обновляем ~/.aws/config:




...
[profile iam-bttrm-eks-root]
role_arn = arn:aws:iam::534***385:role/iam-bttrm-eks-root-role
source_profile = arseniy
region = us-east-2




Удаляем созданный контекст:




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




Генерим новый:




aws --profile iam-bttrm-eks-root eks update-kubeconfig --name bttrm-eks-dev-1 --alias iam-bttrm-eks-root@bttrm-eks-dev-1
Updated context iam-bttrm-eks-root@bttrm-eks-dev-1 in /home/setevoy/.kube/config




Проверяем доступ:




kk get po
NAME                                 READY   STATUS    RESTARTS   AGE
reloader-reloader-55448df76c-wdzqp   1/1     Running   0          28h




(про reloader – см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах)




Окей – возвращаемся к хельму:




helm install deployment-testing-chart --dry-run deployment-testing-chart/
NAME: deployment-testing-chart
LAST DEPLOYED: Thu May  7 20:06:44 2020
NAMESPACE: default
STATUS: pending-install
...




Найс!




Helm – deploy to  a Namespace




А что с деплоем в кастомный неймспейс?




helm install deployment-testing-chart --dry-run deployment-testing-chart/ --namespace deployment-testing-chart-ns
NAME: deployment-testing-chart
LAST DEPLOYED: Thu May  7 20:08:00 2020
NAMESPACE: deployment-testing-chart-ns
STATUS: pending-install
...




Тоже вроде ОК.




Helm и неймспейс сам создаёт? Не верится…




Попробуем реальный запуск, без --dry-run:




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




Нет, не создаёт)




Окей – это хорошо




Гуглим – понятное дело, что до нас люди с таким уже сталкивались, например – https://github.com/helm/helm/issues/4456, и там комментарий https://github.com/helm/helm/issues/4456#issuecomment-412134651:




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.




Ага!




И ещё ниже – https://github.com/helm/helm/issues/4456#issuecomment-607927999:




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", ...




Ага!




Пробуем:




helm install deployment-testing-chart deployment-testing-chart/ --namespace deployment-testing-chart-ns --create-namespace
NAME: deployment-testing-chart
LAST DEPLOYED: Thu May  7 20:15:38 2020
NAMESPACE: deployment-testing-chart-ns
STATUS: deployed
REVISION: 1
...




Бимба!




Проверяем НСы:




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-ресурсы через него деплоить).




Окей – значит так и сделаем.




Создание чарта




Поехали – клонируем репозиторий, создаём каталог, создаём чарт:




mkdir k8s
cd k8s/
helm create bttrm-apps-backend
Creating bttrm-apps-backend




Удаляем шаблоны, копируем из старого репозитория:




cd bttrm-apps-backend
rm -rf templates/*
cp ~/Work/devops/projects/EKS/roles/eks/templates/bttrm-apps/* templates/




Что у нас тут есть:




ls -1 templates/
bttrm-apps-deployment.yaml
bttrm-apps-ns-ingress-hpa.yaml
bttrm-apps-secrets.yaml




Посмотрим файл деплоймента:




apiVersion: apps/v1
kind: Deployment
metadata:
  name: bttrm-apps-EKS_ENV
  annotations:
    secret.reloader.stakater.com/reload: "bttrm-docker-secret"
spec:
  replicas: DEPLOY_REPLICAS_NUM
  strategy:
    type: RollingUpdate
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: bttrm-apps-EKS_ENV
  template:
    metadata:
      labels:
        app: bttrm-apps-EKS_ENV
    spec:
      containers:
      - name: bttrm-apps-EKS_ENV
        image: bttrm/bttrm-apps:IMAGE_TAG
        env:
        - name: CLIENT_HOST
          value: CLIENT_HOST_VAl
        - name: DATABASE_HOST
          value: DATABASE_HOST_VAL
...




Собственно, как говорилось в начале – все КАПСы вырезаются в Jenkins с помощью sed, и меняются на реальные значения, полученные из параметров джобы, а потом выполняется kubectl apply -f.




Как-то так:




...
        dir ('eks') {
            git branch: "${DEVOPS_REPO_BRANCH}", url: "${DEVOPS_REPO_URL}", credentialsId: "jenkins-github"
            def templates_path = "projects/EKS/roles/eks/templates/bttrm-apps" 
            // .env
            sed_env("$templates_path","bttrm-apps-deployment.yaml","BACKEND_HOST_VALUE","$BACKEND_HOST")
            sed_env("$templates_path","bttrm-apps-deployment.yaml","CLIENT_HOST_VAl","$CLIENT_HOST")
            sed_env("$templates_path","bttrm-apps-deployment.yaml","DEPLOY_REPLICAS_NUM","$DEPLOY_REPLICAS_NUM")
...




Ужас, правда?)




Для тестов было ОК, давайте приводить в порядок.




Сначала во всех файлах удаляем все metadata: namespace: – неймспейс будет определяться самим Хельмом.




Удаляем файл values.yaml, который сгененрировал Helm во время инициализации чарта, создаём новый файл, пустой.




Открываем в одном окне values.yaml, в другом – файл деплоймента, начинаем обновлять:




  • kind: Deployment metadata: name: bttrm-apps-EKS_ENV
    испольуем {{ .Chart.Name }}
  • 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 }} – по ходу дела посмотрим. Да и не факт, что использовать версии в имени хорошая идея.




По теме:







Пока в лейблах сделал так:




...
  selector:
    matchLabels:
      application: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        application: {{ .Chart.Name }}
        version: {{ .Chart.Version }}-{{ .Chart.AppVersion }}
        managed-by: {{ .Release.Service }}-{{ .Release.Name }}
...




{{ Chart.version }} и {{ .Chart.appVersion }} будет задаваться в Jenkins, пока будем брать дефолтные значения из Chart.yaml.




Далее, для выбора Docker-образа надо в values задать имя образа и тег.




Мы сейчас хостим образы в DockerHub, но потом думаю переезжать на AWS Elastic Container Registry, поэтому сразу пусть будет с возможностью поменять.




Заполняем values.yaml – добавляем image:




replicaCount: 2
    
image:
  repository: bttrm
  name: bttrm-apps
  tag: ""




Получится строка типа bttrm/bttrm-apps:TAG, а TAG думаю формировать при сборке образа из $BUILD_NUMBER + $GIT_COMMIT, т.е. получим что-то вроде bttrm/bttrm-apps:123.67554433.




Дальше у нас идёт пачка переменных:




...
        env:
        - name: CLIENT_HOST
          value: CLIENT_HOST_VAL
        - name: DATABASE_HOST
          value: DATABASE_HOST_VAL
        - name: DB_USERNAME
          value: DB_USERNAME_VAL
...




Значения для плейсхолдеров типа CLIENT_HOST_VAL в основном будут задаваться из параметров джобы Дженкинса, но добавляем их в values тоже. Где можно – задаём дефолтные значения, где нет – просто “”.




Пока, кстати, вообще все значения можно задать в values – до Jenkins-джобы ещё будем тестить локально.




Кавычки




А что там с кавычками, кстати?




Тоже мрак)




С одной стороны – документация по созданию чарта (тыц>>>) и по переменным (тыц>>>) говорит использовать quote-функцию, которая будет “оборачивать” наши значения в кавычки.




При этом, если посмотреть файлы, сгенерированные при helm create – то там нет ни quote, ни кавычек – в большинстве случаев. Но не везде 




Например, для:




...
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
...




Кавычки есть. Почему? Потому что “составная строка” – конкатенируется из нескольких? Окей, допустим.




Но почему:




...
      containers:
        - name: {{ .Chart.Name }}
...




Без кавычек, если документация говорит их использовать? Потому что это данные из Chart.yaml, а не values.yaml?




В чём принципиальная разница?




Ну и сам YAML, как всегда – “радует”, читаем в той же документации по Values – Make Types Clear:




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, и без явного указания “” в шаблоне вообще.




(у нас тут Stage, который pre-Dev даже)




values.yaml




Получается такой файл:




replicaCount: 2
    
image:
  repository: "bttrm"
  name: "bttrm-apps"
  tag: ""
# backend app config
backendConfig:
  clientHost: "https://test.example.com"
  nodeEnv: "dev"
  host: "https://test.api.example.com"
  port: 3001
  database:
    host: "stage.aurora.example"
    user: "dbuser"
    password: "dbpass"
    connection: "mysql"
    port: 3306




Passwords… Secrets…




Пароли пока хардкодим – потом будем передавать из Credentials parameter в Jenkins-джобе.




А ещё лучше – таки добавить helm secrets – но это потом (добавлено – см. Helm: helm-secrets — шифрование sensitive данных с AWS KMS и деплой из Jenkins)




Секреты




Так, секреты.




В деплойменте передаём секретами, секреты создаём из файла bttrm-apps-secrets.yaml.




Сейчас он выглядит так же, как деплоймент – обрабатывается с sed в старой джобе Jenkins:




---
apiVersion: v1
kind: Secret
metadata:
  name: bttrm-app-secret
type: Opaque
stringData:
  backend-db-password: DB_PASSWORD_VAL
...




В Деплойменте маунтим из секретов:




...
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: {{ .Values.image.repository }}/{{ .Values.image.name }}:{{ .Values.image.tag }}
        env:
        ...
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: bttrm-app-backend-secret
              key: backend-db-password
...




Обновляем секрет – передаём из values:




--- 
apiVersion: v1
kind: Secret
metadata:
  name: bttrm-app-backend-secrets
type: Opaque
stringData:
  backend-db-password: {{ .Values.backendConfig.db.password }}
  backend-apple-cert-passphrase: {{ .Values.backendConfig.apple.certPassphrase }}
  backend-apple-sigin-key-id: {{ .Values.backendConfig.apple.signInKeyID }}




chart 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)
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}




Непонятно, конечно, где именно – но так как порты в деплойменте встречаются не так часто – то просто добавляем кавычки для всех, меняем:




...
        - name: DB_PORT
          value: {{ .Values.backendConfig.port }} 
...




На:




...
        - name: DB_PORT
          value: {{ .Values.backendConfig.db.port | quote }}
...




При этом в 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 uninstall bttrm-apps-backend-release
release "bttrm-apps-backend-release" uninstalled




Деплоим ещё раз, указываем --create-namesapce:




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.yaml 500 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
...




Да, вроде всё верно.




Или реквесты криво описал…




Что там… Как они задаются вообще, где дока?




Вот тут вроде было толково – https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits.




Хм…




Кавычки и m?




Редактируем values – вместо 500 указываем “500m”:




...
resources:
  requests:
    cpu: "500m"
...




И добавим quote в деплоймент:




...

        resources:

          requests:

            cpu: {{ .Values.resources.requests.cpu | quote }} 

...




Пробуем:




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 чарта:




...

k8s/bttrm-apps-backend/secrets/




Обновляем bttrm-apps-secrets.yaml – используем Files, см https://helm.sh/docs/chart_template_guide/accessing_files/:




...

---

apiVersion: v1

kind: Secret

metadata:

  name: apple-certificates

type: Opaque

data:

  ApplePay.crt.pem: {{ .Files.Get "secrets/ApplePay.crt.pem" | b64enc }}

  ApplePay.key.pem: {{ .Files.Get "secrets/ApplePay.key.pem" | b64enc }}

---

apiVersion: v1

kind: Secret

metadata:

  name: apple-key

type: Opaque

data:

  AppleAuth.key.p8: {{ .Files.Get "secrets/AppleAuth.key.p8" | b64enc }}




Запускаем:




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 задан хардкодом:




--- 

apiVersion: v1

kind: Secret

metadata:

  creationTimestamp: null

  name: bttrm-docker-secret

type: kubernetes.io/dockerconfigjson

data:

  .dockerconfigjson: eyJhdXRo[...]

...




Находим пример в документации – Creating Image Pull Secrets.




Обновляем values.yaml – в блок image добавляем registryusername и password, пароль позже будем передавать из Jenkins:




...

image:

  registry: "docker.io" 

  username: "username"

  password: "pass"

  repository: "project"

  name: "bttrm-apps"

  tag: "120"

...




Создаём templates/_helpers.tpl, см. Nested template definitions.




Копируем функцию из примера в Creating Image Pull Secrets, меняем .Values.imageCredentials на наш .Values.image:




{{- define "imagePullSecret" }}

{{- printf "{"auths": {"%s": {"auth": "%s"}}}" .Values.image.registry (printf "%s:%s" .Values.image.username .Values.image.password | b64enc) | b64enc }}

{{- end }}




Обновляем секреты – вместо хардкод-строки в base64 вызываем этот самый {{ template "imagePullSecret" . }}:




--- 

apiVersion: v1

kind: Secret

metadata:

  creationTimestamp: null

  name: bttrm-docker-secret

type: kubernetes.io/dockerconfigjson

data:

  .dockerconfigjson: {{ template "imagePullSecret" . }} 

...




Передеплоиваем для проверки, и если всё ОК – то можно приступать в Jenkins.




Jenkins




Поехали к сиаю.




Что нам надо?




Сначала давай подумаем про шаги, которые надо выполнить в джобе:




  1. склонировать репозиторий приложения с helm-чартом, кодом приложения и докерфайлом
  2. билд образа – тегаем Jenkins $BUILD_NUMBER + $GIT_COMMIT
  3. docker push собранного образа, пока в DockerHub
  4. упаковка релиза + задать номера релиза и AppVersion, AppVersion == Docker Tag. т.е. $BUILD_NUMBER + $GIT_COMMIT
    1. позже – релиз в Гитхаб
  5. сгенерить кубконфиг
  6. выполнить helm install




Версинирование




Тут, конечно, по желанию каждого.




Мне пока в голову пришла такая схема:




  • chart release number == Jenkins $BUILD_NUMBER джобы
  • AppVersion == $BUILD_NUMBER + $GIT_COMMIT
  • Docker image tag == $BUILD_NUMBER + $GIT_COMMIT




Неймспейсы опять (снова?)




Что там с НС-ами было?




Давайте вспоминать – день четвёртый, на самом деле.




Хочется давать на выбор три варианта:




  1. дефолтный неймпейс – APP_NAME + CLUSTER_ENV + “NS”, т.е. поулчим bttrm-apps-dev-1ns
  2. неймспейс с именем бранча – APP_NAME + CLUSTER_ENV + GIT_BRANCH_NAME + “NS”, т.е. bttrm-apps-dev-1-WLIOS-5848-Projects-deployments
  3. кастомный неймспейс, задаётся юзером




Есть сомнения по второму пункту – 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




Ну и ладно – версии в НС мы включать и не думали.




Но подсказали в Kubernetes Slack:




Alan J Castonguay  3 days ago




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-парамтеров, и при необходимости обрезать его.




Про удаление НС-ов – потом, потом…




Тестирование





https://dzone.com/articles/easily-automate-your-cicd-pipeline-with-jenkins-he




Хорошая идея запускать тесты во время билда/деплоя – сначала какие-то юнит-тесты после сборки Docker-образа, потом – smoke-тесты после деплоя.




Но добавим потом – достаточно будет дописать функции на их вызов, пока задача запустить билд-деплой вообще.




Поехали?




Jenkins job




Создаём новую джобу, тип пайплайн:





Будем использовать Jenkins Scripted Pipelines.




Теперь поехали по шагам, которые напланировали выше.




Jenkins job stages




Init stage




Начнём с такого себе init-стейджа, который будет клонировать репозиторий с кодом, настраивать kubectl, и определять общие параметры и переменные.




Что нам в нём надо выполнить?




  • клонируем репозиторий
  • генерируем kubeconfig
  • вызываем kubctl cluster-info – для проверки kubeconfig
  • вызываем helm version – для проверки Helm вообще
  • задаём параметры:
    • release version – $BUILD_NUMBER, получаем из самого Jenkins
    • Docker Tag: будет $BUILD_NUMBER + $GIT_COMMIT, приходит в джобу из Git plugin после чекаута
    • AppVersion – $BUILD_NUMBER + $GIT_COMMIT, приходит в джобу из Git plugin после чекаута




Получается такой код:




node {

    docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') {

        stage('Init') {

            

            gitenv = git branch: "master",

                         credentialsId: 'jenkins-github',

                         url: 'git@github.com:example-org/bttrm-apps-backend.git'

        

            GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8)            

        

            RELEASE_VERSION = "${BUILD_NUMBER}"

            DOCKER_TAG = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}"

            APP_VERSION = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}"

            

            echo "DOCKER_TAG == ${DOCKER_TAG}"

            echo "APP_VERSION == ${APP_VERSION}"

            echo "RELEASE_VERSION == ${RELEASE_VERSION}"                

                

            sh "aws eks update-kubeconfig --region us-east-2 --name bttrm-eks-dev-1"

            sh "kubectl cluster-info"

            sh "helm version"

            sh "helm -n bttrm-apps-dev-1-ns ls "

        }

    }

}




Весь билд будет выполняться нашим кастомным 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 передаём созданные выше данные:




node {

    docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') {

        stage('Init') {

            ...            

        }

        

        stage("Docker build") {

            

            withDockerRegistry(registry: [credentialsId: 'bttrm-docker-hub']){

                docker.build("bttrm/bttrm-apps:${DOCKER_TAG}").push()

            }

        }

    }

}




Запускаем, проверяем, всё работает?




Хорошо, что дальше?




helm package




Дальше генерируем пакет. Пока надо только для того, что бы можно было задать release verion и app-version. Позже хочется добавить их пуш куда-то в репозиторий – или Github, или S3-backended.




...

        stage("Helm package") {

            

            sh "helm package k8s/bttrm-apps-be --version ${RELEASE_VERSION} --app-version ${APP_VERSION}"

        }

...




Запускаем – докер-образ собрался, локальный пакет хельм-чарта собрался – всё хорошо:




...

4d1ab3827f6b: Layer already exists

5.872a4dc3: digest: sha256:558e3b0ec0a7c5fc6302adca8321de2a3ee75656202930dcb979960d49273c0b size: 4089

[Pipeline] }

[Pipeline] // withDockerRegistry

[Pipeline] }

[Pipeline] // stage

[Pipeline] stage

[Pipeline] { (Helm package)

[Pipeline] sh

+ helm package k8s/bttrm-apps-backend --version 5 --app-version 5.5.872a4dc3

Successfully packaged chart and saved it to: /var/lib/jenkins/workspace/DO_EKS/Deployments/bttrm-apps/bttrm-apps-TEST/bttrm-apps-backend-5.tgz

[Pipeline] }

[Pipeline] // stage

[Pipeline] }

docker stop --time=1 023b7970d36e20b85912062b35bc7aa4136af7807b975a373c655c0cf38a6010

docker rm -f 023b7970d36e20b85912062b35bc7aa4136af7807b975a373c655c0cf38a6010

[Pipeline] // withDockerContainer

[Pipeline] }

[Pipeline] // node

[Pipeline] End of Pipeline

Finished: SUCCESS




Дальше – собственно деплой приложения.




helm install




Ну и деплоим – добавляем создание Namespace.




Сам неймспейс пока ставим дефолтный.




Можно добавить --atomic, что бы удалять зафейлившиеся деплои:




...

        stage("Helm install") {

            

            sh "helm install --atomic bttrm-apps-be bttrm-apps-be-${RELEASE_VERSION}.tgz"

        }




Запускаем, и:




...

[Pipeline] { (Helm install)

[Pipeline] sh

+ helm install --atomic bttrm-apps-backend bttrm-apps-backend-6.tgz

Error: unable to build kubernetes objects from release manifest: error validating "": error validating data:

[unknown object type "nil" in Secret.data.ApplePay.crt.pem, unknown object type "nil" in Secret.data.ApplePay.key.pem]

[Pipeline] }

[Pipeline] // stage

[Pipeline] }

docker stop --time=1 fa1e59d52af1c622d5122ddc8db25b72e5e2afff82a211f077fd8c3a8d1a48bb

docker rm -f fa1e59d52af1c622d5122ddc8db25b72e5e2afff82a211f077fd8c3a8d1a48bb

[Pipeline] // withDockerContainer

[Pipeline] }

[Pipeline] // node

[Pipeline] End of Pipeline

ERROR: script returned exit code 1

Finished: FAILURE




Ага, ну да – секреты в репозиторий мы не добавляли, они заигнорены через .gitignore, и должны мапится из Secret file самой джобы.




Собственно – пора добавить и параметры.




Job parameters




Посмотрим на весь скрипт, который у нас уже получился:




node {

    docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') {

        stage('Init') {

            

            gitenv = git branch: "WLIOS-5848-Projects-deployments",

                         credentialsId: 'jenkins-github',

                         url: 'git@github.com:example-org/bttrm-apps-backend.git'

        

            GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8)            

        

            RELEASE_VERSION = "${BUILD_NUMBER}"

            DOCKER_TAG = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}"

            APP_VERSION = "${BUILD_NUMBER}.${DOCKER_TAG}"

            

            echo "DOCKER_TAG == ${DOCKER_TAG}"

            echo "APP_VERSION == ${APP_VERSION}"

            echo "RELEASE_VERSION == ${RELEASE_VERSION}"                

                

            sh "aws eks update-kubeconfig --region us-east-2 --name bttrm-eks-dev-1"

            sh "kubectl cluster-info"

            sh "helm version"

            sh "helm -n bttrm-apps-dev-1-ns ls "

        }

        

        stage("Docker build") {

            

            withDockerRegistry(registry: [credentialsId: 'bttrm-docker-hub']){

                docker.build("bttrm/bttrm-apps:${DOCKER_TAG}").push()

            }

        }

        

        stage("Helm package") {

            

            sh "helm package k8s/bttrm-apps-backend --version ${RELEASE_VERSION} --app-version ${APP_VERSION}"

        }

        

        stage("Helm install") {

            

            sh "helm install --atomic bttrm-apps-backend bttrm-apps-backend-${RELEASE_VERSION}.tgz"

        }

    }

}




Что из этого надо вынести в параметры, и в какие?




Собственно – почти всё надо выносить в параметры джобы, потому что это же скрипт будет использоваться для деплоя аналогичных проектов.




Пока сделаем:




  • репозиторий проекта – String parameter
  • бранч проекта – String parameter
  • регион для aws eks update-kubeconfig – можно задать в скрипте, вряд ли кто-то будет деплоить в кластера в других регионах (хотя Jenkins-джоба для создания кластеров в любом регионе есть, см. AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl)
  • имя кластера для создания kubeconfig – в параметры джобы, String parameter
  • namespace – пока сделаем один, дефолтный – выносим в параметр джобы, Choice parameter
  • имя чарта – String parameter





Добавляем секретные файлы.




Выбираем тип Credentials parameter, тип Secret file:





Либо, если ключи для Dev/Stage/Prod одинаковые – то не выносить их в параметры, а сразу использовать в скрипте.




Теперь нам надо замапить эти ключи в контейнер во время билда, см. Jenkins: Credentials Binding Plugin и использование нескольких Secret file в Jenkins pipeline.




Получается такой скрипт:




node {

    docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') {

        stage('Init') {

            

            gitenv = git branch: '${APP_REPO_BRANCH}',

                         credentialsId: 'jenkins-github',

                         url: '${APP_REPO_URL}'

        

            GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8)            

        

            RELEASE_VERSION = "${BUILD_NUMBER}"

            DOCKER_TAG = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}"

            APP_VERSION = "${BUILD_NUMBER}.${DOCKER_TAG}"

            AWS_EKS_REGION = "us-east-2"

            

            echo "DOCKER_TAG == ${DOCKER_TAG}"

            echo "APP_VERSION == ${APP_VERSION}"

            echo "RELEASE_VERSION == ${RELEASE_VERSION}"                

            

            sh "aws eks update-kubeconfig --region ${AWS_EKS_REGION} --name ${AWS_EKS_CLUSTER}"

            sh "kubectl cluster-info"

            sh "helm version"

            sh "helm -n ${AWS_EKS_NAMESPACE} ls "

        }

        

        stage("Docker build") {

            

            /*

            withDockerRegistry(registry: [credentialsId: 'bttrm-docker-hub']){

                docker.build("bttrm/bttrm-apps:${DOCKER_TAG}").push()

            }

            */

            echo "Docker build"

        }

        

        stage("Helm package") {

            dir("k8s") {

                sh "helm package ${APP_CHART_NAME} --version ${RELEASE_VERSION} --app-version ${APP_VERSION}"

            }

        }

        

        stage("Helm install") {

            

            dir("k8s") {

                

                withCredentials([

                    file(credentialsId: 'bttrm-apps.applepay.crt', variable: 'APPLEPAY_CERT'),

                    file(credentialsId: 'bttrm-apps.applepay.key', variable: 'APPLEPAY_KEY'),

                    file(credentialsId: 'bttrm-apps.appleauth.key', variable: 'APPLEAUTH_KEY'),

                    ]) {

                    

                    sh "test -d bttrm-apps-backend/secrets || mkdir bttrm-apps-backend/secrets"

                    sh "cp $APPLEPAY_CERT bttrm-apps-backend/secrets/ApplePay.crt.pem"

                    sh "cp $APPLEPAY_KEY bttrm-apps-backend/secrets/ApplePay.key.pem"

                    sh "cp $APPLEAUTH_KEY bttrm-apps-backend/secrets/AppleAuth.key.p8"

                    

                    sh "helm install --namespace ${AWS_EKS_NAMESPACE} --create-namespace --atomic ${APP_CHART_NAME} ${APP_CHART_NAME}-${RELEASE_VERSION}.tgz"

                }

            }

        }

    }

}




Стейдж сборки докер-образа пока комментируем, что бы не захламлять репозиторий.




Тут в Helm install мы:




  1. переходим в каталог k8s репозитория
  2. проверяем наличие каталога bttrm-apps-backend/secrets, если его нет – создаём
  3. из Jenkins Credentials создаём три файла ключей
  4. и выполняем установку чарта




Параметры при запуске выглядят так:





Запускаем, проверяем:





Супер!




“It works!” (c)




Helm: “Error: cannot re-use a name that is still in use”




Но – если мы запустим билд повторно – он уже сфейлится с ошибкой “Error: cannot re-use a name that is still in use“:





Ожидаемо – раз уже есть задплоенный резил с таким именем – Helm не будет его перезаписывать.




Тут достаточно просто заменить install на helm upgrade --install:





В целом-то это всё…




Что осталось ещё?







Но пока, думаю, хватит.




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 задаёт аннотации и лейблы сам – но есть смысл добавлять их в шаблоны манифестов:




kk -n bttrm-apps-dev-1-ns get rolebinding rbac-bttrm-web-apps-ro-role-binding -o yaml

apiVersion: rbac.authorization.k8s.io/v1

kind: RoleBinding

metadata:

annotations:

meta.helm.sh/release-name: bttrm-apps-backend

meta.helm.sh/release-namespace: bttrm-apps-dev-1-ns

creationTimestamp: "2020-05-14T16:02:42Z"

labels:

app.kubernetes.io/managed-by: Helm

...




Такие дела.




Источник: https://rtfm.co.ua/helm-poshagovoe-sozdanie-charta-i-deplojmenta-iz-jenkins/



2022-08-08T20:57:39
DevOps

Полезные команды Linux. Манипуляции с текстом

Вывести содержимое файла, нумеруя выводимые строки




cat -n file1




Вывести только не четные строки файла




cat example.txt | awk 'NR%2==1'




Вывести первую и третью колонки. Разделение, по-умолчанию, по пробельным символам или символу табуляции




echo a b c | awk '{print $1,$3}'




Вывести первую колонку содержимого файла. Разделение, по-умолчанию, по пробельным символам или символу табуляции




echo a b c | awk '{print $1}'




Сравнить содержимое двух файлов, удаляя строки встречающиеся в обоих файлах




comm -3 file1 file2




Сравнить содержимое двух файлов, не отображая строки принадлежащие файлу file1




comm -1 file1 file2




Сравнить содержимое двух файлов, не отображая строки принадлежащие файлу file2




comm -2 file1 file2




Отобрать и вывести строки содержащие цифровые символы из файла /var/log/messages




grep [0-9] /var/log/messages




Отобрать и вывести строки, начинающиеся с сочетания символов «Aug», из файла /var/log/messages




grep ^Aug /var/log/messages




Отобрать и вывести строки, содержащие сочетание символов «Aug» из файла /var/log/messages




grep Aug /var/log/messages




Отобрать и вывести строки, содержащие сочетание символов «Aug», из всех файлов, расположенных в директории /var/log и ниже




grep Aug -R /var/log/*




Объединить содержимое file1 и file2 в виде таблицы с разделителем «+»




paste -d '+' file1 file2




Объединить содержимое file1 и file2 в виде таблицы: строка 1 из file1 = строка 1 колонка 1-n, строка 1 из file2 = строка 1 колонка n+1-m




paste file1 file2




Сравнить содержимое двух файлов




sdiff file1 file2




Заменить string1 на string2 в файле example.txt и вывести содержимое




sed 's/string1/string2/g' example.txt




Удалить пустые строки и комментарии из файла example.txt




sed '/ *#/d; /^$/d' example.txt




Удалить пустые строки и комментарии из файла example.txt




sed '/^$/d' example.txt




Удалить первую строку из файла example.txt




sed -e '1d' exampe.txt




Отобразить только строки содержащие string1




sed -n '/string1/p'




Удалить строку string1 из текста файла example.txt не изменяя всего остального




sed -e 's/string//g' example.txt




Удалить пустые символы в конце каждой строки файла example.txt




sed -e 's/ *$//' example.txt




Вывести пятую строку




sed -n '5p;5q' example.txt




Вывести строки со второй по пятую




sed -n '2,5p' example.txt




Заменить последовательность из любого количества нулей одним нулём




sed -e 's/00*/0/g' example.txt




Вывести отсортированное содержимое двух файлов




sort file1 file2




Вывести отсортированное содержимое двух файлов исключая повторные значения




sort file1 file2 | uniq




Вывести уникальные значения из отсортированного содержимого двух файлов




sort file1 file2 | uniq -u




Вывести только повторяющиеся значения из отсортированного содержимого двух файлов




sort file1 file2 | uniq -d




Перевести символы нижнего регистра в верхний




echo 'word' | tr '[:lower:]' '[:upper:]'



2022-08-08T00:48:58
Утилиты командной строки

Стена огня lvl2. Настраиваем файрвол для отражения атак на примере MikroTik

Содержание статьи




  • Унификация настроек
  • Защищаемся от атак
  • Цепочка Forward
  • Заключение




В этой статье мы рассмотрим, как защищать роутер MikroTik от атак и сканеров портов, а также предотвратим попадание нашей сети в бан-листы. Полученный опыт поможет тебе настраивать и другие виды файрволов.




INFO




Общие принципы безопасной настройки роутера и принципы оптимальной настройки файрвола ты можешь узнать в предыдущих статьях.




Унификация настроек




Если у тебя большая сеть с несколькими филиалами и в каждом из них по два роутера для отказоустойчивости, то правила лучше настроить так, чтобы их можно было легко развернуть на любой железке, независимо от количества и именования портов или типа подключения. Для соединения по PPPoE, например, WAN-интерфейсом будет pppoe-out1, а для DHCP — ether1. Если попытаться экспортировать конфиг файрвола с одного роутера на другой, ничего не выйдет, потому что у второго просто нет интерфейса pppoe-out1. Или представь себе, что в одном филиале локальная сеть висит на ether9, а в другом стоит роутер с пятью портами, из-за чего конфигурация девятого порта просто не встанет и вылетит с ошибкой.




Поэтому мы будем настраивать роутер так, чтобы конфиг можно было без проблем перенести на любой другой роутер. Это немного усложнит первоначальную настройку, но сэкономит кучу времени в будущем.




Мы уже рассматривали списки интерфейсов. Это фича для оперирования несколькими интерфейсами как одним. Создадим листы WAN и LAN, а затем добавим туда нужные интерфейсы. Теперь правила файрвола будем привязывать к интерфейс-листам, а не к отдельным интерфейсам. Перед экспортом правил на другой роутер просто создадим на нем нужные листы, и конфиг встанет без ошибок.




Interface List




Обрати внимание, что для использования в файрволе нам нужны L3-интерфейсы, то есть те, на которых есть IP-адреса. Если ты получаешь интернет по PPPoE, то в WAN-лист надо добавить именно его. Если IP локальной сети прописан на бридже или VLAN’е, то и в лист LAN нужно добавить их, а не физические интерфейсы. Если включить в список и логический, и физический интерфейс, ничего страшного произойти не должно, но это нужно будет учитывать в конфигурации.




Но это еще не все. Понятно, что в каждом филиале у нас будет своя подсеть LAN: где-то 192.168.10.0/24, где-то 192.168.11.0/24. Чтобы не путаться с этими значениями и не менять конфиг при переносе с одного роутера на другой, оперировать будем не адресами и подсетями, а списками адресов. На каждом роутере создаем список LAN и дальше работаем только с ним.




В прошлый раз мы создавали адрес-лист MGMT, в котором открывали доступ к управлению роутером только с определенных адресов. А еще раньше рассматривали решение Port Knocking, которое предоставляет доступ к управлению, только если со стороны клиента выполнить секретные манипуляции. Для доступа к роутеру из доверенной сети (LAN) вполне подходит вариант с адрес-листом, а для доступа снаружи — Port Knocking. Было бы хорошо совместить эти варианты в нашей конфигурации. Еще будет удобно разделить цепочку input на две части: input со стороны интернета и input со стороны локалки. Тогда можно применять разные политики фильтрации к разным сегментам сети. В этом нам помогут пользовательские цепочки.




Все, что пришло снаружи, перекидываем в новую цепочку WAN_INPUT. Все, что изнутри, — в LAN_INPUT:




/ip firewall filter
add action=jump chain=input in-interface-list=WAN jump-target=WAN_INPUT
add action=jump chain=input in-interface-list=LAN jump-target=LAN_INPUT




Теперь политики фильтрации будут разными для разного источника трафика. Для внешнего трафика будем использовать цепочку WAN_INPUT и более жесткие ограничения, для внутреннего — LAN_INPUT и правила попроще. Цепочка input нам больше не нужна, теперь мы все будем делать в новых цепочках. Причем указывать интерфейсы или списки интерфейсов в правилах больше не понадобится. Однако этот подход может использоваться в сложных решениях, например когда у тебя два провайдера с разными политиками фильтрации или локалка поделена на разные VLAN. Но об этом позже.




В статье о безопасной настройке роутера мы настраивали Port Knocking для доступа к управлению роутером. Ограничивать таким образом доступ изнутри локальной сети — излишество. Поэтому поменяем в правилах цепочку с input на WAN_INPUT. Изнутри сети разрешим доступ к WinBox только с нужных адресов: мы уже делали это в статье про основы файрвола. Оставим в правиле только порт WinBox — TCP 8291. А для SSH разрешим подключения из всей нашей сети, но предотвратим возможность брутфорса (да, изнутри сети тоже может произойти брутфорс SSH, потому что отсутствие троянов в ней не гарантировано).




add action=drop chain=LAN_INPUT comment="drop ssh brute forcers" src-address-list=ssh_blacklist
add action=add-src-to-address-list address-list=ssh_blacklist address-list-timeout=1w3d chain=LAN_INPUT connection-state=new dst-port=22 protocol=tcp src-address-list=ssh_stage3
add action=add-src-to-address-list address-list=ssh_stage3 address-list-timeout=1m chain=LAN_INPUT connection-state=new dst-port=22 protocol=tcp src-address-list=ssh_stage2
add action=add-src-to-address-list address-list=ssh_stage2 address-list-timeout=1m chain=LAN_INPUT connection-state=new dst-port=22 protocol=tcp src-address-list=ssh_stage1
add action=add-src-to-address-list address-list=ssh_stage1 address-list-timeout=1m chain=LAN_INPUT connection-state=new dst-port=22 protocol=tcp src-address-list=!ssh_open
add action=accept chain=LAN_INPUT dst-port=22 protocol=tcp




Тут применяется механизм динамических адрес-листов с тайм-аутами. Мы рассматривали их в статье «Защищаем MikroTik. Хитрости безопасной настройки роутера». При первой попытке подключения пакет обработается правилом 5, и адрес хакера попадет в адрес-лист ssh_stage1. Вторая попытка подключения обработается правилом 4 и добавит брутфорсера в лист ssh_stage2. И так далее вплоть до листа ssh_blacklist, где адрес будет храниться десять дней, а весь трафик, идущий с адресов из этого списка, будет дропаться.




В прошлой статье мы создавали правила, разрешающие коннекты established, related и запрещающие invalid. Давай продублируем эти правила и перенесем их в новые цепочки, а из input удалим. В результате мы получим четыре правила вместо двух. На прохождение трафика это не повлияет, зато позволит видеть статистику по трафику с разных сторон. В правиле с established, related поставь галочку untracked. Чуть позже объясню, зачем она. Думаю, адаптировать остальные правила под новую логику не составит труда. В конце каждой цепочки не забудь указать правило дропа.




Должно получиться примерно так




Две цепочки позволят нам уменьшить количество переходов трафика по правилам, а значит, и немного снизить нагрузку на CPU. Счетчики в разных цепочках дадут возможность увидеть чуть более детальную статистику трафика. Хоть правил и стало больше, но они не применяются ко всему объему трафика: при первом джампе весь трафик будет обрабатываться уже новой цепочкой и в другую никогда не попадет. Подобный подход также упрощает поддержку за счет того, что по названию цепочки сразу видно, что это за трафик и откуда он идет. Можно для разных типов трафика создавать свои цепочки, например отдельную цепочку для management-трафика. За возврат трафика в родительскую цепочку отвечает action return.




Защищаемся от атак




До сих пор мы рассматривали правила файрвола, позволяющие обрабатывать трафик по простым признакам: интерфейсу, адресу, порту. Но файрвол гораздо более гибкий инструмент, с его помощью можно строить сложную логику для противодействия разным типам атак.




Есть зарезервированные адреса, которые не используются в интернете. Они называются «богон-адресами». Отсечем пакеты с таких адресов:




/ip firewall address-list
add address=0.0.0.0/8 comment="Self-Identification [RFC 3330]" list=Bogon
add address=10.0.0.0/8 comment="Private[RFC 1918] - CLASS A" list=Bogon
add address=127.0.0.0/16 comment="Loopback [RFC 3330]" list=Bogon
add address=169.254.0.0/16 comment="Link Local [RFC 3330]" list=Bogon
add address=172.16.0.0/12 comment="Private[RFC 1918] - CLASS B" list=Bogon
add address=192.168.0.0/16 comment="Private[RFC 1918] - CLASS C" list=Bogon
add address=192.0.2.0/24 comment="Reserved - IANA - TestNet1" list=Bogon
add address=192.88.99.0/24 comment="6to4 Relay Anycast [RFC 3068]" list=Bogon
add address=198.18.0.0/15 comment="NIDB Testing" list=Bogon
add address=198.51.100.0/24 comment="Reserved - IANA - TestNet2" list=Bogon
add address=203.0.113.0/24 comment="Reserved - IANA - TestNet3" list=Bogon
add address=224.0.0.0/4 comment="MC, Class D, IANA" list=Bogon




Мы ожидаем пакеты только с юникаст-адресов, поэтому запретим все, кроме них.




Drop non unicast




Port Scan Detect — функция, позволяющая обнаружить сканер портов. Как она работает? Портам задается некий условный вес — Weight. Причем для системных портов (до 1024-го) весовой коэффициент низкий (Low ports), а для остальных — высокий (High ports). Если в течение времени Delay Threshold от одного хоста на роутер прилетят пакеты на порты, сумма весов которых окажется больше, чем Weight Threshold, то адрес отправителя будет добавлен в блек-лист. В нашем примере, если с одного хоста за три секунды поступят десять пакетов на порты до 1024-го (общий вес 10 * 2 = 20) и двадцать пакетов на порты выше 1024-го (20 * 1 = 20), общий их вес составит 40. Обрати внимание, что Port Scan Detect работает только для TCP- или UDP-трафика.




Защищаемся от сканеров




Один из самых распространенных видов атак — это атака на отказ в обслуживании, или DDoS. Защититься от нее своими силами практически нереально. Но с помощью простого правила можно отсечь самые простые попытки атаки. Находим хост, который насоздавал к нам больше 100 сессий, и добавляем его в блек-лист. В этом правиле обязательно нужно использовать параметр connection-state=new. Но мы ведь уже разрешили все established, related и untracked, а invalid дропнули, поэтому сюда дойдут только пакеты new. Оставлять или нет этот флажок в правиле — твое дело. Отмечу, что с помощью этой же фичи можно выявлять в своей сети торрентокачальщиков.




Защищаемся от DDoS




ICMP — один из важных протоколов в любой сети. Многие админы любят блокировать его, но это очень плохой подход. Именно ICMP позволяет работать трассировке, указывать на недоступность UDP-портов, отправлять разные служебные сообщения. И если запретить его полностью, можно наловить кучу багов. У каждого сообщения ICMP свое предназначение, и уже по этому параметру нетрудно понять, имеет ли смысл разрешить какие-то типы ICMP изнутри сети или снаружи. Например:




  • ICMP Echo Request — наш любимый пинг, имеет тип 8, код 0;
  • ICMP Echo Reply — ответ на пинг, тип 0, код 0;
  • Destination Unreachable — узел недоступен, тип 3 и коды 0–15 в зависимости от причины недоступности:0 — сеть недоступна;
  • 1 — хост недоступен;
  • 2 — протокол недоступен;
  • 3 — порт недоступен;
  • 4 — необходима фрагментация пакета, но она запрещена (стоит флаг DF — Don’t Fragment).




Остальное легко найти в интернете, а лучше почитать RFC 792.




Создадим цепочку ICMP и отправим в нее весь ICMP-трафик (можно создать две цепочки: для LAN и WAN — и настроить разные политики). Разрешаем только нужные типы сообщений и ограничиваем обработку пятью пакетами в секунду:




/ip firewall filter
add action=jump chain=WAN_INPUT jump-target=ICMP protocol=icmp
add action=jump chain=LAN_INPUT jump-target=ICMP protocol=icmp
add action=accept chain=ICMP comment="Allow Echo Reply (0:0-255), Limit 5pps" icmp-options=0:0-255 limit=5,5:packet protocol=icmp
add action=accept chain=ICMP comment="ICMP - Allow Destination Unreachable (3:0-255), Limit 5pps" icmp-options=3:0-255 limit=5,5:packet protocol=icmp
add action=accept chain=ICMP comment="ICMP - Allow Source Quench (4:0), Limit 5pps" icmp-options=4:0-255 limit=5,5:packet protocol=icmp
add action=accept chain=ICMP comment="ICMP - Allow Echo Request (8:0), Limit 5pps" icmp-options=8:0-255 limit=5,5:packet protocol=icmp
add action=accept chain=ICMP comment="ICMP - Allow Time Exceeded (11:0), Limit 5pps" icmp-options=11:0-255 limit=5,5:packet protocol=icmp
add action=accept chain=ICMP comment="ICMP - Allow Parameter Bar (12:0), Limit 5pps" icmp-options=12:0-255 limit=5,5:packet protocol=icmp
add action=drop chain=ICMP comment="ICMP - Drop All Others" protocol=icmp




Пример правила ICMP




TCP тоже поддерживает кучу флагов, часть которых не может содержаться в одном пакете. Комбинации этих флагов часто используются сканерами портов, чтобы пробить плохо настроенную защиту. Сделаем отдельную цепочку для TCP и дропнем подобные «подозрительные» пакеты:




/ip firewall filter
add action=jump chain=WAN_INPUT comment="Invalid TCP" jump-target=invalid_tcp protocol=tcp
add action=jump chain=LAN_INPUT comment="Invalid TCP" jump-target=invalid_tcp protocol=tcp
add action=jump chain=forward comment="Invalid TCP" jump-target=invalid_tcp protocol=tcp
add action=drop chain=invalid_tcp comment="Invalid TCP - !(FIN/SYN/RST/ACK)" protocol=tcp tcp-flags=!fin,!syn,!rst,!ack
add action=drop chain=invalid_tcp comment="Invalid TCP - FIN/SYN" protocol=tcp tcp-flags=fin,syn
add action=drop chain=invalid_tcp comment="Invalid TCP - FIN/RST" protocol=tcp tcp-flags=fin,rst
add action=drop chain=invalid_tcp comment="Invalid TCP - FIN/!ACK" protocol=tcp tcp-flags=fin,!ack
add action=drop chain=invalid_tcp comment="Invalid TCP - FIN/URG" protocol=tcp tcp-flags=fin,urg
add action=drop chain=invalid_tcp comment="Invalid TCP - SYN/RST" protocol=tcp tcp-flags=syn,rst
add action=drop chain=invalid_tcp comment="Invalid TCP - RST/URG" protocol=tcp tcp-flags=rst,urg
add action=drop chain=invalid_tcp comment="Invalid TCP - Source Port 0" protocol=tcp src-port=0
add action=drop chain=invalid_tcp comment="Invalid TCP - Destination Port 0" dst-port=0 protocol=tcp




Пример с TCP-флагами




То же самое для UDP:




add action=drop chain=invalid_udp comment="Invalid UDP - Source Port 0" protocol=udp src-port=0
add action=drop chain=invalid_udp comment="Invalid UDP - Destination Port 0" dst-port=0 protocol=udp




Цепочка Forward




До сих пор мы в основном смотрели на трафик, прилетевший в input-цепочку, а дальше по каким-то признакам направляли его в разные цепочки. Но весь этот трафик предназначался самому роутеру. Цепочку output используют редко, но ты можешь отфильтровать в ней, например, ICMP-ответы от роутера или IPsec-трафик. Понятно, что большая часть трафика будет попадать в forward — ведь на то он и роутер, чтобы перенаправлять пакеты из одной сети (локалка) в другую (интернет или второй VLAN локалки). И в этой цепочке мы будем управлять трафиком пользователей.




Я не стану детально рассказывать о том, что надо разрешить или запретить, — об основных приемах настройки и так уже написано несколько статей и есть куча примеров в интернете. Рассмотрим более интересный кейс: репутацию сети.




В интернете есть сервисы, содержащие списки спамеров, ддосеров, распространителей нелегального контента. Если на машины в твоей сети попал троян-спамер, то ты тоже окажешься в этих списках. Через какое-то время письма от любого клиента изнутри сети начнут попадать в спам у всех получателей, потом ты будешь добавлен в публичные блек-листы и у пользователей исчезнет доступ ко многим ресурсам. В том числе к сетям партнеров, админы которых пользуются такими списками, чтобы запретить доступ потенциальным вредителям. Представь, что произойдет с твоей премией, когда письмо с многомиллионным контрактом от твоего шефа упадет у контрагента в папку «Спам».




Попробуем защитить свою премию. Для этого нужно понять, по какому поводу нас могут внести в списки. Причин этому несколько:




  • мы часть DoS- или иного ботнета;
  • мы рассылаем спам;
  • с наших адресов брутфорсят чужие сервисы;
  • мы нарушаем авторские права (раздаем торренты).




Некоторые читатели этой статьи вполне могли участвовать в DDoS-ботнете, сами того не осознавая. Атаки UDP Amplification основаны на некорректных настройках сервисов, когда можно обратиться к ним с просьбой узнать что-то у другого сервера. Например, к нам может прилететь DNS-запрос с просьбой отрезолвить адрес жертвы. И таких, как мы, миллионы. Когда к жертве поступит миллион пакетов в секунду, она не обрадуется, а мы увидим загрузку CPU под 100%, жуткие тормоза и однажды окажемся в блек-листе. Такая же схема работает и с другими UDP-сервисами, например NTP. Вывод простой: блокируй трафик к этим сервисам снаружи. Но это все еще про INPUT.




Не только роутер может быть частью такого ботнета, но и машины внутри сети. Для детекта таких хостов воспользуемся уже известной фичей connection limit.




/ip firewall filter
add action=add-src-to-address-list address-list=dns-flood address-list-timeout=none-dynamic chain=forward comment="DNS Flood" connection-limit=100,32 dst-port=53 in-interface-list=LAN protocol=udp
add action=add-src-to-address-list address-list=smb-flood address-list-timeout=none-dynamic chain=forward comment="SMB Flood" connection-limit=100,32 dst-port=445 in-interface-list=LAN protocol=tcp 
add action=add-src-to-address-list address-list=telnet-flood address-list-timeout=none-dynamic chain=forward comment="Telnet Flood" connection-limit=20,32 dst-port=23 in-interface-list=LAN protocol=tcp
add action=add-src-to-address-list address-list=ssh-flood address-list-timeout=none-dynamic chain=forward comment="SSH Flood" connection-limit=20,32 dst-port=22 in-interface-list=LAN protocol=tcp
add action=add-src-to-address-list address-list=snpp-flood address-list-timeout=none-dynamic chain=forward comment="SNPP Flood" connection-limit=20,32 dst-port=444 in-interface-list=LAN protocol=tcp
add action=add-src-to-address-list address-list=msf-indication address-list-timeout=none-dynamic chain=forward comment="Metasploit Indication" connection-limit=20,32 dst-port=4444 in-interface-list=LAN protocol=tcp




Слишком «толстые» потоки тоже могут вызывать подозрения. Залогируем их:




add action=log chain=forward comment="Abnormal Traffic" connection-bytes=80000000 in-interface-list=LAN log-prefix=Abnormal-Traffic




По порту назначения можно определить, к какому сервису обращаются хосты изнутри нашей сети. И если это общеизвестный порт, например СУБД, а все наши базы расположены внутри периметра, логично предположить, что сотни пакетов в секунду к этому порту в интернете с компьютера бухгалтера — не простая ошибка и не личный интерес самого бухгалтера. Дропаем подозрительные пакеты и возвращаемся в родительскую цепочку (последнее правило):




add action=jump chain=forward comment="Jump to Virus Chain" disabled=no jump-target=Virus
add action=drop chain=Virus comment="Drop Blaster Worm" disabled=no dst-port=135-139 protocol=tcp
add action=drop chain=Virus comment="Drop Blaster Worm" disabled=no dst-port=445 protocol=tcp
add action=drop chain=Virus comment="Drop Blaster Worm" disabled=no dst-port=445 protocol=udp
add action=drop chain=Virus comment="Drop Messenger Worm" disabled=no dst-port=135-139 protocol=udp
add action=drop chain=Virus comment=Conficker disabled=no dst-port=593 protocol=tcp
add action=drop chain=Virus comment=Worm disabled=no dst-port=1024-1030 protocol=tcp
add action=drop chain=Virus comment="ndm requester" disabled=no dst-port=1363 protocol=tcp
add action=drop chain=Virus comment="ndm server" disabled=no dst-port=1364 protocol=tcp
add action=drop chain=Virus comment="screen cast" disabled=no dst-port=1368 protocol=tcp
add action=drop chain=Virus comment=hromgrafx disabled=no dst-port=1373 protocol=tcp
add action=drop chain=Virus comment="Drop MyDoom" disabled=no dst-port=1080 protocol=tcp
add action=drop chain=Virus comment=cichlid disabled=no dst-port=1377 protocol=tcp
add action=drop chain=Virus comment=Worm disabled=no dst-port=1433-1434 protocol=tcp
add action=drop chain=Virus comment="Drop Dumaru.Y" disabled=no dst-port=2283 protocol=tcp
add action=drop chain=Virus comment="Drop Beagle" disabled=no dst-port=2535 protocol=tcp
add action=drop chain=Virus comment="Drop Beagle.C-K" disabled=no dst-port=2745 protocol=tcp
add action=drop chain=Virus comment="Drop MyDoom" disabled=no dst-port=3127-3128 protocol=tcp
add action=drop chain=Virus comment="Drop Backdoor OptixPro" disabled=no dst-port=3410 protocol=tcp
add action=drop chain=Virus comment="Drop Sasser" disabled=no dst-port=5554 protocol=tcp
add action=drop chain=Virus comment=Worm disabled=no dst-port=4444 protocol=tcp
add action=drop chain=Virus comment=Worm disabled=no dst-port=4444 protocol=udp
add action=drop chain=Virus comment="Drop Beagle.B" disabled=no dst-port=8866 protocol=tcp
add action=drop chain=Virus comment="Drop Dabber.A-B" disabled=no dst-port=9898 protocol=tcp
add action=drop chain=Virus comment="Drop Dumaru.Y" disabled=no dst-port=10000 protocol=tcp
add action=drop chain=Virus comment="Drop MyDoom.B" disabled=no dst-port=10080 protocol=tcp
add action=drop chain=Virus comment="Drop NetBus" disabled=no dst-port=12345 protocol=tcp
add action=drop chain=Virus comment="Drop Kuang2" disabled=no dst-port=17300 protocol=tcp
add action=drop chain=Virus comment="Drop SubSeven" disabled=no dst-port=27374 protocol=tcp
add action=drop chain=Virus comment="Drop PhatBot, Agobot, Gaobot" disabled=no dst-port=65506 protocol=tcp
add action=drop chain=Virus comment="Drop MemCached flood" disabled=no dst-port=11211 protocol=udp
add action=return chain=Virus comment="Return From Virus Chain" disabled=no




Заключение




Мы рассмотрели более продвинутые методы настройки файрвола. Эту статью не нужно воспринимать как инструкцию по настройке: у каждой сети свои особенности и сервисы. Роутеры у всех тоже разные — у кого-то он спокойно обработает тысячи неоптимизированных правил файрвола, для других сотня правил будет обрабатываться с трудом. Поэтому подходи к настройке файрвола с умом.




В две статьи всё не вместишь, и мы не затронули еще несколько больших тем: таблицы NAT, RAW, IPv6 Firewall, Bridge Firewall, фильтрацию по контенту, определение типа трафика по его содержимому (когда мы меняем порт у HTTP, а файрвол все равно понимает, что внутри HTTP), проксирование трафика.




Все эти темы рассматриваются в официальном обучающем курсе MikroTik — MikroTik Certified Traffic Control Engineer. Но чтобы на него попасть, нужно пройти курс MikroTik Certified Network Associate, где изучаются общие принципы настройки роутера и работа TCP/IP.




Источник: https://telegra.ph/Haker—Stena-ognya-lvl2-Nastraivaem-fajrvol-dlya-otrazheniya-atak-na-primere-MikroTik-09-23



2022-08-08T00:38:40
Network

На коленке: агрегация VPN, или надежная связь на ненадежных каналах

Представьте задачу: необходимо обеспечить стабильным интернетом и покрыть бесшовным Wi-Fi здание площадью 300 м2 с возможной расчетной нагрузкой до 100 человек. На первый взгляд, “вроде изян”. Но стоит добавить пару деталей, и задача усложняется:




  • здание стоит в лесопарковой зоне, где нет оптики, так что наш вариант – мобильная связь;
  • нужно обеспечить регулярные видеотрансляции, то есть добиться стабильного интернета при единственном GSM-провайдере;
  • бюджет ограничен.




Итого: потери и отвалы от базовой станции подкрадываются в самое неподходящее время.




Такие проблемы я встречал у колл-центров без выделенных каналов связи, передвижных репортерских комплексов, критически важных удаленных систем. Трудности могут возникнуть не только в случае с VoIP и стримингом видео, но и с любым запросом на гарантированный канал доставки чувствительного к потерям трафика. К сожалению, не всегда есть возможность подвести оптику и закупить дорогостоящее оборудование.




В статье покажу, как в одном проекте я решил эти задачи “дешево и сердито” – такой вариант подойдет малому бизнесу. В конце предложу варианты решения для менее скромного бюджета – для крупных заказчиков.




Схема решения вкратце




Итак, при первом столкновении с проблемой отвалов я начал с агрегации частот и убедился, что это не поможет. Смена категории LTE-модема с Cat4 на Cat6 или – еще круче – Cat12 давала преимущество в скорости, но в потерях и отвалах – нет. Пришел к выводу, что нужен второй LTE-провайдер. При этом при переключении не должен потеряться ни один кадр и трансляция не должна отвалиться.




На помощь пришла такая связка: агрегация, она же bonding, и TCP-OpenVPN-туннель поверх этого.




  1. в облаке создал “сервер агрегации” – виртуалку с CLOUD HOSTED ROUTER (CHR) на базе Router OS;
  2. на ней поднял L2TP-сервер с включенным шифрованием IPsec;
  3. поверх L2TP over IPsec создал два EoIP-туннеля;
  4. EoIP-туннели агрегированы bonding-интерфейсом;
  5. вишенка на торте – TCP-шный OpenVPN-туннель.




Итоговая схема:







Вместо виртуальной машины в дата-центре в качестве R1 может выступать любая железка с достаточной производительностью. Например, тот же MikroTik серии CCR, компьютер, размещенный где угодно. Главное – позаботиться о производительности и стабильных каналах связи, использовать схемы активного резервирования (VRRP в помощь).




Поддержка OpenVPN UDP реализована только в 7-й версии RouterOS, поэтому в этой конфигурации безальтернативно используется протокол TCP.




Сейчас схема стабильно работает, но нет предела совершенству. Для надежности можно добавить еще LTE-провайдеров или проводные каналы связи, когда такая возможность появится.




Теперь расскажу подробнее о строительстве схемы. Начнем с R1 (облачного маршрутизатора) и – далее – R2 (филиального).




Маршрутизатор R1




  1. Сначала берем второй белый IP в дата-центре. У меня CHR находился за Edge в облаке VMware, так что обязательно пробрасываем порты на Edge UDP 1701, 500 и 4500 NAT-T – IPSec Network Address Translator Traversal. Также делаем разрешающее правило в межсетевом экране Edge.
  2. Добавляем в таблицу firewall filter разрешающее правило доступа к маршрутизатору извне для портов UDP 1701, 500 и 4500. Если у вас белые IP непосредственно на маршрутизаторе без пробросов через Edge, галочку NAT Traversal НУЖНО СНЯТЬ!Проверяем дефолтный IPsec-профиль:







/ip ipsec profile
set [ find default=yes ] dh-group=modp1024 enc-algorithm=3de




  1. Создаем профиль для L2TP-туннелей:







/ppp profile
add change-tcp-mss=no name=profile01 use-compression=no use-encryption=no use-mpls=no use




и настраиваем учетные записи:







/ppp secret
add local-address=172.16.0.1 name=l2tp_R1-R2_ISP1 password=ros7.elements.forever profile=profile01 remote-address=172.16.0.2 service=l2tp
add local-address=172.16.0.5 name=l2tp_R1-R2_ISP2 password=ros7.elements.forever profile=profile01 remote-address=172.16.0.6 service=l2tp




  1. Активируем L2TP-сервер и включаем шифрование IPsec:







/interface l2tp-server server
set authentication=mschap2 caller-id-type=number default-profile=profile01 enabled=yes ipsec-secret=ВАШ КРУТОЙ ПАРОЛЬ use-ipsec=yes




  1. Поднимаем два EoIP-туннеля поверх L2TP/IPsec-туннелей:







/interface eoip
add keepalive=1s,5 local-address=172.16.0.1 mac-address=00:00:00:00:00:A1 name=eoip-tun1_over_l2tp_R1-R2_ISP1 remote-address=172.16.0.2 tunnel-id=1
add keepalive=1s,5 local-address=172.16.0.5 mac-address=00:00:00:00:00:B1 name=eoip-tun2_over_l2tp_R1-R2_ISP2 remote-address=172.16.0.6 tunnel-id=2




Обязательно указываем минимальный keepalive timeout равным 1 секунде и для каждого EoIP-туннеля указываем уникальный ID.




  1. Настраиваем bonding и назначаем на него IP-адрес:







/interface bonding
add lacp-rate=1sec mii-interval=1ms mode=broadcast name=bonding1 slaves=eoip-tun1_over_l2tp_R1-R2_ISP1,eoip-tun2_over_l2tp_R1-R2_ISP2




/ip address
add address=172.16.1.1/30 interface=bonding1




Тут важно заметить, что в поле mode (режим работы bonding-интерфейса) я указал broadcast, чтобы пакеты отправлялись сразу по двум тоннелям. Таким образом потеря пакета на любом из двух интерфейсов не приведет к потере пакета на bonding-интерфейсе. Остальные значения устанавливаем, как на картинке.




Активируем OpenVPN-сервер




Так как у меня OpenVPN использовался еще и для внешних подключений, то я предварительно сгенерировал сертификаты и импортировал их в CHR. На этом останавливаться подробно не буду.




Создаем /ppp profile и /ppp secret для OpenVPN:










/ppp profile
add change-tcp-mss=no name=profile02 use-compression=no use-encryption=no use-mpls=no use
/ppp secret
add local-address=172.16.2.1 name=ovpn_over_bonding1 password=ros7.elements.forever profile=profile02 remote-address=172.16.2.2 service=ovpn
/interface ovpn-server server
set auth=sha1 certificate=server.crt_0 cipher=aes256 default-profile=profile02 enabled=yes keepalive-timeout=30 port=1194 require-client-certificate=yes




Обязательно прописываем в nat-таблицу межсетевого экрана правило для нашей серой филиальной сети за маршрутизатором R2, чтобы трафик выходил наружу через R1:







/ip firewall nat
add action=masquerade chain=srcnat out-interface-list=WAN src-address=192.168.1.0/24




Обратный маршрут до серой сети за маршрутизатором R2 указываем через OpenVPN-туннель:







/ip route
add check-gateway=ping distance=1 dst-address=192.168.1.0/24 gateway=172.16.2.2




Маршрутизатор R2




  1. Первым делом прописываем маршруты от одного интерфейса LTE-модема до одного белого IP-адреса дата-центра. Запрещаем в настройках межсетевого экрана в цепочке output прохождение пакетов с другого интерфейса:




/ip route
add distance=1 dst-address= 198.51.100.10/32 gateway=lte1
add distance=1 dst-address= 198.51.100.20/32 gateway=lte2
/ip firewall filter
add action=drop chain=output dst-address= 198.51.100.10 out-interface=lte2
add action=drop chain=output dst-address= 198.51.100.20 out-interface=lte1




  1. Приводим в соответствие с R1 дефолтный конфиг /ip ipsec profile:







/ip ipsec profile
set [ find default=yes ] dh-group=modp1024 enc-algorithm=3de




  1. Создаем /ppp profile:







и два L2TP/IPsec-подключения к дата-центру для каждого из провайдеров:







/ppp profile
add change-tcp-mss=no name=profile01 use-compression=no use-encryption=no use-mpls=no use
/interface l2tp-client
add allow=mschap2 connect-to= 198.51.100.10 disabled=no ipsec-secret= ros7.elements.forever keepalive-timeout=30 name=l2tp_to_R1_over_ISP1 password=ros7.elements.forever
    profile=profile01 use-ipsec=yes user=l2tp_R1-R2_ISP1
add allow=mschap2 connect-to= 198.51.100.20 disabled=no ipsec-secret= ros7.elements.forever keepalive-timeout=30 name=l2tp_to_R1_over_ISP2 password=ros7.elements.forever
    profile=profile01 use-ipsec=yes user=l2tp_R1-R2_ISP2




  1. Создаем EoIP-туннели по аналогии с R1, только меняем местами local и remote IP L2TP/IPsec-линков маршрутизатора R2. Bonding-интерфейс такой же, как на R1:







/interface eoip
add keepalive=1s,5 local-address=172.16.0.2 mac-address=00:00:00:00:00:A2 name=eoip-tun1_over_l2tp_R1-R2_ISP1 remote-address=172.16.0.1 tunnel-id=1
add keepalive=1s,5 local-address=172.16.0.6 mac-address=00:00:00:00:00:B2 name=eoip-tun2_over_l2tp_R1-R2_ISP2 remote-address=172.16.0.5 tunnel-id=2
/interface bonding
add lacp-rate=1sec mii-interval=1ms mode=broadcast name=bonding1 slaves=eoip-tun1_over_l2tp_R1-R2_ISP1,eoip-tun2_over_l2tp_R1-R2_ISP2
/ip address
add address=172.16.1.2/30 interface=bonding1




  1. Также импортируем сертификаты, создаем профиль:










Настраиваем OpenVPN-клиента на R2:







/ppp profile
add change-tcp-mss=no name=profile02 use-compression=no use-encryption=no use-ipv6=no use-mpls=no use-upnp=no
/interface ovpn-client
add certificate=client.crt_0 cipher=aes256 connect-to=172.16.1.1 mac-address=00:00:00:00:00:C2 name=ovpn_over_bonding1 password=ВАШ КРУТОЙ ПАРОЛЬ profile=profile02 use-peer-dns=no user="ovpn_over_bonding1 " verify-server-certificate=yes




  1. Туннели загорелись волшебной буквой R, а EoIP – еще и RS. OpenVPN тоже завелся. Теперь можно направлять трафик с компьютера трансляций в наш слоеный бутерброд – в OpenVPN-туннель. Для этого создаем правило /ip firewall mangle и прописываем сразу новую таблицу маршрутизации:







/ip firewall mangle
add action=mark-routing chain=prerouting dst-address-list=google_sites dst-port=1935 new-routing-mark=pc_to_stream-youtube_over_R1 passthrough=yes protocol=tcp src-address=192.168.1.1




  1. Создаем маршрут через наш OpenVPN-туннель с данной таблицей маршрутизации:







/ip route
add check-gateway=ping distance=1 gateway=172.16.2.1 routing-mark=pc_to_stream-youtube_over_R1




И готово!




Траблшутинг




  • При развертывании конфигурации на действующем железе нужно обязательно переключить прямой и обратный маршруты с туннелей L2TP на OpenVPN-туннель. Если, например, переключить только прямой маршрут, а обратный оставить на L2TP вместо OpenVPN, агрегация полностью работать не будет и пакеты все равно будут теряться.
  • Утилиты RouterOS в разделе /tools очень полезны при траблшутинге. Еще неплохо работает связка /tools Packet Sniffer + Wireshark.
  • Не забудьте “поиграться с mtu”, чтобы достичь лучшей производительности туннелей.
  • Качество сигнала никто не отменял. RSRP, RSRQ и SINR покажут, насколько все хорошо. При большом удалении от базовой станции и плохом сигнале помогут внешние направленные антенны.
  • Важно! Если провайдер фильтрует трафик и идет блокировка L2TP, то можно поднять другие туннели в качестве основы для EoIP, например: OpenVPN или SSTP.
  • Чтобы проверить все в деле, можно сымитировать сбой. Отключаем любой из LTE-интерфейсов или создаем потери искусственно: добавляем в межсетевой экран правило частичной блокировки пакетов и указываем при создании нового правила значение в поле random.




Что еще можно улучшить и оптимизировать




  • Не рекомендую заворачивать весь интернет-трафик, так как это вызовет повышенные накладные расходы (утилизация процессоров, каналов и др.). Лучше пользоваться маркировкой для гарантированной доставки действительно необходимого трафика, а все остальное отправлять на LTE-провайдеров. К примеру, я так делал с загрузкой видеофайлов на облачный диск.
  • QOS – хорошая штука, особенно на каналах LTE, и особенно с VoIP. Не забываем про это, чтобы остальной трафик не забил и так не слишком широкий канал.
  • Можно усилить безопасность, если ограничить подключение извне к портам для L2TP и IPsec маршрутизатора R1. Указываем белый IP LTE-провайдера  с помощью firewall и адресных листов. Хоть адрес и из NAT и на нем висит не один клиент, все равно будет лучше. Так как IP динамический, то нужно включить на MikroTik функцию ip – cloud, чтобы DNS-сервера всегда знали актуальный IP, технология DDNS.




Конечно же, у схемы есть коммерческие аналоги с возможностями работы из коробки, например: peplink MAX HD4 LTE и тому подобное оборудование, – агрегирующие соединения. Тут бизнес сам оценивает их стоимость для себя.




Источник: https://uni.dtln.ru/digest/na-kolenke-agregaciya-vpn-ili-nadezhnaya-svyaz-na-nenadezhnyh-kanalah



2022-08-08T00:33:16
Network

Mkrotik и vlan по быстрому.

Купили новый маленький роутер от Mikrotik — RB4011iGS+RM. Решили попробовать как он будет работать в маленькой сетке в качестве роутера (странно? Да? 🙂 ). У него есть 10G SFP+ интерфейс, подключенный непосредственно к процессору. Если включить FastPath, то по идее должен справляться.




SFP+ интерфейс естественно в trank. На всякий пожарный eth6, тоже в транк. Eth с 1-го по 5-й в 1-й vlan, в режиме access mode, что бы подключаться прямо в серверной к коммутаторам, если вдруг чего. Остальные не задействованы.




При обращении к Google с вопросом Mirotik+vlan вываливается куча страниц с описанием настройки vlan. Но, проблема в том, что они блин все устарели! В новых версиях router os все уже не так.




Как сейчас рулить vlan с возможностью маршрутизации? Теперь там все через bridge интерфейс.




В первую очередь добавляем сам мост:




> /interface bridge




/interface bridge> add name=core




Добавим к мосту порты, в которых будут бегать тегированные пакеты:




/interface bridge> port




/interface bridge port> add bridge=core interface=sfp-trunk




/interface bridge port> add bridge=core interface=ether6




Порты, работающие в access режиме:




/interface bridge port> add bridge=core interface=ether1




/interface bridge port> add bridge=core interface=ether2




/interface bridge port> add bridge=core interface=ether3




/interface bridge port> add bridge=core interface=ether4




/interface bridge port> add bridge=core interface=ether5




Там же, в разделе bridge есть возможность указывать какие vlan на каком интерфейсе и режим работы интерфейса.




/interface bridge port> /interface bridge vlan




/interface bridge vlan> add bridge=core tagged=sfp-trunk,ether6,core untagged=ether1,ether2,ether3,ether4,ether5 vlan-ids=1




/interface bridge vlan> add bridge=core tagged=sfp-trunk,ether6,core vlan-ids=1010,1011,1254




Итак, мы добавили vlan 1 к нужным нам интерфейсам и заодно определили режимы работы самих интерфейсов. Так же были определены другие vlan, которые будут приходить на mikrotik и в дальнейшем мы будем заниматься маршрутизацией между этими сетями.




На данном этапе пакеты Ethernet начнут бегать в своих vlan.




Пришло время заняться маршрутизацией.




Обязательно добавляйте интерфейс моста в параметре tagget при добавлении vlan в разделе bridge! Без этого у нас ничего не получиться на 3-м уровне 🙁




Для того, что бы маршрутизатор смог маршрутизировать 🙂 нам необходимо для каждого vlan создать свой интерфейс и задать им параметры ip.




Как обычно, интерфейсы создаём в разделе interface vlan (не bridge vlan!).




> /interface vlan




/interface vlan> add interface=core name=VLAN-1254 vlan-id=1254




/interface vlan> add interface=core name=VLAN-1010 vlan-id=1010




/interface vlan> add interface=core name=VLAN-1011 vlan-id=1011




Обратите внимание, что vlan-ы мы добавляем к bridge интерфейсу core. Не зря мы включали на этом интерфейсе режим работы tagged.




Осталось добавить ip адреса на интерфейсы:




> /ip address




/ip address> add address=192.168.1.1/24 interface=core network=192.168.1.0




/ip address> add address=192.168.10.1.24 interface=VLAN-1010 network=192.168.10.0




/ip address> add address=192.168.11.1/24 interface=VLAN-1011 network=192.168.11.0




/ip address> add address=192.168.254.1/24 interface=VLAN-1254 network=192.168.254.0




Наши админы очень любят DHCP сервер mikrotik, поэтому его тоже придется поднимать на этом устройстве. Поскольку интерфейсы сконфигурированы, DHCP сервер настраивается обычным образом.




Вот и все.




Источник: https://www.kryukov.biz/2019/04/mkrotik-i-vlan-po-bystromu/



2022-08-08T00:24:11
Network

Pfsense и MikroTik, настройка VPN туннеля

Инструкция по настройке VPN туннеля типа IpSec между облачным роутером Pfsense и MikroTik. В результате настройки должно получиться объединение двух сетей, за MikroTik и за Pfsense.







Что такое Pfsense




PfSense — дистрибутив для создания межсетевого экрана/маршрутизатора, основанный на FreeBSD. PfSense предназначен для установки на персональный компьютер, известен своей надежностью и предлагает функции, которые часто можно найти только в дорогих коммерческих межсетевых экранах. Настройки можно проводить через web-интерфейс, что позволяет использовать его без знаний базовой системы FreeBSD. Сетевые устройства с pfSense обычно применяются в качестве периметровых брандмауэров, маршрутизаторов, серверов DHCP/DNS, и в технологии VPN в качестве узла топологии hub/spoke.




Настройка VPN в MikroTik для подключения к Pfsense




Со стороны роутера MikroTik будет настроен стандартный VPN туннель типа IpSec. Больше сведений по настройке VPN типа IpSec представлено в статье:




VPN туннель IpSec состоит из двух фаз:




  • PHASE-1 – идентификация устройств между собой, по заранее определенному IP адресу и ключу.
  • PHASE-2 – определение политики для трафика между туннелей: шифрование, маршрутизация, время жизни туннеля.




Создание профиля для MikroTik IpSec phase-1




Настройка находится в IP→IPsec→Profile




Pfsense и MikroTik, создание профиля для IpSec phase-1




Создание Peer для MikroTik IpSec phase-1




Настройка находится в IP→IPsec→Peers




Pfsense и MikroTik, создание Peer для IpSec phase-1




Определение ключа MikroTik IpSec phase-1




Настройка находится в IP→IPsec→Identities




Pfsense и MikroTik, определение ключа IpSec phase-1




Настройка параметров MikroTik Proposal IpSec phase-2




Настройка находится в IP→IPsec→Proposals




Pfsense и MikroTik, настройка параметров Proposal IpSec phase-2




Создание политики(Policies) MikroTik IpSec phase-2




Настройка находится в IP→IPsec→Policies




Pfsense и MikroTik, создание политики(Policies) IpSec phase-2




Настройка политики(Policies) MikroTik IpSec phase-2




Настройка находится в IP→IPsec→Policies→Action




Pfsense и MikroTik, настройка политики(Policies) IpSec phase-2




/ip ipsec profile
add dh-group=modp1024 enc-algorithm=3des hash-algorithm=md5 lifetime=8h name=Pfsense
/ip ipsec peer
add address=10.10.10.10/32 name=Pfsense profile=Pfsense
/ip ipsec proposal
add auth-algorithms=md5 enc-algorithms=3des lifetime=8h name=Pfsense
/ip ipsec policy
add dst-address=192.168.5.0/24 peer=Pfsense proposal=Pfsense 
sa-dst-address=10.10.10.10 sa-src-address=0.0.0.0 src-address=
192.168.1.0/24 tunnel=yes
/ip ipsec identity
add peer=Pfsense secret=Pt36ENepT3t3




Настройка VPN в Pfsense для подключения к MikroTik




Pfsense имеет удобный web интерфейс, с помощью которого и будет производиться настройка VPN туннеля типа IpSec для связи с роутером MikroTik.




Настройка phase-1 для Pfsense IpSec




Создание phase-1 для Pfsense IpSec







Настройка phase-2 для Pfsense IpSec




Настройка phase-2 для Pfsense IpSec




Настройка phase-2 для Pfsense IpSec, шифрование




Настройка phase-2 для Pfsense IpSec, общие параметры




Настройка Pfsense Firewall




Настройка Pfsense Firewall, разрешение внешнего подключения IpSec




Настройка Pfsense Firewall, обмен трафиком внутри VPN




Результат настройки VPN IpSec между Pfsense и MikroTik




После успешно принятых настроек, проверить соединение VPN типа IpSec между Pfsense и MikroTik можно так:




Pfsense и MikroTik, статус VPN туннеля IpSec




MikroTik и Pfsense, статус VPN IpSec туннеля




Источник: https://xn—-7sba7aachdbqfnhtigrl.xn--j1amh/pfsense-i-mikrotik-nastrojka-vpn-tunnelya/



2022-08-08T00:21:27
Network