Постоянная интеграция / развертывание / доставка в Google App Engine, слишком рискованно?

Недавно мы настроили непрерывную интеграцию / развертывание / доставку веб-приложения nodejs в Google App Engine. Сервер CI (GitLabCI) выполняет установку зависимостей, сборку, тестирование и развертывание в интеграцию / продакшн в зависимости от отрасли (разработка / мастер).

На сегодняшний день единственные ошибки, с которыми мы столкнулись, были на этапе зависимостей, и поэтому нас это не особо волновало. Но вчера (21/10/16) произошел широкомасштабный сбой DNS, и в середине этапа развертывания произошел сбой конвейера, что привело к поломке продукта. Просто повторный запуск конвейера сделал работу, но проблему можно воспроизвести в любое время.

Мои вопросы:

  • Как мы можем решить такие проблемы с сетью в процессе непрерывного развертывания?
  • Является ли непрерывное развертывание в Google App Engine действительно хорошей идеей?
  • Если да, то каков метод развертывания App Engine? Я не нахожу никаких соответствующих документов об этом...

На данный момент у нас есть только две версии "dev" и "prod", которые обновляются после фиксации, но в случайное время я мог наблюдать странное поведение.

Любые отзывы / предложения / отзывы очень приветствуются!

Пример трассировки стека, касающийся проблем с сетью, о которых я говорю:

DEBUG: Error sending result: 'MetadataServerException(HTTPError(),)'. Reason: 'PicklingError("Can't pickle <type 'cStringIO.StringO'>: attribute lookup cStringIO.StringO failed",)'
Traceback (most recent call last):
  File "/google-cloud-sdk/lib/googlecloudsdk/calliope/cli.py", line 733, in Execute
    resources = args.calliope_command.Run(cli=self, args=args)
  File "/google-cloud-sdk/lib/googlecloudsdk/calliope/backend.py", line 1630, in Run
    resources = command_instance.Run(args)
  File "/google-cloud-sdk/lib/surface/app/deploy.py", line 53, in Run
    return deploy_util.RunDeploy(self, args)
  File "/google-cloud-sdk/lib/googlecloudsdk/command_lib/app/deploy_util.py", line 387, in RunDeploy
    all_services)
  File "/google-cloud-sdk/lib/googlecloudsdk/command_lib/app/deploy_util.py", line 247, in Deploy
    manifest = _UploadFiles(service, code_bucket_ref)
  File "/google-cloud-sdk/lib/googlecloudsdk/command_lib/app/deploy_util.py", line 115, in _UploadFiles
    service, code_bucket_ref)
  File "/google-cloud-sdk/lib/googlecloudsdk/api_lib/app/deploy_app_command_util.py", line 277, in CopyFilesToCodeBucketNoGsUtil
    _UploadFiles(files_to_upload, bucket_ref)
  File "/google-cloud-sdk/lib/googlecloudsdk/api_lib/app/deploy_app_command_util.py", line 219, in _UploadFiles
    results = pool.map(_UploadFile, tasks)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 251, in map
    return self.map_async(func, iterable, chunksize).get()
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 558, in get
    raise self._value
MaybeEncodingError: Error sending result: 'MetadataServerException(HTTPError(),)'. Reason: 'PicklingError("Can't pickle <type 'cStringIO.StringO'>: attribute lookup cStringIO.StringO failed",)'
DEBUG: Exception captured in Error
Traceback (most recent call last):
  File "/google-cloud-sdk/lib/googlecloudsdk/core/metrics.py", line 411, in Wrapper
    return func(*args, **kwds)
TypeError: Error() takes exactly 3 arguments (1 given)
ERROR: gcloud crashed (MaybeEncodingError): Error sending result: 'MetadataServerException(HTTPError(),)'. Reason: 'PicklingError("Can't pickle <type 'cStringIO.StringO'>: attribute lookup cStringIO.StringO failed",)'
Traceback (most recent call last):
  File "/google-cloud-sdk/lib/gcloud.py", line 65, in <module>
    main()
  File "/google-cloud-sdk/lib/gcloud.py", line 61, in main
    sys.exit(googlecloudsdk.gcloud_main.main())
  File "/google-cloud-sdk/lib/googlecloudsdk/gcloud_main.py", line 145, in main
    crash_handling.HandleGcloudCrash(err)
  File "/google-cloud-sdk/lib/googlecloudsdk/command_lib/crash_handling.py", line 107, in HandleGcloudCrash
    _ReportError(err)
  File "/google-cloud-sdk/lib/googlecloudsdk/command_lib/crash_handling.py", line 86, in _ReportError
    util.ErrorReporting().ReportEvent(error_message=stacktrace,
  File "/google-cloud-sdk/lib/googlecloudsdk/api_lib/error_reporting/util.py", line 28, in __init__
    self._API_NAME, self._API_VERSION)
  File "/google-cloud-sdk/lib/googlecloudsdk/core/apis.py", line 254, in GetClientInstance
    http_client = http.Http()
  File "/google-cloud-sdk/lib/googlecloudsdk/core/credentials/http.py", line 60, in Http
    creds = store.Load()
  File "/google-cloud-sdk/lib/googlecloudsdk/core/credentials/store.py", line 282, in Load
    if account in c_gce.Metadata().Accounts():
  File "/google-cloud-sdk/lib/googlecloudsdk/core/credentials/gce.py", line 122, in Accounts
    gce_read.GOOGLE_GCE_METADATA_ACCOUNTS_URI + '/')
  File "/google-cloud-sdk/lib/googlecloudsdk/core/util/retry.py", line 160, in TryFunc
    return func(*args, **kwargs), None
  File "/google-cloud-sdk/lib/googlecloudsdk/core/credentials/gce.py", line 45, in _ReadNoProxyWithCleanFailures
    raise MetadataServerException(e)
googlecloudsdk.core.credentials.gce.MetadataServerException: HTTP Error 503: Service Unavailable
DEBUG: Uploading [/builds/apps/webapp/lib/jinja2/defaults.pyc] to [151c77b4e5bdd2c38b6a2bf914fffa3a6ffa71a6]
INFO: Uploading [/builds/apps/webapp/lib/jinja2/defaults.pyc] to [151c77b4e5bdd2c38b6a2bf914fffa3a6ffa71a6]
INFO: Refreshing access_token

1 ответ

Решение

Хорошо плохо? Субъективно - значит, не по теме для SO. Предположим, вопрос заключается в том, как сделать надежное непрерывное развертывание:)

Проблема в том, что вы используете версии приложений в качестве среды CI, что означает, что вы не можете избежать поломок из-за плохой конкретной версии. Вы можете только надеяться, что восстановитесь как можно быстрее, повторно развернув версию (когда сбой закончится) - это можно автоматизировать.

Ваш производственный сайт не должен работать прямо с версии, перезаписанной CI production В противном случае вы рискуете отключить сайт при неудачном развертывании. Вместо этого вы можете использовать новую / уникальную версию для каждого выполнения CI production конвейер и только после этого успешно завершается, вы, наконец, переключаете трафик сайта на его версию, используя поток, описанный ниже (который также может использоваться внутри конвейеров CI, если используются другие приложения вместо версий приложений в качестве сред CI)

От развертывания вашей программы:

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

Чтобы переопределить это поведение, вы можете указать идентификатор версии с флагом версии:

gcloud app deploy --version myID

Вы также можете указать не отправлять весь трафик в новую версию немедленно с флагом --no-promoter:

gcloud app deploy --no-promote

Поэтому убедитесь, что вы никогда не развернете версию и сделаете ее версией назначения трафика по умолчанию на одном и том же шаге (возможно, не атомарной, если она ведется со стороны клиента). Специально для производственного приложения. Вместо:

  • развернуть новую версию (gcloud app deploy --no-promote --version ...)
  • начать новую версию (gcloud app versions ...) и проверьте, что это работает
  • если он работает нормально переключи реальный трафик на него (gcloud app services set-traffic ...)

Таким образом, единственной критической операцией является переключение трафика, которое (надеюсь) является атомарной операцией, которая либо успешна, либо полностью отменена на стороне GAE (если нет, это ошибка GAE). Если этот шаг не пройден, приложение должно продолжать работать со старой версией.

Конечно, это предполагает, что проблемы с сетью возникают только между вами и GAE, если они также влияют на внутренние операции GAE, все ставки сняты (но те, на которые я надеюсь, должны быть исправлены довольно своевременно).