Автор оригинала: Leonardo Giordani.
Это было отвратительно. Они хотели, чтобы я препарировал лягушку. (Beetlejuice, 1988)
Первоначально опубликовано на The Digital Cat
Недавно работая с молодыми веб-разработчиками, которые впервые познакомились с надлежащей производственной инфраструктурой, я получил много вопросов о различных компонентах, которые можно найти в архитектуре “веб-сервиса”. Эти вопросы ясно выражали замешательство (а иногда и разочарование) разработчиков, которые понимают, как создавать конечные точки на языке высокого уровня, таком как Node.js или Python, но никогда не были ознакомлены со сложностью того, что происходит между браузером пользователя и его фреймворком выбора. В большинстве случаев они вообще не знают, почему существует сама структура.
Задача ясна, если мы просто перечислим (в случайном порядке) некоторые слова, которые мы используем при обсуждении (Python) Веб-разработка: HTTP, cookies, веб-сервер, Websockets, FTP, многопоточный, обратный прокси, Django, nginx, статические файлы, POST, сертификаты, фреймворк, Flask, SSL, GET, WSGI, управление сеансами, TLS, балансировка нагрузки, Apache.
В этом посте я хочу рассмотреть все слова, упомянутые выше (и еще пару), пытаясь создать готовый к производству веб-сервис с нуля. Я надеюсь, что это поможет молодым разработчикам получить полную картину и понять смысл этих “неясных” имен, которые старшие разработчики, такие как я, склонны бросать в повседневных разговорах (иногда, возможно, вне очереди).
Поскольку основное внимание в этом посте уделяется глобальной архитектуре и причинам наличия конкретных компонентов, примером сервиса, который я буду использовать, будет базовая HTML-веб-страница. Эталонным языком будет Python, но общее обсуждение применимо к любому языку или фреймворку.
Мой подход будет состоять в том, чтобы сначала сформулировать обоснование, а затем реализовать возможное решение. После этого я укажу на недостающие части или нерешенные проблемы и перейду к следующему слою. В конце процесса читатель должен иметь четкое представление о том, почему каждый компонент был добавлен в систему.
Идеальная архитектура
Очень важная основополагающая концепция системной архитектуры заключается в том, что не существует идеального решения, разработанного каким-то более мудрым гением, которое нам просто нужно применить. К сожалению, часто люди ошибочно принимают шаблоны проектирования за такое “волшебное решение”. Однако в оригинальной книге “Шаблоны проектирования” говорится, что
Ваш дизайн должен быть специфичным для конкретной проблемы, но и достаточно общим для решения будущих проблем и требований. Вы также хотите избежать редизайна или, по крайней мере, свести его к минимуму.
А потом
Шаблоны проектирования облегчают повторное использование успешных проектов и архитектур. […] Шаблоны проектирования помогают вам выбирать альтернативы проектирования, которые делают систему многоразовой, и избегать альтернатив, которые ставят под угрозу возможность повторного использования.
Авторы книги обсуждают объектно-ориентированное программирование, но эти предложения могут быть применены к любой архитектуре. Как вы можете видеть, у нас есть “проблема под рукой” и “проектные альтернативы”, а это значит, что самое важное, что нужно понять, – это требования, как настоящие, так и будущие. Только имея в виду четкие требования, можно эффективно разработать решение, возможно, используя большое количество шаблонов, которые уже были разработаны другими дизайнерами.
И последнее замечание. Веб-стек-это сложное устройство, состоящее из нескольких компонентов и программных пакетов, разработанных разными программистами с разными целями. Поэтому вполне понятно, что такие компоненты имеют некоторую степень суперпозиции. В то время как линия разделения между теоретическими слоями обычно очень четкая, на практике разделение часто бывает размытым. Ожидайте этого много, и вы больше никогда не потеряетесь в веб-стеке.
Некоторые определения
Давайте кратко рассмотрим некоторые из наиболее важных концепций, связанных с веб-стеком, – протоколы.
TCP/IP
TCP/IP-это сетевой протокол, то есть набор установленных правил, которым должны следовать два компьютера, чтобы соединиться по физической сети для обмена сообщениями. TCP/IP состоит из двух различных протоколов, охватывающих два различных уровня стека OSI, а именно Транспортный (TCP) и сетевой (IP). TCP/IP может быть реализован поверх любого физического интерфейса (канала передачи данных и физического уровня OSI), такого как Ethernet и Wireless. Субъекты в сети TCP/IP идентифицируются с помощью сокета , который представляет собой кортеж, состоящий из IP-адреса и номера порта.
Однако, что касается разработки веб-сервиса, мы должны знать, что TCP/IP является надежным протоколом, что в телекоммуникациях означает, что сам протокол заботится или повторяет передачу при потере пакетов. Другими словами, хотя скорость связи не предоставляется, мы можем быть уверены, что, как только сообщение будет отправлено, оно достигнет места назначения без ошибок.
HTTP
TCP/IP может гарантировать, что необработанные байты, отправленные одним компьютером, достигнут места назначения, но это оставляет совершенно нетронутой проблему передачи значимой информации. В частности, в 1989 году проблема, которую хотел решить Тим Бернерс-Ли, заключалась в том, как однозначно назвать гипертекстовые ресурсы в сети и как получить к ним доступ.
HTTP-это протокол, который был разработан для решения такой проблемы и с тех пор значительно эволюционировал. С помощью других протоколов, таких как WebSocket, HTTP вторгся в области коммуникации, для которых он изначально считался непригодным, такие как общение в реальном времени или игры.
По своей сути HTTP-это протокол, который определяет формат текстового запроса и возможные текстовые ответы. Первоначальная версия 0.9, опубликованная в 1991 году, определяла концепцию URL и разрешала только операцию GET, которая запрашивала определенный ресурс. HTTP 1.0 и 1.1 добавили важные функции, такие как заголовки, дополнительные методы и важные оптимизации производительности. На момент написания статьи принятие HTTP/2 составляет около 45% веб-сайтов в мире, а HTTP/3 все еще находится в стадии разработки.
Самая важная особенность HTTP, которую мы должны иметь в виду как разработчики, заключается в том, что это протокол без состояния . Это означает, что протокол не требует от сервера отслеживания состояния связи между запросами, в основном оставляя управление сеансами разработчику самой службы.
Управление сеансами в настоящее время имеет решающее значение, потому что вы обычно хотите иметь уровень аутентификации перед службой, где пользователь предоставляет учетные данные и получает доступ к некоторым частным данным. Однако он полезен в других контекстах, таких как визуальные предпочтения или выбор, сделанный пользователем и повторно используемый в последующих обращениях к тому же веб-сайту. Типичные решения проблемы управления сеансами HTTP включают использование файлов cookie или токенов сеанса.
HTTPS
Безопасность стала очень важным словом в последние годы, и не без причины. Количество конфиденциальных данных, которыми мы обмениваемся в Интернете или храним на цифровых устройствах, растет в геометрической прогрессии, но, к сожалению, растет и количество злоумышленников и уровень ущерба, который они могут нанести своими действиями. Протокол HTTP по своей сути
HTTP изначально небезопасен, поскольку представляет собой обычную текстовую связь между двумя серверами, которая обычно происходит в совершенно ненадежной сети, такой как Интернет. Хотя безопасность не была проблемой, когда протокол был первоначально задуман, сегодня это проблема первостепенной важности, поскольку мы обмениваемся частной информацией, часто жизненно важной для безопасности людей или для бизнеса. Мы должны быть уверены, что отправляем информацию на правильный сервер и что данные, которые мы отправляем, не могут быть перехвачены.
HTTPS решает как проблему фальсификации, так и подслушивания, шифруя HTTP с помощью протокола Transport Layer Security (TLS), который также обеспечивает использование цифровых сертификатов, выданных доверенным центром. На момент написания статьи примерно 80% веб-сайтов, загружаемых Firefox, по умолчанию используют HTTPS. Когда сервер получает HTTPS-соединение и преобразует его в HTTP-соединение, обычно говорят, что он завершает TLS (или SSL, старое название TLS).
WebSocket
Одним из больших недостатков HTTP является то, что связь всегда инициируется клиентом и что сервер может отправлять данные только тогда, когда это явно запрашивается. Опрос может быть реализован для обеспечения начального решения, но он не может гарантировать работу правильной полнодуплексной связи, когда канал остается открытым между сервером и клиентом, и оба могут отправлять данные без запроса. Такой канал обеспечивается протоколом WebSocket.
WebSocket-это убийственная технология для таких приложений, как онлайн-игры, каналы реального времени, такие как финансовые билеты или спортивные новости, или мультимедийные коммуникации, такие как конференции или удаленное образование.
Важно понимать, что WebSocket не является HTTP и может существовать и без него. Также верно, что этот новый протокол был разработан для использования поверх существующего HTTP-соединения, поэтому связь WebSocket часто встречается в частях веб-страницы, которая изначально была получена с помощью HTTP в первую очередь.
Реализация службы по протоколу HTTP
Давайте, наконец, начнем обсуждать биты и байты. Отправной точкой нашего путешествия является служба по протоколу HTTP, что означает обмен HTTP-запросами и ответами. В качестве примера рассмотрим GET-запрос, самый простой из HTTP-методов.
GET / HTTP/1.1 Host: localhost User-Agent: curl/7.65.3 Accept: */*
Как видите, клиент отправляет на сервер чистое текстовое сообщение в формате, заданном протоколом HTTP. Первая строка содержит имя метода ( GET
), URL-адрес ( /
) и используемый нами протокол, включая его версию ( HTTP/1.1
). Остальные строки называются headers и содержат метаданные, которые могут помочь серверу управлять запросом. Полное значение заголовка Host
в данном случае равно localhost:80
, но поскольку стандартный порт для HTTP-сервисов равен 80, нам не нужно его указывать.
Если сервер localhost
обслуживает HTTP (то есть запускает какое-то программное обеспечение, которое понимает HTTP) на порту 80, то ответ, который мы можем получить, будет чем-то похожим на
HTTP/1.0 200 OK Date: Mon, 10 Feb 2020 08:41:33 GMT Content-type: text/html Content-Length: 26889 Last-Modified: Mon, 10 Feb 2020 08:41:27 GMT ...
Как и в случае с запросом, ответ представляет собой текстовое сообщение, отформатированное в соответствии со стандартом. В первой строке упоминается протокол и статус запроса ( 200
в данном случае это означает успех), в то время как следующие строки содержат метаданные в различных заголовках. Наконец, после пустой строки сообщение содержит ресурс, который запросил клиент, исходный код базового URL-адреса веб-сайта в данном случае. Поскольку эта HTML-страница, вероятно, содержит ссылки на другие ресурсы, такие как CSS, JS, изображения и т. Д., браузер отправит несколько других запросов, чтобы собрать все данные, необходимые для правильного отображения страницы пользователю.
Итак, первая проблема, с которой мы сталкиваемся, – это реализация сервера, который понимает этот протокол и отправляет правильный ответ при получении HTTP-запроса. Мы должны попытаться загрузить запрошенный ресурс и вернуть либо успех (HTTP 200), если мы можем его найти, либо неудачу (HTTP 404), если мы не можем.
1 Сокеты и парсеры
TCP/IP-это сетевой протокол, который работает с сокетами . Сокет-это кортеж IP-адреса (уникального в сети) и порта (уникального для конкретного IP-адреса), который компьютер использует для связи с другими. Сокет-это файлоподобный объект в операционной системе, который может быть таким образом открыт и закрыт , и что мы можем читать из или писать в. Программирование сокетов-это довольно низкоуровневый подход к сети, но вы должны знать, что каждое программное обеспечение на вашем компьютере, обеспечивающее доступ к сети, в конечном счете имеет дело с сокетами (скорее всего, через какую-то библиотеку).
Поскольку мы строим вещи с нуля, давайте реализуем небольшую программу Python, которая открывает сокет-соединение, получает HTTP-запрос и отправляет HTTP – ответ. Поскольку порт 80-это “низкий порт” (число меньше 1024), у нас обычно нет разрешений на открытие сокетов там, поэтому я буду использовать порт 8080. На данный момент это не проблема, так как HTTP может быть подан на любой порт.
1.2 Реализация
Создайте файл server.py
и введите этот код. Да, введите его , не просто копируйте и вставляйте, иначе вы ничего не узнаете.
import socket # Create a socket instance # AF_INET: use IP protocol version 4 # SOCK_STREAM: full-duplex byte stream s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Allow reuse of addresses s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Bind the socket to any address, port 8080, and listen s.bind(('', 8080)) s.listen() # Serve forever while True: # Accept the connection conn, addr = s.accept() # Receive data from this socket using a buffer of 1024 bytes data = conn.recv(1024) # Print out the data print(data.decode('utf-8')) # Close the connection conn.close()
Эта маленькая программа принимает соединение по порту 8080 и печатает полученные данные на терминале. Вы можете протестировать его, выполнив его, а затем запустив curl localhost:8080
в другом терминале. Вы должны увидеть что-то вроде
$ python3 server.py GET / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.65.3 Accept: */*
Сервер продолжает выполнять код в цикле while
, поэтому, если вы хотите завершить его, вы должны сделать это с помощью Ctrl+C. Пока все хорошо, но это еще не HTTP-сервер, так как он не посылает никакого ответа; на самом деле вы должны получить сообщение об ошибке от curl, которое говорит curl: (52) Пустой ответ от сервера
.
Отправить обратно стандартный ответ очень просто, нам просто нужно вызвать conn.sendall
передавая необработанные байты. Минимальный HTTP-ответ содержит протокол и статус, пустую строку и фактическое содержимое, например
HTTP/1.1 200 OK Hi there!
Тогда наш сервер становится
import socket # Create a socket instance # AF_INET: use IP protocol version 4 # SOCK_STREAM: full-duplex byte stream s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Allow reuse of addresses s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Bind the socket to any address, port 8080, and listen s.bind(('', 8080)) s.listen() # Serve forever while True: # Accept the connection conn, addr = s.accept() # Receive data from this socket using a buffer of 1024 bytes data = conn.recv(1024) # Print out the data print(data.decode('utf-8')) conn.sendall(bytes("HTTP/1.1 200 OK\n\nHi there!\n", 'utf-8')) # Close the connection conn.close()
Однако на данный момент мы на самом деле не отвечаем на запрос пользователя. Попробуйте использовать различные командные строки curl, такие как curl localhost:8080/index.html
или curl localhost:8080/main.css
и вы всегда будете получать один и тот же ответ. Мы должны попытаться найти ресурс, который запрашивает пользователь, и отправить его обратно в контенте ответа.
Эта версия HTTP – сервера правильно извлекает ресурс и пытается загрузить его из текущего каталога, возвращая либо успех, либо неудачу
import socket import re # Create a socket instance # AF_INET: use IP protocol version 4 # SOCK_STREAM: full-duplex byte stream s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Allow reuse of addresses s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Bind the socket to any address, port 8080, and listen s.bind(('', 8080)) s.listen() HEAD_200 = "HTTP/1.1 200 OK\n\n" HEAD_404 = "HTTP/1.1 404 Not Found\n\n" # Serve forever while True: # Accept the connection conn, addr = s.accept() # Receive data from this socket using a buffer of 1024 bytes data = conn.recv(1024) request = data.decode('utf-8') # Print out the data print(request) resource = re.match(r'GET /(.*) HTTP', request).group(1) try: with open(resource, 'r') as f: content = HEAD_200 + f.read() print('Resource {} correctly served'.format(resource)) except FileNotFoundError: content = HEAD_404 + "Resource /{} cannot be found\n".format(resource) print('Resource {} cannot be loaded'.format(resource)) print('--------------------') conn.sendall(bytes(content, 'utf-8')) # Close the connection conn.close()
Как видите, эта реализация чрезвычайно проста. Если вы создадите простой локальный файл с именем index.html
с этим содержанием
This is my page Some random content
и беги завивайся localhost:8080/index.html
вы увидите содержимое файла. На этом этапе вы даже можете использовать свой браузер, чтобы открыть http://localhost:8080/index.html
и вы увидите название страницы и ее содержание. Веб-браузер-это программное обеспечение, способное отправлять HTTP-запросы и интерпретировать содержимое ответов, если это HTML (и многие другие типы файлов, такие как изображения или видео), поэтому он может отображать содержимое сообщения. Браузер также отвечает за извлечение недостающих ресурсов, необходимых для рендеринга, поэтому, когда вы предоставляете ссылки на таблицы стилей или JS-скрипты с тегами или
Выход server.py
когда я получаю доступ http://localhost:8080/index.html
есть
GET /index.html HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 Pragma: no-cache Cache-Control: no-cache Resource index.html correctly served -------------------- GET /main.css HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0 Accept: text/css,*/*;q=0.1 Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Referer: http://localhost:8080/index.html Pragma: no-cache Cache-Control: no-cache Resource main.css cannot be loaded -------------------- GET /favicon.ico HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0 Accept: image/webp,*/* Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Resource favicon.ico cannot be loaded --------------------
Как вы можете видеть, браузер отправляет богатые HTTP-запросы с большим количеством заголовков, автоматически запрашивая CSS-файл, упомянутый в HTML-коде, и автоматически пытаясь получить изображение favicon.
1.3 Ресурсы
Эти ресурсы предоставляют более подробную информацию по темам, обсуждаемым в этом разделе
1.4 Проблемы
Это дает определенную дозу удовлетворения, чтобы построить что-то с нуля и обнаружить, что это работает гладко с полноценным программным обеспечением, таким как браузер, который вы используете каждый день. Я также думаю, что очень интересно обнаружить, что такие технологии, как HTTP, которые в основном управляют миром в наши дни, по своей сути очень просты.
Тем не менее, есть много функций HTTP, которые мы не охватили нашим простым программированием сокетов. Для начала, HTTP/1.0 ввел другие методы после GET, такие как POST, которые имеют первостепенное значение для современных веб-сайтов, где пользователи продолжают отправлять информацию на серверы через формы. Чтобы реализовать все 9 HTTP-методов, нам нужно правильно разобрать входящий запрос и добавить соответствующие функции в наш код.
Однако на этом этапе вы можете заметить, что мы имеем дело с низкоуровневыми деталями протокола, которые обычно не являются ядром нашего бизнеса. Когда мы создаем сервис через HTTP, мы считаем, что у нас есть знания, чтобы правильно реализовать некоторый код, который может упростить определенный процесс, будь то поиск других веб-сайтов, покупка книг или обмен фотографиями с друзьями. Мы не хотим тратить свое время на понимание тонкостей сокетов TCP/IP и написание парсеров для протоколов запроса-ответа. Приятно видеть, как работают эти технологии, но ежедневно нам нужно сосредоточиться на чем-то более высоком.
Ситуация с нашим небольшим HTTP-сервером, возможно, ухудшается из-за того, что HTTP-это протокол без состояния. Протокол не предоставляет никакого способа соединить два последовательных запроса, таким образом отслеживая состояние связи, которая является краеугольным камнем современного Интернета. Каждый раз, когда мы аутентифицируемся на веб-сайте и хотим посетить другие страницы, нам нужно, чтобы сервер помнил, кто мы, а это подразумевает отслеживание состояния соединения.
Короче говоря, чтобы работать как правильный HTTP-сервер, наш код должен на этом этапе реализовать все методы HTTP и управление файлами cookie. Мы также должны поддерживать другие протоколы, такие как Websockets. Все это довольно тривиальные задачи, поэтому нам определенно нужно добавить какой-то компонент ко всей системе, который позволит нам сосредоточиться на бизнес-логике, а не на низкоуровневых деталях прикладных протоколов.
2 Веб-фреймворк
Войдите в веб-фреймворк!
Как я уже много раз обсуждал (см. the book on clean architectures или the relative post ), роль веб-фреймворка заключается в том, чтобы преобразовывать HTTP-запросы в вызовы функций , а возвращаемые функции – в HTTP-ответы. Истинная природа фреймворка-это слой, который соединяет рабочую бизнес-логику с Сетью через HTTP и связанные с ней протоколы. Фреймворк заботится об управлении сеансами для нас и сопоставляет URL-адреса с функциями, позволяя нам сосредоточиться на логике приложения.
В большой схеме службы HTTP это то, что должен делать фреймворк. Все, что фреймворк предоставляет вне этой области, например слои для доступа к базам данных, шаблонным движкам и интерфейсам к другим системам, является дополнением, которое вы, как программист, можете найти полезным, но в принципе не является частью причины, по которой мы добавили фреймворк в систему. Мы добавляем фреймворк, потому что он действует как слой между нашей бизнес-логикой и HTTP.
2.2 Реализация
Спасибо Мигелю Гринбергу и его удивительному мега-учебнику по колбе Я могу настроить Фляжку за считанные секунды. Я не буду бегать по учебнику здесь, так как вы можете следить за ним на сайте Мигеля. Я буду использовать только содержание первой статьи (из 23!) для создания чрезвычайно простого приложения “Hello, world”.
Для запуска следующего примера вам понадобится виртуальная среда, и вам придется pip install flask
. Следуйте инструкциям Мигеля, если вам нужна более подробная информация об этом.
приложение/__init__.py
файл есть
from flask import Flask application = Flask( __name__ ) from app import routes
и то app/routes.py
файл есть
from app import application @application.route('/') @application.route('/index') def index(): return "Hello, world!"
Вы уже можете увидеть здесь силу фреймворка в действии. Мы определили функцию index
и связали ее с двумя различными URL-адресами ( /|и
| index/|) в 3 строках Python. Это оставляет нам время и силы для правильной работы над бизнес-логикой, что в данном случае является революционным “Здравствуй, мир!”. Никто никогда не делал этого раньше.
Наконец, service.py
файл есть
from app import application
Flask поставляется с так называемым веб-сервером разработки (звучат ли сейчас эти слова?), который мы можем запустить на терминале
$ FLASK_APP=service.py flask run * Serving Flask app "service.py" * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Теперь вы можете посетить данный URL-адрес с помощью своего браузера и убедиться, что все работает правильно. Помните, что 127.0.0.1-это специальный IP-адрес, который относится к “этому компьютеру”; имя localhost
обычно создается операционной системой в качестве псевдонима для этого, поэтому они взаимозаменяемы. Как вы можете видеть, стандартный порт для сервера разработки Flask-5000, поэтому вы должны упомянуть его явно, иначе ваш браузер попытается получить доступ к порту 80 (HTTP по умолчанию). При подключении к браузеру вы увидите некоторые сообщения журнала о HTTP запросах
127.0.0.1 - - [14/Feb/2020 14:54:27] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [14/Feb/2020 14:54:28] "GET /favicon.ico HTTP/1.1" 404 -
Теперь вы можете распознать и то, и другое, так как это тот же самый запрос, который мы получили с нашим маленьким сервером в предыдущей части статьи.
2.3 Ресурсы
Эти ресурсы предоставляют более подробную информацию по темам, обсуждаемым в этом разделе
2.4 Проблемы
Видимо, мы решили все наши проблемы, и многие программисты просто останавливаются на этом. Они учатся использовать фреймворк (что является большим достижением!), но, как мы вскоре обнаружим, этого недостаточно для производственной системы. Давайте подробнее рассмотрим выходные данные сервера Flask. Это ясно говорит, среди прочего
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
Главная проблема, с которой мы сталкиваемся, когда имеем дело с любой производственной системой, – это представления. Подумайте о том, что мы делаем с JavaScript, когда минимизируем код: мы сознательно запутываем код, чтобы сделать файл меньше, но это делается с единственной целью-ускорить извлечение файла.
Для HTTP-серверов история не очень отличается. Веб-фреймворк обычно предоставляет веб-сервер разработки, как это делает Flask, который правильно реализует HTTP, но делает это очень неэффективным способом. Во-первых, это фреймворк blocking , который означает, что если наш запрос будет обслуживаться в течение нескольких секунд (например, потому, что конечная точка извлекает данные из очень медленной базы данных), то любой другой запрос должен будет ждать обслуживания в очереди. Это в конечном счете означает, что пользователь увидит спиннер на вкладке браузера и просто покачает головой, думая, что мы не можем создать современный веб-сайт. Другие проблемы производительности могут быть связаны с управлением памятью или дисковыми кэшами, но в целом мы можем с уверенностью сказать, что этот веб-сервер не может справиться с какой-либо производственной нагрузкой (т. Е. Несколько пользователей одновременно обращаются к веб-сайту и ожидают хорошего качества обслуживания).
В этом нет ничего удивительного. В конце концов, мы не хотели иметь дело с TCP/IP-соединениями, чтобы сосредоточиться на нашем бизнесе, поэтому мы делегировали это другим кодерам, которые поддерживают фреймворк. Авторы фреймворка, в свою очередь, хотят сосредоточиться на таких вещах, как промежуточное программное обеспечение, маршруты, правильная обработка HTTP-методов и так далее. Они не хотят тратить время на попытки оптимизировать работу “многопользовательского” интерфейса. Это особенно верно в мире Python (и почему – то менее верно для Node.js например): Python не сильно ориентирован на параллелизм, и как стиль программирования, так и производительность не благоприятствуют быстрым, неблокирующим приложениям. В последнее время это меняется, с асинхронностью и улучшениями в интерпретаторе, но я оставляю это для другого поста.
Итак, теперь, когда у нас есть полноценный HTTP-сервис, нам нужно сделать его настолько быстрым, чтобы пользователи даже не заметили, что он не работает локально на их компьютере.
3 Параллелизм и фасады
Что ж, всякий раз, когда у вас возникают проблемы с производительностью, просто переходите к параллелизму. Теперь у вас много проблем! (см. здесь )
Да, параллелизм решает многие проблемы, и он является источником всего этого, поэтому нам нужно найти способ использовать его самым безопасным и менее сложным способом. В принципе, мы могли бы добавить слой, который запускает фреймворк каким-то параллельным способом, не требуя от нас ничего менять в самом фреймворке.
И всякий раз, когда вам нужно гомогенизировать разные вещи, просто создайте слой косвенности. Это решает любую проблему, кроме одной. (см. здесь )
Поэтому нам нужно создать слой, который запускает наш сервис параллельно, но мы также хотим, чтобы он был отделен от конкретной реализации сервиса, то есть не зависел от фреймворка или библиотеки, которую мы используем.
3.2 Реализация
В этом случае решение заключается в том, чтобы дать спецификацию API, который веб-фреймворки должны предоставить для использования независимыми сторонними компонентами. В мире Python этот набор правил получил название WSGI, интерфейс шлюза веб-сервера, но такие интерфейсы существуют и для других языков, таких как Java или Ruby. Упомянутый здесь “шлюз” – это часть системы вне рамок, которая в данном обсуждении является частью, имеющей дело с производственными представлениями. С помощью WSGI мы определяем способ для фреймворков предоставлять общий интерфейс, оставляя людей, заинтересованных в параллелизме, свободными реализовывать что-то независимо.
Если фреймворк совместим с интерфейсом шлюза, мы можем добавить программное обеспечение, которое имеет дело с параллелизмом и использует фреймворк через уровень совместимости. Таким компонентом является готовый к производству HTTP-сервер, и два распространенных варианта в мире Python-Gunicorn и uWSGI.
Готовый к производству HTTP-сервер означает, что программное обеспечение понимает HTTP так же, как это уже сделал сервер разработки, но в то же время подталкивает производительность для поддержания большей рабочей нагрузки, и, как мы уже говорили, это делается с помощью параллелизма.
Колба совместима с WSGI, поэтому мы можем заставить ее работать с Gunicorn. Чтобы установить его в нашей виртуальной среде, запустите pip install gunicorn
и настройте его, создав имена файлов wsgi.py
со следующим содержанием
from app import application if __name__ == " __main__": application.run()
Для запуска Gunicorn укажите количество параллельных экземпляров и внешний порт
$ gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi [2020-02-12 18:39:07 +0000] [13393] [INFO] Starting gunicorn 20.0.4 [2020-02-12 18:39:07 +0000] [13393] [INFO] Listening at: http://0.0.0.0:8000 (13393) [2020-02-12 18:39:07 +0000] [13393] [INFO] Using worker: sync [2020-02-12 18:39:07 +0000] [13396] [INFO] Booting worker with pid: 13396 [2020-02-12 18:39:07 +0000] [13397] [INFO] Booting worker with pid: 13397 [2020-02-12 18:39:07 +0000] [13398] [INFO] Booting worker with pid: 13398
Как вы можете видеть, у Gunicorn есть концепция workers , которая является общим способом выражения параллелизма. В частности, Gunicorn реализует модель pre-fork worker, что означает, что он (pre)создает отдельный Unix-процесс для каждого рабочего. Вы можете проверить это, запустив ps
$ ps ax | grep gunicorn 14919 pts/1 S+ 0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi 14922 pts/1 S+ 0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi 14923 pts/1 S+ 0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi 14924 pts/1 S+ 0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi
Использование процессов-это всего лишь один из двух способов реализации параллелизма в системе Unix, а другой-использование потоков. Однако преимущества и недостатки каждого решения выходят за рамки этой статьи. Пока просто помните, что вы имеете дело с несколькими рабочими, которые обрабатывают входящие запросы асинхронно, таким образом реализуя неблокирующий сервер, готовый принять несколько подключений.
3.3 Ресурсы
Эти ресурсы предоставляют более подробную информацию по темам, обсуждаемым в этом разделе
3.4 Проблемы
Используя Единорога, мы теперь имеем готовый к производству HTTP-сервер и, по-видимому, реализовали все, что нам нужно. Однако есть еще много соображений и недостающих частей.
Спектакли (опять же)
Достаточно ли 3 рабочих, чтобы выдержать нагрузку нашего нового мобильного приложения-убийцы? Мы ожидаем тысячи посетителей в минуту, так что, возможно, нам стоит добавить еще несколько. Но в то время как мы увеличиваем количество рабочих, мы должны иметь в виду, что машина, которую мы используем, имеет конечное количество мощности процессора и памяти. Итак, опять же, мы должны сосредоточиться на производительности и, в частности, на масштабируемости: как мы можем продолжать добавлять рабочих без необходимости останавливать приложение, заменять машину более мощной и перезапускать службу?
Примите перемены
Это не единственная проблема, с которой мы сталкиваемся в производстве. Важным аспектом технологии является то, что она меняется с течением времени, поскольку новые и (надеюсь) лучшие решения становятся широко распространенными. Мы обычно проектируем системы, разделяя их как можно больше на сообщающиеся слои именно потому, что мы хотим быть свободными, чтобы заменить слой чем-то другим, будь то более простой компонент или более продвинутый, с лучшими характеристиками или, может быть, просто более дешевый. Итак, опять же, мы хотим иметь возможность развивать базовую систему, сохраняя тот же интерфейс, что и в случае веб-фреймворков.
HTTPS
Еще одна недостающая часть системы-HTTPS. Gunicorn и uWSGI не понимают протокола HTTPS, поэтому нам нужно что-то перед ними, что будет иметь дело с частью протокола “S”, оставляя часть “HTTP” внутренним слоям.
Балансировщики нагрузки
В общем случае балансировщик нагрузки-это просто компонент в системе, который распределяет работу между пулом работников. Gunicorn уже распределяет нагрузку между своими рабочими, так что это не новая концепция, но мы обычно хотим сделать это на более высоком уровне, среди машин или среди целых систем. Балансировка нагрузки может быть иерархической и структурированной на многих уровнях. Мы также можем придавать большее значение некоторым компонентам системы, помечая их как готовые принять большую нагрузку (например, потому, что их аппаратное обеспечение лучше). Балансировщики нагрузки чрезвычайно важны в сетевых сервисах, и определение нагрузки может сильно отличаться от системы к системе: вообще говоря, в веб-сервисе количество подключений является стандартной мерой нагрузки, поскольку мы предполагаем, что в среднем все соединения приносят системе одинаковый объем работы.
Обратные прокси
Балансировщики нагрузки являются прямыми прокси-серверами, поскольку они позволяют клиенту связаться с любым сервером в пуле. В то же время обратный прокси позволяет клиенту извлекать данные, полученные несколькими системами через одну и ту же точку входа. Обратные прокси-серверы-это идеальный способ маршрутизации HTTP-запросов к подсистемам, которые могут быть реализованы с помощью различных технологий. Например, вы можете захотеть, чтобы часть системы была реализована с помощью Python, используя Django и Postgres, а другая часть обслуживалась функцией AWS Lambda, написанной на Go и связанной с нереляционной базой данных, такой как DynamoDB. Обычно в HTTP-сервисах этот выбор делается в соответствии с URL-адресом (например, маршрутизация каждого URL-адреса, начинающегося с /api/
).
Логика
Нам также нужен слой, который может реализовать определенное количество логики, чтобы управлять простыми правилами, которые не связаны с сервисом, который мы внедрили. Типичным примером является перенаправление HTTP: что произойдет, если пользователь обращается к службе с префиксом http://| вместо
https:|//? Правильный способ справиться с этим-через код HTTP 301, но вы не хотите, чтобы такой запрос достиг вашего фреймворка, тратя ресурсы на такую простую задачу.
4 Веб – сервер
Общая метка Web server присваивается программному обеспечению, выполняющему задачи, которые мы обсуждали. Два очень распространенных варианта для этой части системы-nginx и Apache, два проекта с открытым исходным кодом, которые в настоящее время лидируют на рынке. При различных технических подходах они оба реализуют все функции, которые мы обсуждали в предыдущем разделе (и многие другие).
4.2 Реализация
Чтобы протестировать nginx без необходимости бороться с ОС и устанавливать слишком много пакетов, мы можем использовать Docker. Docker полезен для моделирования многомашинной среды, но он также может быть вашей технологией выбора для реальной производственной среды (например, AWS ECS работает с контейнерами Docker).
Базовая конфигурация, которую мы запустим, очень проста. Один контейнер будет содержать код Flask и запускать фреймворк с Gunicorn, в то время как другой контейнер будет запускать nginx. Gunicorn будет обслуживать HTTP на внутреннем порту 8000, не выставленном Docker и, следовательно, недоступном из нашего браузера, в то время как nignx будет выставлять порт 80, традиционный HTTP-порт.
В том же каталоге файла wsgi.py
, создайте Dockerfile
FROM python:3.6 ADD app /app ADD wsgi.py / WORKDIR . RUN pip install flask gunicorn EXPOSE 8000
Это начинается с образа Python Docker, добавляет каталог app
и wsgi.py
файл, и устанавливает Gunicorn. Теперь создайте конфигурацию для nginx в файле с именем nginx.conf
в том же каталоге
server { listen 80; server_name localhost; location / { proxy_pass http://application:8000/; } }
Это определяет сервер, который прослушивает порт 80 и который соединяет все URL-адреса, начинающиеся с /|, с сервером под названием
application на порту 8000, который является контейнером под управлением Gunicorn.
Наконец, создайте файл docker-compose.yml
, который будет описывать конфигурацию контейнеров.
version: "3.7" services: application: build: context: . dockerfile: Dockerfile command: gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi expose: - 8000 nginx: image: nginx volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - 8080:80 depends_on: - application
Как вы можете видеть, имя application
, которое мы упомянули в конфигурационном файле nginx, не является волшебной строкой, а является именем, которое мы присвоили контейнеру Gunicorn в конфигурации Docker Compose.
Чтобы создать эту инфраструктуру, нам нужно установить Docker Compose в нашей виртуальной среде через pip install docker-compose
. Я также создал файл с именем .env
с именем проекта
COMPOSE_PROJECT_NAME=service
На этом этапе вы можете запустить Docker Compose с помощью docker-compose up-d
$ docker-compose up -d Creating network "service_default" with the default driver Creating service_application_1 ... done Creating service_nginx_1 ... done
Если все работает правильно, то открытие браузера и посещение localhost
должно показать вам HTML-страницу, которую обслуживает Flask.
Через docker-compose logs
мы можем проверить, что делают службы. Мы можем распознать вывод Gunicorn в журналах службы с именем application
$ docker-compose logs application Attaching to service_application_1 application_1 | [2020-02-14 08:35:42 +0000] [1] [INFO] Starting gunicorn 20.0.4 application_1 | [2020-02-14 08:35:42 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) application_1 | [2020-02-14 08:35:42 +0000] [1] [INFO] Using worker: sync application_1 | [2020-02-14 08:35:42 +0000] [8] [INFO] Booting worker with pid: 8 application_1 | [2020-02-14 08:35:42 +0000] [9] [INFO] Booting worker with pid: 9 application_1 | [2020-02-14 08:35:42 +0000] [10] [INFO] Booting worker with pid: 10
но сейчас нас больше всего интересует сервис под названием nginx
, поэтому давайте следить за журналами в режиме реального времени с помощью docker-compose logs-f nginx
. Обновите страницу localhost
, которую вы посетили с помощью браузера, и контейнер должен вывести что-то вроде
$ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | 192.168.192.1 - - [14/Feb/2020:08:42:20 +0000] "GET / HTTP/1.1" 200 13 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0" "-"
это стандартный формат журнала nginx. Он показывает IP-адрес клиента ( 192.168.192.1
), метку времени подключения, HTTP-запрос и код состояния ответа (200), а также другую информацию о самом клиенте.
Давайте теперь увеличим количество сервисов, чтобы увидеть механизм балансировки нагрузки в действии. Для этого сначала нам нужно изменить формат журнала nginx, чтобы показать IP-адрес машины, которая обслуживала запрос. Измените файл nginx.conf
, добавив параметры log_format
и access_log
log_format upstreamlog '[$time_local] $host to: $upstream_addr: $request $status'; server { listen 80; server_name localhost; location / { proxy_pass http://application:8000; } access_log /var/log/nginx/access.log upstreamlog; }
Переменная $upstream_addr
– это переменная, содержащая IP-адрес сервера, проксируемого nginx. Теперь запустите docker-compose down
, чтобы остановить все контейнеры, а затем docker-compose up-d --scale
, чтобы запустить их снова
$ docker-compose down Stopping service_nginx_1 ... done Stopping service_application_1 ... done Removing service_nginx_1 ... done Removing service_application_1 ... done Removing network service_default $ docker-compose up -d --scale application=3 Creating network "service_default" with the default driver Creating service_application_1 ... done Creating service_application_2 ... done Creating service_application_3 ... done Creating service_nginx_1 ... done
Как вы можете видеть, Docker Compose теперь запускает 3 контейнера для application
service. Если вы откроете поток журналов и посетите страницу в браузере, то теперь увидите немного другой вывод
$ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | [14/Feb/2020:09:00:16 +0000] localhost to: 192.168.240.4:8000: GET / HTTP/1.1 200
где вы можете определить to: 192.168.240.4:8000
, который является IP-адресом одного из контейнеров приложения. Если вы теперь посетите страницу снова несколько раз, вы должны заметить изменение в восходящем адресе, что-то вроде
$ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | [14/Feb/2020:09:00:16 +0000] localhost to: 192.168.240.4:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.3:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.4:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.2:8000: GET / HTTP/1.1 200
Это показывает, что nginx выполняет балансировку нагрузки, но, по правде говоря, это происходит через DNS Docker, а не явным действием, выполняемым веб-сервером. Мы можем проверить это, обратившись к контейнеру nginx и запустив приложение dig
(вам нужно запустить apt update
и apt install dnsutils
для установки dig
)
root@99c2f348140e:/# dig application ; <<>> DiG 9.11.5-P4-5.1-Debian <<>> application ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7221 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;application. IN A ;; ANSWER SECTION: application. 600 IN A 192.168.240.2 application. 600 IN A 192.168.240.4 application. 600 IN A 192.168.240.3 ;; Query time: 1 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Fri Feb 14 09:57:24 UTC 2020 ;; MSG SIZE rcvd: 110
Чтобы увидеть балансировку нагрузки, выполняемую nginx, мы можем явно определить два сервиса и назначить им разные веса. Запустите docker-compose down
и измените конфигурацию nginx на
upstream app { server application1:8000 weight=3; server application2:8000; } log_format upstreamlog '[$time_local] $host to: $upstream_addr: $request $status'; server { listen 80; server_name localhost; location / { proxy_pass http://app; } access_log /var/log/nginx/access.log upstreamlog; }
Здесь мы определили структуру upstream
, которая перечисляет две различные службы, application1
и application2
, придавая первой вес 3. Это означает, что каждые 4 запроса 3 будут перенаправлены в первую службу, а один-во вторую. Теперь nginx не просто полагается на DNS, но и сознательно выбирает между двумя различными сервисами.
Давайте определим соответствующие службы в конфигурационном файле Docker Compose
version: "3" services: application1: build: context: . dockerfile: Dockerfile command: gunicorn --workers 6 --bind 0.0.0.0:8000 wsgi expose: - 8000 application2: build: context: . dockerfile: Dockerfile command: gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi expose: - 8000 nginx: image: nginx volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - 80:80 depends_on: - application1 - application2
Я в основном продублировал определение application
, но первая служба работает сейчас 6 рабочих, просто ради того, чтобы показать возможную разницу между ними. Теперь запустите docker-compose up -d
и docker-compose logs -f nginx
. Если вы несколько раз обновите страницу в браузере, то увидите что-то вроде
$ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | [14/Feb/2020:11:03:25 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:25 +0000] localhost to: 172.18.0.2:8000: GET /favicon.ico HTTP/1.1 404 nginx_1 | [14/Feb/2020:11:03:30 +0000] localhost to: 172.18.0.3:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:31 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:32 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:33 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:33 +0000] localhost to: 172.18.0.3:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:34 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:34 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:35 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:35 +0000] localhost to: 172.18.0.3:8000: GET / HTTP/1.1 200
где вы можете четко заметить балансировку нагрузки между 172.18.0.2
( приложение 1
) и 172.18.0.3
( приложение 2
) в действии.
Я не буду показывать здесь пример обратного прокси или HTTPS, чтобы этот пост не стал слишком длинным. Вы можете найти ресурсы по этим темам в следующем разделе.
4.3 Ресурсы
Эти ресурсы предоставляют более подробную информацию по темам, обсуждаемым в этом разделе
4.4 Проблемы
Ну что ж, наконец-то можно сказать, что дело сделано. Теперь у нас есть готовый к производству веб-сервер перед нашей многопоточной веб-платформой, и мы можем сосредоточиться на написании кода Python вместо того, чтобы иметь дело с HTTP-заголовками.
Использование веб-сервера позволяет нам масштабировать инфраструктуру, просто добавляя за ней новые экземпляры, не прерывая работу сервиса. Параллельный сервер HTTP запускает несколько экземпляров нашего фреймворка, а сам фреймворк абстрагирует HTTP, сопоставляя его с нашим языком высокого уровня.
Бонус: облачные инфраструктуры
В первые годы существования Интернета компании имели собственные локальные серверы, а системные администраторы запускали весь стек непосредственно на голой операционной системе. Излишне говорить, что это было сложно, дорого и рискованно.
В настоящее время “облако” – это путь, поэтому я хочу кратко упомянуть некоторые компоненты, которые могут помочь вам запустить такой веб-стек на AWS, который является платформой, которую я знаю больше всего и наиболее распространенным облачным провайдером в мире на момент написания этой статьи.
Эластичный Бобовый Стебель
Это решение начального уровня для простых приложений, представляющее собой управляемую инфраструктуру, обеспечивающую балансировку нагрузки, автоматическое масштабирование и мониторинг. Вы можете использовать несколько языков программирования (среди которых Python и Node.js) и выбирать между различными веб-серверами, такими как, например, Apache или nginx. Компоненты электронной службы не скрыты, но у вас нет прямого доступа к ним, и вы должны полагаться на файлы конфигурации, чтобы изменить способ их работы. Это хорошее решение для простых услуг, но вам, вероятно, скоро понадобится больше контроля.
Перейти к Эластичному Бобовому стеблю
Эластичный контейнерный сервис (ECS)
С помощью ECS вы можете запускать контейнеры Docker, группируя их в кластеры и настраивая политики автоматического масштабирования, связанные с метриками, поступающими из CloudWatch. У вас есть выбор: запускать их на экземплярах EC2 (виртуальных машинах), управляемых вами, или на бессерверной инфраструктуре под названием Fargate. ECS будет запускать ваши контейнеры Docker, но вам все равно придется самостоятельно создавать DNS-записи и балансировщики нагрузки. У вас также есть выбор запуска ваших контейнеров на Kubernetes с помощью EKS (Elastic Kubernetes Service).
Перейдите в Службу эластичных контейнеров
Эластичное вычислительное облако (EC2)
Это голый металл AWS, где вы запускаете автономные виртуальные машины или автоматически масштабируемую группу из них. Вы можете войти в эти экземпляры по SSH и предоставить сценарии для установки и настройки программного обеспечения. Вы можете установить здесь свое приложение, веб-серверы, базы данных, все, что захотите. Хотя это было так в самом начале эпохи облачных вычислений, я не думаю, что вы должны идти на это. Облачный провайдер может предоставить вам так много сопутствующих услуг, таких как журналы или мониторинг, а также с точки зрения производительности, что нет смысла избегать их использования. EC2 все еще существует, во всяком случае, и если вы запускаете ECS поверх него, вам нужно знать, что вы можете и что вы не можете сделать.
Перейдите в Elastic Compute Cloud
Упругая Балансировка Нагрузки
В то время как балансировщики сетевой нагрузки (NLB) управляют чистыми соединениями TCP/IP, балансировщики нагрузки приложений предназначены для HTTP и могут выполнять многие из необходимых нам служб. Они могут отменить прокси-сервер с помощью правил (которые были недавно улучшены) и завершить TLS, используя сертификаты, созданные в ACM (AWS Certificate Manager). Как вы можете видеть, ALBS являются хорошей заменой веб-серверу, хотя им явно не хватает экстремальной конфигурируемости программного обеспечения. Однако вы можете использовать их в качестве первого уровня балансировки нагрузки, все еще используя nginx или Apache позади них, если вам нужны некоторые из функций, которые они предоставляют.
Перейти к Балансировке упругой нагрузки
CloudFront
CloudFront-это сеть доставки контента, то есть географически распределенный кэш, который обеспечивает более быстрый доступ к вашему контенту. Хотя CDNS не являются частью стека, который я обсуждал в этом посте, я думаю, что стоит упомянуть CF, поскольку он может ускорить любой статический контент, а также прекратить TLS в связи с AWS Certificate Manager.
Перейти к CloudFront
Вывод
Как вы можете видеть, веб-стек-это довольно богатый набор компонентов, и причина, стоящая за ними, часто связана с производительностью. Есть много технологий, которые мы считаем само собой разумеющимися и которые, к счастью, стали легче развертывать, но я все еще считаю, что инженер полного стека должен знать не только о существовании таких слоев, но и об их назначении и, по крайней мере, об их базовой конфигурации.
Первоначально опубликовано на The Digital Cat