Я использую Google Cloud с Kubernetes Engine в течение 2 месяцев. На самом деле мне не понадобилось и месяца, чтобы уложить все в голове, но потребовалось еще столько же, чтобы разобраться с некоторыми неприятностями.
TL;DR: Google делает довольно хорошую работу, поэтому AWS не расслабляется. Если вы хорошо знаете AWS, я бы посоветовал протестировать Google Cloud. Возможно, из-за мышечной памяти мне было бы комфортнее с AWS, но я изучил Google Cloud и Kubernetes и уверен в них для большинства моих сценариев.
Я не эксперт, поэтому примите мои слова с долей скептицизма. Google Cloud и Kubernetes – одна из тех тем, о которых я очень хочу поговорить, но я не всегда могу подобрать правильные слова и надеюсь, что вы получите верное представление о предлагаемых решениях.
Цель статьи – сохранить некоторые фрагменты и мысли для дальнейшего использования. Поэтому имейте в виду, что это не пошаговое руководство. Сперва я намеревался написать руководство, но потом понял, что это почти как написать целую книгу, так что не в этот раз.
Чтобы добиться успеха с чем-то вроде Google Cloud и Kubernetes, у вас должно быть достаточно опыта. Если вы никогда не устанавливали Linux From Scratch, если никогда не проводили оптимизацию сервера, если вам не нравятся железные серверные компоненты, не пытайтесь выполнить реальное производственное развертывание. Ваша самая безопасная ставка по-прежнему будет на Heroku.
Вы должны быть тем человеком, который любит повозиться (как в моих предыдущих блогах).
Я не знаю всего, но знаю достаточно. Для начала предстояло понять, что мне нужно. Важно излагать свои потребности, прежде чем пытаться написать первый файл YAML. Планирование имеет решающее значение.
Вот что нужно было мне:
- Масштабируемый уровень веб-приложений, где я мог бы выполнять как скользящие обновления (для нулевых обновлений простоя), так и автоматическое и ручное горизонтальное масштабирование серверов.
- Монтируемое постоянное хранилище с автоматическими моментальными снимками / резервными копиями.
- Управляемая надежная база данных (Postgresql) с автоматическим резервным копированием и простой репликацией в экземпляры только для чтения.
- Управляемое решение для хранения секретов (таких как поддержка ENV Heroku). Никогда не храните производственную конфигурацию в исходном коде.
- Поддержка изображений Docker без необходимости создания настраиваемой инфраструктуры для развертывания.
- Статические внешние IP-адреса для интеграции, требующие фиксированного IP-адреса.
- SSL-прекращение, чтобы я мог подключиться к CloudFlare (CDN является обязательным, но недостаточным. В 2018 году нам нужна будет защита от DDoS).
- Достаточно безопасности по умолчанию, то есть теоретически все заблокировано, пока я не решу открыть.
- Высокая доступность в различных регионах и зонах центров обработки данных.
Простое демонстрационное веб-приложение легко развернуть. Но я не хотел демо, я хотел решение для производства в долгосрочной перспективе.
Некоторые проблемы новичков:
- Документация очень обширна, и вы найдете почти все, если знаете, что ищете. Также имейте в виду, что Azure и AWS реализуют Kubernetes с определенными отличиями, поэтому некоторые документы не применяются к Google Cloud и наоборот.
- В альфа-, бета- и стабильных стадиях есть много особенностей. Документация, вроде бы, понятна, но большинство учебных пособий, которым всего лишь пару месяцев, могут работать не так, как предполагалось (Kubernetes 1.8.4-gke в их числе).
- Существует целый набор слов, которые относятся к одному и тому же понятию. Нужно привыкнуть к лексике.
- Представьте, что вы играете в лего. Детали можно мешать, по-разному сочетать, но очень легко все испортить. Это значит, что вы можете создать свою конфигурацию, которая будет отвечать вашим требованиям. Но нельзя ее просто скопировать.
- Вы можете сделать почти все через YAML файлы и командную строку, но повторное использование конфигурации (для рабочей и промежуточной среды, например) не тривиальная задача. Существуют сторонние инструменты, которые имеют дело с параметризуемыми и многоразовыми битами YAML, но я бы сделал все это вручную. Никогда, никогда не пробуйте автоматизированные шаблоны в инфраструктуре, не зная точно, что они делают.
- У вас есть два тяжелых инструмента командной строки:
gcloud
иkubectl
.
Еще раз напомню: это не пошаговое руководство. Я аннотирую несколько шагов.
Масштабируемый веб-уровень (само веб-приложение)
Первое, что вам необходимо, – полное 12-factors app.
Будь то Ruby on Rails, Django, Laravel, Node.js (что угодно), это должно быть приложение, которое не имеет общего доступа и не зависит от записи чего-либо в локальной файловой системе. Приложение, которое вы можете легко отключать и запускать экземпляры независимо. Не должно быть сессий в локальной памяти или в локальных файлах (я предпочитаю избегать близости сессии). Отсутствует загрузка файлов в локальную файловую систему (если необходимо, вам нужно смонтировать внешнее постоянное хранилище), всегда предпочитайте отправлять двоичные потоки в управляемые службы хранения.
У вас должен быть надлежащий конвейер, который выводит кеширование через фингерпринтные активы (нравится вам это или нет, Rails по-прежнему имеет лучшее из готовых решений в Asset Pipeline).
Проинструментируйте приложение, добавьте New Relic RPM и Rollbar.
2018 год, вы не хотите разворачивать собственный код с помощью SQL-инъекции (или любой другой входной), неконтролируемый eval вокруг вашего кода, нет места для CSRF или XSS и т. д. Двигайтесь вперед, купите лицензию на Brakeman Pro и добавьте его в конвейер CI. Я могу подождать…
Поскольку это не учебник, предполагаю, что вы можете зарегистрироваться в Google Cloud и настроить проект, свой регион и зону.
Мне потребовалось некоторое время, чтобы понять первичную структуру в Google Cloud:
- Вы начинаете с проекта, который служит прикрытием для всего необходимого для вашего приложения.
- Затем вы создаете «кластеры». Например, у вас может быть производственный, промежуточный кластер, веб-кластер или разделенный кластер служб для несетевых материалов.
- У кластера есть «кластер-мастер», который является контроллером всего остального (команды
gcloud
иkubectl
взаимодействуют API-интерфейсами). - У кластера множество «экземпляров узлов», правильных «машин» (или, точнее, экземпляров VM).
- Каждый кластер также имеет по крайней мере один «пул узлов» («пул по умолчанию»), который представляет собой набор экземпляров узлов с одинаковой конфигурацией и тем же «машинным типом».
- Наконец, каждый экземпляр узла запускает один или несколько «контейнеров», которые представляют собой простые контейнеры, такие как LXC. Вот где ваше приложение на самом деле.
Пример создания кластера:
gcloud container clusters create my-web-production --enable-cloud-logging --enable-cloud-monitoring --machine-type n1-standard-4 --enable-autoupgrade --enable-autoscaling --max-nodes=5 --min-nodes=2 --num-nodes 2
Как я уже упоминал, кластер создает default-pool
с машинным типом n1-standard-4
. Выберите комбинацию CPU/RAM, которая вам понадобится для приложения. Тип, который я выбрал, имеет 4 vCPU и 15 ГБ оперативной памяти.
По умолчанию он начинается с 3 узлов, поэтому я сначала выбрал 2, но автоматически масштабировался до 5 (вы можете обновить его позже, если нужно, но убедитесь, что есть место для первоначального роста). Вы можете продолжать добавлять дополнительные пулы для экземпляров узлов разного размера, скажем, для рабочих Sidekiq, чтобы выполнять интенсивную обработку фона. Затем создайте отдельный пул узлов с другим машинным типом для своего набора экземпляров, например:
gcloud container node-pools create large-pool --cluster=my-web-production --node-labels=pool=large --machine-type=n1-highcpu-8 --num-nodes 1
Этот другой пул управляет 1 узлом типа n1-highcpu-8
, который имеет 8 vCPU с 7,2 ГБ ОЗУ. Больше процессоров, меньше памяти. У вас есть категория highmem
, которая меньше CPU с гораздо большим объемом памяти. Опять же, нужно знать, чего вы хотите.
Важный момент здесь – --node-labels
– как я буду сопоставлять развертывание для выбора между пулами узлов (в данном случае между пулом по умолчанию и большим пулом).
Создав кластер, вы должны выдать следующую команду для получения своих учетных данных:
gcloud container clusters get-credentials my-web-production
Команда задает kubectl
. Если у вас более одного кластера (my-web-production
и my-web-staging
), нужно быть осторожными сget-credentials
для правильного кластера, иначе вы можете запустить промежуточную развертывание на производственном кластере.
Поскольку это сбивает с толку, я изменил свой ZSH PROMPT, чтобы всегда видеть, с каким кластером сталкиваюсь. Я адаптировался из zsh-kubectl-prompt:
Поскольку у вас будет много кластеров в большом приложении, я настоятельно рекомендую добавить этот PROMPT в свою оболочку.
Как вы разворачиваете приложение в подах в экземплярах узлов?
У вас должен быть файл Docker в репозитории проектов приложений для создания образа Docker. Это один из примеров приложения Ruby on Rails:
FROM ruby:2.4.3
ENV RAILS_ENV production
ENV SECRET_KEY_BASE xpto
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get update && apt-get install -y nodejs postgresql-client cron htop vim
ADD Gemfile* /app/
WORKDIR /app
RUN gem update bundler --pre
RUN bundle install --without development test
RUN npm install
ADD . /app
RUN cp config/database.yml.prod.example config/database.yml && cp config/application.yml.example config/application.yml
RUN RAILS_GROUPS=assets bundle exec rake assets:precompile
В Google Cloud Web Console вы найдете «Container Registry», который является Private Docker Registry.
Добавьте удаленный URL-адрес в локальную конфигурацию следующим образом:
git remote add gcloud https://source.developers.google.com/p/my-project/r/my-app
Теперь вы можете git push gcloud master
. Я рекомендую добавить триггеры, чтобы помечать изображения. Я добавляю 2 триггера: один, чтобы пометить его latest
, а другой – пометить его случайным номером версии. Вам это понадобится позже.
После добавления репозитория реестра в качестве удаленного в вашей конфигурации git (git remote add
) нажмите на него. Он должен начать создавать образ Docker с соответствующими тегами, которые вы настроили с помощью триггеров.
Убедитесь, что ваше приложение Ruby on Rails не имеет ничего в инициализаторах, которым требуется подключение к базе данных, поскольку оно недоступно. Вы можете застрять, когда сборка Docker завершится с ошибкой из-за того, что assets:precompile
— задача загрузила инициализатор, который случайно вызывает модель, а это вызывает триггеры ActiveRecord :: Base
для попытки подключиться.
Убедитесь, что версия Ruby в файле Dockerfile совпадает с версией в Gemfile, иначе она также потерпит неудачу.
Вы заметили странную config/application.yml
. Это от фигаро. Рекомендую упростить настройку переменной ENV в системе. Мне не нравятся секреты Rails, и это не совсем дружественно по отношению к системам развертывания после того, как Heroku сделал ENV vars повсеместным. Придерживайтесь ENV vars. Kubernetes поблагодарит вас за это.
Теперь вы можете переопределить любую переменную окружения из файла развертывания Kubernetes и yaml. Сейчас самое время показать пример. Вы можете назвать его deploy / web.yml
или как-то, как вам подходит. И, конечно же, проверьте его в репозитории исходного кода.
kind: Deployment
apiVersion: apps/v1beta1
metadata:
name: web
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
minReadySeconds: 10
replicas: 2
template:
metadata:
labels:
app: web
spec:
containers:
- image: gcr.io/my-project/my-app:latest
name: my-app
imagePullPolicy: Always
ports:
- containerPort: 4001
command: ["passenger", "start", "-p", "4001", "-e", "production",
"--max-pool-size", "2", "--min-instances", "2", "--no-friendly-error-pages"
"--max-request-queue-time", "10", "--max-request-time", "10",
"--pool-idle-time", "0", "--memory-limit", "300"]
env:
- name: "RAILS_LOG_TO_STDOUT"
value: "true"
- name: "RAILS_ENV"
value: "production"
# ... obviously reduced the many ENV vars for brevity
- name: "REDIS_URL"
valueFrom:
secretKeyRef:
name: my-env
key: REDIS_URL
- name: "SMTP_USERNAME"
valueFrom:
secretKeyRef:
name: my-env
key: SMTP_USERNAME
- name: "SMTP_PASSWORD"
valueFrom:
secretKeyRef:
name: my-env
key: SMTP_PASSWORD
# ... this part below is mandatory for Cloud SQL
- name: DB_HOST
value: 127.0.0.1
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: password
- name: DB_USER
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: username
- image: gcr.io/cloudsql-docker/gce-proxy:latest
name: cloudsql-proxy
command: ["/cloud_sql_proxy", "--dir=/cloudsql",
"-instances=my-project:us-west1:my-db=tcp:5432",
"-credential_file=/secrets/cloudsql/credentials.json"]
volumeMounts:
- name: cloudsql-instance-credentials
mountPath: /secrets/cloudsql
readOnly: true
- name: ssl-certs
mountPath: /etc/ssl/certs
- name: cloudsql
mountPath: /cloudsql
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
- name: ssl-certs
hostPath:
path: /etc/ssl/certs
- name: cloudsql
emptyDir:
Поясню пример:
kind
иapiVersion
– важны, следите за документацией, она может измениться. Это то, что называется Deployment. Раньше был контроллер репликации (вы найдете его в старых учебниках), но он больше не используется. Рекомендация: изпользуйте ReplicaSet.- Называйте вещи своими именами, у вас есть
metadata:name
сweb
. Обратите внимание наspec:template:metadata:labels
, где я помещаю каждый блок с меткойapp: web
. Она понадобится, чтобы иметь возможность выбирать элементы позже в разделе «Сервис». - У меня есть
spec:strategy
, в которой мы настраиваем Rolling Update. spec:replicas
объявляет, сколько подов я хочу. Вам придется вручную вычислить машинный тип пула узлов, а затем разделить ресурсы сервера для каждого приложения.- Изображение Docker, которое мы создали, с «последним» тегом. Вы ссылаетесь на него в
spec: template: spec: container: image
. - Я использую Passenger с конфигурацией производства (ознакомьтесь с документацией Phusion)
- В spec: template: spec: container: env section я могу переопределить VAR ENV с настоящими производственными секретами. Я могу кодировать значения или использовать это приспособление:
- name: "SMTP_USERNAME"
valueFrom:
secretKeyRef:
name: my-env
key: SMTP_USERNAME
Это ссылка на «секретное» хранилище, которое я назвал my-env. И вот как вы создаете свой собственный:
kubectl create secret generic my-env --from-literal=REDIS_URL=redis://foo.com:18821 --from-literal=SMTP_USERNAME=foobar
Прочитайте документацию, поскольку вы можете загружать текстовые файлы, а не декларировать все из командной строки.
Как я уже говорил, я бы предпочел использовать управляемую услугу для базы данных. Вы можете загрузить свой образ Docker, но я не рекомендую. То же самое касается других сервисов, подобных базам данных, таких как Redis, Mongo. Если вы используете AWS, то имейте в виду: Google Cloud SQL похож на RDS.
После создания экземпляра PostgreSQL вы не сможете получить к нему доступ непосредственно из веб-приложения. У вас есть шаблон для второго изображения Docker, «CloudSQL Proxy».
Для этого необходимо сначала создать учетную запись службы:
gcloud sql users create proxyuser host --instance=my-db --password=abcd1234
После создания экземпляра PostgreSQL вам будет предложено загрузить учетные данные JSON. Сохраните их где-нибудь. Надеюсь, я не должен напоминать о необходимости надежного пароля. Нужно создать дополнительные секреты:
kubectl create secret generic cloudsql-instance-credentials --from-file=credentials.json=/home/myself/downloads/my-db-12345.json
kubectl create secret generic cloudsql-db-credentials --from-literal=username=proxyuser --from-literal=password=abcd1234
Они упоминаются в этой части развертывания:
- image: gcr.io/cloudsql-docker/gce-proxy:latest
name: cloudsql-proxy
command: ["/cloud_sql_proxy", "--dir=/cloudsql",
"-instances=my-project:us-west1:my-db=tcp:5432",
"-credential_file=/secrets/cloudsql/credentials.json"]
volumeMounts:
- name: cloudsql-instance-credentials
mountPath: /secrets/cloudsql
readOnly: true
- name: ssl-certs
mountPath: /etc/ssl/certs
- name: cloudsql
mountPath: /cloudsql
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
- name: ssl-certs
hostPath:
path: /etc/ssl/certs
- name: cloudsql
emptyDir:
Вы должны добавить имя базы данных (my-db в нашем случае) в -instance в команде.
Кстати, gce-proxy:latest
относится к версии 1.09, когда уже существовала версия 1.11. Новая версия создала головную боль обрывом соединения и продлением времени ожидания. Поэтому я вернулся к более поздней версии – 1.09, и все наладилось. Так что не все новое хорошо. В инфраструктуре лучше придерживаться стабильности.
You may also want the option to load a separated CloudSQL instance instead of having it in each pod, so the pods could connect to just one proxy. You may want to read this thread on the subject.
Вы можете загрузить отдельный экземпляр CloudSQL вместо того, чтобы иметь его в каждом поде, чтобы поды могли подключаться только к одному прокси. Рекомендую прочитать эту статью.
Кажется, ничто не подвергается воздействию. Поэтому нужно разоблачить поды через Node Port Service. Давайте создадим файл deploy/web-svc.yamlfile
.
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
sessionAffinity: None
ports:
- port: 80
targetPort: 4001
protocol: TCP
type: NodePort
selector:
app: web
Вот почему я подчеркнул важность spec:template:metadata:labels
. Мы будем использовать его в spec:selector
, чтобы выбрать правильные контейнеры.
Теперь мы можем развернуть эти 2 пода следующим образом:
kubectl create -f deploy/web.yml
kubectl create -f deploy/web-svc.yml
Вы видите, что поды созданы с помощью kubectl get pods --watch
.
Балансировка нагрузки
Во многих учебниках поды предоставляются через другой сервис, который называется Load Balancer. Я не уверен, насколько хорошо он ведет себя под давлением, есть ли у него завершение SSL и т. д. Я решил включиться на полную мощность с Ingress Load Balancer при помощи контроллера NGINX.
Прежде всего я решил создать для него отдельный пул узлов, например:
gcloud container node-pools create web-load-balancer --cluster=my-web-production --node-labels=role=load-balancer --machine-type=g1-small --num-nodes 1 --max-nodes 3 --min-nodes=1 --enable-autoscaling
Как только создан пример large-pool
, позаботьтесь о добавлении --node-labels
, чтобы установить контроллер вместо default-pool
. Нужно знать имя экземпляра узла, и мы можем сделать это следующим образом:
$ gcloud compute instances list
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
gke-my-web-production-default-pool-123-123 us-west1-a n1-standard-4 10.128.0.1 123.123.123.12 RUNNING
gke-my-web-production-large-pool-123-123 us-west1-a n1-highcpu-8 10.128.0.2 50.50.50.50 RUNNING
gke-my-web-production-web-load-balancer-123-123 us-west1-a g1-small 10.128.0.3 70.70.70.70 RUNNING
Сохраним его:
export LB_INSTANCE_NAME=gke-my-web-production-web-load-balancer-123-123
Вы можете вручную зарезервировать внешний IP-адрес и присвоить ему имя:
gcloud compute addresses create ip-web-production --ip-version=IPV4 --global
Предположим, что он сгенерировал зарезервированный IP-адрес "111.111.111.111". Сохраним его:
export LB_ADDRESS_IP=$(gcloud compute addresses list | grep "ip-web-production" | awk '{print $3}')
Cвяжем адрес с экземпляром узла балансировки нагрузки:
export LB_INSTANCE_NAT=$(gcloud compute instances describe $LB_INSTANCE_NAME | grep -A3 networkInterfaces: | tail -n1 | awk -F': ' '{print $2}')
gcloud compute instances delete-access-config $LB_INSTANCE_NAME --access-config-name "$LB_INSTANCE_NAT"
gcloud compute instances add-access-config $LB_INSTANCE_NAME --access-config-name "$LB_INSTANCE_NAT" --address $LB_ADDRESS_IP
Добавим остальную конфигурацию Ingress Deployment. Это может занять много времени, но в основном мы будем идти по шаблону. Начнем с определения другого веб-приложения, называющегося default-http-backend
. Оно будет использоваться для ответа на запросы HTTP, если веб-контейнеры по какой-либо причине недоступны. Назовем его deploy / default-web.yml
:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
spec:
replicas: 1
template:
metadata:
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.0
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
Не надо ничего менять. Теперь вы знакомы с шаблоном развертывания. Нужно разоблачить шаблон через NodePort, поэтому давайте добавим deploy/default-web-svc.yml
:
kind: Service
apiVersion: v1
metadata:
name: default-http-backend
spec:
selector:
app: default-http-backend
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: NodePort
Снова ничего не меняем. Следующие 3 файла являются важными частями. Во-первых, мы создадим балансировщик нагрузки NGINX, назовем его deploy / nginx.yml
:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: nginx-ingress-lb
spec:
# hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration
# however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host
# that said, since hostPort is broken on CNI (https://github.com/kubernetes/kubernetes/issues/31307) we have to use hostNetwork where CNI is used
hostNetwork: true
terminationGracePeriodSeconds: 60
nodeSelector:
role: load-balancer
containers:
- args:
- /nginx-ingress-controller
- "--default-backend-service=$(POD_NAMESPACE)/default-http-backend"
- "--default-ssl-certificate=$(POD_NAMESPACE)/cloudflare-secret"
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: "gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.5"
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 5
name: nginx-ingress-controller
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
volumeMounts:
- mountPath: /etc/nginx-ssl/dhparam
name: tls-dhparam-vol
volumes:
- name: tls-dhparam-vol
secret:
secretName: tls-dhparam
Обратите внимание, что nodeSelector
создает метку узла, которую мы добавили, когда создали новый пул узлов.
Возможно, вы захотите поработать с метками, количеством реплик. Важно заметить, что здесь монтируется том, который я назвал tls-dhparam-vol
. Это Diffie Hellman Ephemeral Parameters
. Мы генерируем его:
sudo openssl dhparam -out ~/documents/dhparam.pem 2048
kubectl create secret generic tls-dhparam --from-file=/home/myself/documents/dhparam.pem
kubectl create secret generic tls-dhparam --from-file=/home/myself/documents/dhparam.pem
Обратите внимание, что я использую версию 0.9.0-beta_5 для изображения контроллера. Он работает хорошо, никаких проблем до сих пор. Но следите за комментариями к выпуску и для новых версий и проводите собственное тестирование.
Опять же, давайте разоблачим этот контроллер Ingress через службу балансировки нагрузки. Назовем его deploy / nginx-svc.yml
:
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
spec:
type: LoadBalancer
loadBalancerIP: 111.111.111.111
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
selector:
k8s-app: nginx-ingress-lb
Помните статический внешний IP, который мы зарезервировали выше и сохранили в LB_INGRESS_IP ENV var
? Нужно включить его в раздел spec: loadBalancerIP
.Это также IP-адрес, который вы добавите в качестве «записи» в своей службе DNS (скажем, сопоставление вашего www.my-app.com.br c CloudFlare).
Теперь мы можем создать собственную конфигурацию Ingress, давайте создадим deploy / ingress.yml
следующим образом:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.org/ssl-services: "web-svc"
kubernetes.io/ingress.global-static-ip-name: ip-web-production
ingress.kubernetes.io/ssl-redirect: "true"
ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- www.my-app.com.br
secretName: cloudflare-secret
rules:
- host: www.my-app.com.br
http:
paths:
- path: /
backend:
serviceName: web-svc
servicePort: 80
Таким образом мы связали службу NodePort, созданную для веб-модулей, с контроллером входа nginx и добавили завершение SSL через spec: tls: secretName
. Как это создать? Во-первых, вы должны приобрести сертификат SSL, используя CloudFlare в качестве примера.
При покупке провайдер должен предоставить вам секретные файлы для загрузки (сохраните их в безопасности! общедоступная папка с Dropbox небезопасна!). Затем вы должны добавить его в инфраструктуру следующим образом:
kubectl create secret tls cloudflare-secret --key ~/downloads/private.pem --cert ~/downloads/fullchain.pem
Теперь, когда мы отредактировали множество файлов, можем развернуть весь пакет балансировки нагрузки:
kubectl create -f deploy/default-web.yml
kubectl create -f deploy/default-web-svc.yml
kubectl create -f deploy/nginx.yml
kubectl create -f deploy/nginx-svc.yml
kubectl create -f deploy/ingress.yml
Эта конфигурация NGINX Ingress основана на сообщении блога Zihao Zhang. Также есть примеры в репозитории инкубаторов кубернетов. Можете проверить это.
Если вы все сделали правильно, https://www.my-app-com.br должно загрузить ваше веб-приложение. Можно проверить время на первый байт (TTFB) через CloudFlare следующим образом:
curl -vso /dev/null -w "Connect: %{time_connect} \n TTFB: %{time_starttransfer} \n Total time: %{time_total} \n" https://www.my-app.com.br
Если у вас медленный TTFB:
curl --resolve www.my-app.com.br:443:111.111.111.111 https://www.my-app.com.br -svo /dev/null -k -w "Connect: %{time_connect} \n TTFB: %{time_starttransfer} \n Total time: %{time_total} \n"
TTFB должен быть в районе 1 секунды или меньше. Иначе это означает, что в приложении есть ошибка. Необходимо проверить типы экземпляров машинных узлов, количество рабочих, загруженных на один модуль, версию прокси-сервера CloudSQL, версию контроллера NGINX и т. д. Это процедура проб и ошибок. Подпишитесь на Loader или Web Page Test для понимания.
Роллинг-обновление
Теперь, когда все работает, как выполнить обновление Rolling Update, о котором я упоминал в начале? Сначала выполните git push
на репозиторий реестра Container и дождитесь создания образа Docker.
Помните, я сделал так, чтобы триггер помещал изображение со случайным номером версии? Давайте использовать (его можно увидеть в списке Build History в Контейнерном реестре консоли Google Cloud):
kubectl set image deployment web my-app=gcr.io/my-project/my-app:1238471234g123f534f543541gf5 --record
Необходимо использовать то же имя и образ, который объявлен в deploy/web.yml
. Начнется роллинг-обновление, добавление нового модуля, а затем завершение блока, пока все они не будут обновлены без простоя для пользователей.
Роллинг-обновления должны выполняться тщательно. Например, если для развертывания требуется миграция базы данных, вы должны добавить окно обслуживания (нужно делать это среди ночи, когда объем трафика небольшой ). Таким образом, вы можете запустить команду миграции следующим образом:
kubectl get pods # to get a pod name
kubectl exec -it my-web-12324-121312 /app/bin/rails db:migrate
# you can also bash to a pod like this, but remember that this is an ephemeral container, so file you edit and write there disappear on the next restart:
kubectl exec -it my-web-12324-121312 bash
Чтобы перераспределить все, не прибегая к роллинг-обновлению, нужно сделать следующее:
kubectl delete -f deploy/web.yml && kubectl apply -f deploy/web.yml
Бонус: Автоснимки
В моем списке «Я хочу» был пункт – иметь постоянное монтируемое хранилище с автоматическими резервными копиями/моментальными снимками. Google Cloud предоставляет половину этого. Для подключения к модулям вы можете создавать постоянные диски, но без функции автоматического резервного копирования. Однако у хранилища есть API для создания снимка вручную.
Создадим новый SSD-диск и отформатируем его:
gcloud compute disks create --size 500GB my-data --type pd-ssd
gcloud compute instances list
Последняя команда позволяет скопировать имя экземпляра узла. Предположим, что это gke-my-web-app-default-pool-123-123
. Мы прикрепим диск my-data
к нему:
gcloud compute instances attach-disk gke-my-web-app-default-pool-123-123 --disk my-data --device-name my-data
gcloud compute ssh gke-my-web-app-default-pool-123-123
Последняя команда ssh в экземпляре. Мы можем перечислить прикрепленные диски с помощью sudo lsblk
, и вы увидите диск 500 ГБ, возможно, как / dev / sdb
. Убедитесь, что это нужный диск, мы будем его форматировать!
sudo mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb
Теперь мы можем выйти из сеанса SSH и отсоединить диск:
gcloud compute instances detach-disk gke-my-web-app-default-pool-123-123 --disk my-data
Вы можете установить этот диск в свои модули, добавив к развертыванию yaml следующее:
spec:
containers:
- image: ...
name: my-app
volumeMounts:
- name: my-data
mountPath: /data
# readOnly: true
# ...
volumes:
- name: my-data
gcePersistentDisk:
pdName: my-data
fsType: ext4
Теперь создадим файл развертывания CronJob как deploy / auto-snapshot.yml
:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: auto-snapshot
spec:
schedule: "0 4 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: auto-snapshot
image: grugnog/google-cloud-auto-snapshot
command: ["/opt/entrypoint.sh"]
env:
- name: "GOOGLE_CLOUD_PROJECT"
value: "my-project"
- name: "GOOGLE_APPLICATION_CREDENTIALS"
value: "/credential/credential.json"
volumeMounts:
- mountPath: /credential
name: editor-credential
volumes:
- name: editor-credential
secret:
secretName: editor-credential
Как мы уже делали ранее, нужно будет создать еще одну учетную запись службы с разрешениями редактора в разделе IAM & admin консоли Google Cloud, затем загрузить учетные данные JSON и, наконец, загрузить ее следующим образом:
kubectl create secret generic editor-credential --from-file=credential.json=/home/myself/download/my-project-1212121.json
Также обратите внимание: вы можете поменять задание cron. В примере «0 4 *» означает, что он будет запускать моментальный снимок каждый день в 4 часа утра.
Более подробно ознакомьтесь с исходным репозиторием этого решения.
Как я уже сказал, это не полная процедура, просто основные моменты некоторых важных частей. Если вы новичок в Kubernetes, прочитайте о Deployment, Service, Ingress, но у вас есть ReplicaSet, DaemonSet и многое другое.
Потребуется много времени, чтобы добавить настройку multi-region High Availability, поэтому оставим ее.
Оригинал: My Notes about a Production-grade Ruby on Rails Deployment on Google Cloud Kubernetes Engine