开发用于 Azure SQL 数据库的 Kubernetes 应用程序

适用于:Azure SQL 数据库

在本教程中,我们将了解如何使用 Python、Docker 容器、Kubernetes 和 Azure SQL 数据库来开发新式应用程序。

新式应用程序开发面临着诸多挑战。 从选择前端的“堆栈”到按照多个竞争标准进行数据存储和处理,通过确保最高级别的安全性和性能,开发人员必须确保应用程序缩放,而且性能良好,可在多个平台上得到支持。 为了满足上述要求,将应用程序捆绑到容器技术(例如 Docker),并将多个容器部署到 Kubernetes 平台,目前已经称为应用程序开发中的常规方法。

在此示例中,我们将探讨如何使用 Python、Docker 容器和 Kubernetes - 它们都在 Azure 平台上运行。 使用 Kubernetes 意味着你还可以灵活地使用本地环境甚至其他云,实现应用程序的无缝一致部署,并且允许使用多云部署,从而提高复原能力。 我们还将 Azure SQL 数据库用于基于服务、可缩放、高度可复原且安全的环境,来进行数据存储和处理。 事实上,在许多情况下,其他应用程序通常已在使用 Azure SQL 数据库,而此示例应用程序可用来进一步使用和扩充这些数据。

此示例的范围非常全面,但却使用了最简单的应用程序、数据库和部署来说明这个过程。 你可以对此示例进行调整,让它更加可靠,甚至包括使用最新技术来处理返回的数据。 它是为其他应用程序创建模式的实用学习工具。

在实际示例中使用 Python、Docker 容器、Kubernetes 和 AdventureWorksLT 示例数据库

AdventureWorks 是一家虚构的公司,他们使用一个数据库来存储有关销售和营销、产品、客户、制造的数据。 该公司还使用视图和存储过程,用于联接有关产品的信息,例如产品名称、类别、价格和简要说明。

AdventureWorks 开发团队希望创建一个概念证明 (PoC),从 AdventureWorksLT 数据库中的视图返回数据,并将其作为 REST API 提供。 使用此 PoC,开发团队将为销售团队创建可缩放性更强的多云就绪应用程序。 他们选择了 Azure 平台来处理部署的各个方面。 PoC 使用以下元素:

  • 一个 Python 应用程序,使用 Flask 包进行无界面的 Web 部署。
  • Docker 容器,用于代码和环境隔离,存储在专用注册表中,让整个公司能够在今后的项目中重复使用应用程序容器,从而节省时间和资金。
  • Kubernetes,便于轻松进行部署和缩放,并且避免平台锁定。
  • Azure SQL 数据库,用于选择大小、性能、规模、自动管理和备份,还可用于最高安全级别的关系数据存储和处理。

在本文中,我们将介绍创建整个概念证明项目的过程。 下面是创建应用程序的常规步骤:

  1. 设置先决条件
  2. 创建应用程序
  3. 创建 Docker 容器以部署应用程序和测试
  4. 创建 Azure 容器服务 (ACS) 注册表并将容器加载到 ACS 注册表
  5. 创建 Azure Kubernetes 服务 (AKS) 环境
  6. 将应用程序容器从 ACS 注册表部署到 AKS
  7. 测试应用程序
  8. 清理

先决条件

在本文中,有一些值应该进行替换。 请确保在每个步骤中一致地替换这些值。 在完成概念证明项目时,你可能需要打开文本编辑器并删除这些值,以便设置正确的值:

  • ReplaceWith_AzureSubscriptionName:将此值替换为你的 Azure 订阅的名称。
  • ReplaceWith_PoCResourceGroupName:将此值替换你要创建的资源组的名称。
  • ReplaceWith_AzureSQLDBServerName:将此值替换为你使用 Azure 门户创建的 Azure SQL Server 数据库逻辑服务器的名称。
  • ReplaceWith_AzureSQLDBSQLServerLoginName:将此值替换为你在 Azure 门户中创建的 SQL Server 用户名的值。
  • ReplaceWith_AzureSQLDBSQLServerLoginPassword:将此值替换为你在 Azure 门户中创建的 SQL Server 用户密码的值。
  • ReplaceWith_AzureSQLDBDatabaseName:将此值替换为你使用 Azure 门户创建的 Azure SQL 数据库的名称。
  • ReplaceWith_AzureContainerRegistryName:将此值替换为你要创建的 Azure 容器注册表的名称。
  • ReplaceWith_AzureKubernetesServiceName:将此值替换为你要创建的 Azure Kubernetes 服务的名称。

AdventureWorks 的开发人员结合使用 Windows、Linux 和 Apple 系统进行开发,因而他们使用 Visual Studio Code 作为环境,使用 git 进行源代码管理,这两者都跨平台运行。

对于 PoC 项目,开发团队需要满足以下先决条件:

  1. Python、pip 和包 - 开发团队选择 Python 编程语言 作为这个基于 Web 的应用程序的标准。 目前他们使用的是版本 3.9,但支持 PoC 必需包的任何版本都是可以接受的。

  2. 开发团队使用 pyodbc 包进行数据库访问。

  3. 开发团队使用 ConfigParser 包来控制和设置配置变量。

  4. 开发团队将 Flask 包用作应用程序的 Web 接口。

  5. 接下来,开发团队安装了 Azure CLI 工具,可以轻松地使用 az 语法进行标识。 借助这个跨平台工具,他们可以将命令行和脚本化方法用于 PoC,这样他们就能够在进行更改和改进时重复这些步骤。

  6. 设置 Azure CLI 后,开发团队登录到其 Azure 订阅,并设置用于 PoC 的订阅名称。 然后,他们确保订阅可以访问 SQL 数据库服务器和数据库:

    az login
    az account set --name "ReplaceWith_AzureSubscriptionName"
    az sql server list
    az sql db list ReplaceWith_AzureSQLDBDatabaseName 
    
  7. Azure 资源组是用于保存 Azure 解决方案相关资源的逻辑容器。 通常可将共享相同生命周期的资源添加到同一资源组,以便将其作为一个组轻松部署、更新和删除。 资源组存储有关资源的元数据,你可以指定资源组的位置。

    可以使用 Azure 门户或 Azure CLI 来创建和管理资源组。 它们还可用于对应用程序的相关资源进行分组,并将其划分为用于生产和非生产的组,或者划分为你需要的任何组织结构。

    Screenshot from the Azure portal showing how to search for and filter for Azure Resource groups.

    在以下代码片段中,可以看到使用 az 命令来创建资源组。 在我们的示例中,我们使用 Azure 的 chinanorth3 区域

    az group create --name ReplaceWith_PoCResourceGroupName --location chinanorth3
    
  8. 开发团队使用 SQL 身份验证登录名,创建安装了 AdventureWorksLT 示例数据库的 Azure SQL 数据库。

    AdventureWorks 将 Microsoft SQL Server 关系数据库管理系统平台作为标准,开发团队希望为数据库使用托管服务,而不是在本地安装。 使用 Azure SQL 数据库,无论这些托管服务在何处运行 SQL Server 引擎,包括在本地、容器、Linux 或 Windows 环境中运行,甚至在物联网 (IoT) 环境中运行,都可以完全兼容托管服务。

    1. 创建过程中,他们使用 Azure 管理门户,将应用程序的防火墙设置为本地开发计算机,并更改了此处显示的默认设置,以启用“允许所有 Azure 服务”,并检索了连接凭据

      Screenshot from the Azure portal showing the Create SQL Database page. On the Networking tab, for Connectivity method, the Public endpoint option is selected. The Add current client IP Address option is Yes.

      使用此方法,可在另一个区域甚至不同的订阅中访问数据库。

    2. 开发团队设置了 SQL 身份验证登录名,用于进行测试,但在安全评审中,我们将重新考虑这个决定。

    3. 开发团队为 PoC 使用了 AdventureWorksLT 示例数据库,还使用了相同的 PoC 资源组。 别担心,在本教程结束时,我们将清理这个新 PoC 资源组中的所有资源。

    4. 你可以使用 Azure 门户来部署 Azure SQL 数据库。 创建 Azure SQL 数据库时,在“其他设置”选项卡中,选择“使用现有数据”选项,再选择“示例”

      Screenshot from the Azure portal showing the Create SQL Database page. In the Additional settings tab, for the Use existing data option, select Sample.

    5. 最后,在新 Azure SQL 数据库的“标记”选项卡上,开发团队为此 Azure 资源提供了标记元数据,例如 OwnerServiceClassWorkloadName

创建应用程序

接下来,开发团队创建了一个简单的 Python 应用程序,该应用程序打开与 Azure SQL 数据库的连接,并返回产品列表。 此代码将替换为更复杂的函数,还可能包括部署到用于生产的 Kubernetes Pod 中的多个应用程序,以便使用可靠的清单驱动的方法,提供应用程序解决方案。

  1. 开发团队创建了一个名为 .env 的简单文本文件,用于保存服务器连接和其他信息的变量。 使用 python-dotenv 库,他们可以将变量与 Python 代码分开。 这是让机密和其他信息不保留在代码自身中的常见方法。

    SQL_SERVER_ENDPOINT = ReplaceWith_AzureSQLDBServerName
    SQL_SERVER_USERNAME = ReplaceWith_AzureSQLDBSQLServerLoginName
    SQL_SERVER_PASSWORD = ReplaceWith_AzureSQLDBSQLServerLoginPassword
    SQL_SERVER_DATABASE = ReplaceWith_AzureSQLDBDatabaseName
    

    注意

    为了清晰和简单起见,此应用程序使用从 Python 读取的配置文件。 由于代码将使用容器进行部署,因而连接信息也许能够从内容派生。 应该谨慎考虑用于保护安全、连接和机密的各种方法,并确定应该用于应用程序的最佳级别和机制。 始终选择最高级别的安全性,甚至选择多个级别,从而确保应用程序是安全的。 有多个选项可用于处理机密信息(如连接字符串等),以下列表显示了其中的一些选项。

    有关详细信息,请参阅 Azure SQL 数据库安全

  2. 然后,开发团队编写了 PoC 应用程序,并将其命名为 app.py

    以下脚本完成这些步骤:

    1. 为配置和基本 Web 接口设置库。
    2. .env 文件加载变量。
    3. 创建 Flask-RESTful 应用程序。
    4. 使用 config.ini 文件值,访问 Azure SQL 数据库连接信息。
    5. 使用 config.ini 文件值,创建与 Azure SQL 数据库的连接。
    6. 使用 pyodbc 包,连接到 Azure SQL 数据库。
    7. 创建要对数据库运行的 SQL 查询。
    8. 创建将用于从 API 返回数据的类。
    9. 将 API 终结点设置为 Products 类。
    10. 最后,在默认 Flask 端口 5000 上启动应用程序。
    # Set up the libraries for the configuration and base web interfaces
    from dotenv import load_dotenv
    from flask import Flask
    from flask_restful import Resource, Api
    import pyodbc
    
    # Load the variables from the .env file
    load_dotenv()
    
    # Create the Flask-RESTful Application
    app = Flask(__name__)
    api = Api(app)
    
    # Get to Azure SQL Database connection information using the config.ini file values
    server_name = os.getenv('SQL_SERVER_ENDPOINT')
    database_name = os.getenv('SQL_SERVER_DATABASE')
    user_name = os.getenv('SQL_SERVER_USERNAME')
    password = os.getenv('SQL_SERVER_PASSWORD')
    
    # Create connection to Azure SQL Database using the config.ini file values
    ServerName = config.get('Connection', 'SQL_SERVER_ENDPOINT')
    DatabaseName = config.get('Connection', 'SQL_SERVER_DATABASE')
    UserName = config.get('Connection', 'SQL_SERVER_USERNAME')
    PasswordValue = config.get('Connection', 'SQL_SERVER_PASSWORD')
    
    # Connect to Azure SQL Database using the pyodbc package
    # Note: You may need to install the ODBC driver if it is not already there. You can find that at:
    # https://learn.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server
    connection = pyodbc.connect(f'Driver=ODBC Driver 17 for SQL Server;Server={ServerName};Database={DatabaseName};uid={UserName};pwd={PasswordValue}')
    
    # Create the SQL query to run against the database
    def query_db():
        cursor = connection.cursor()
        cursor.execute("SELECT TOP (10) [ProductID], [Name], [Description] FROM [SalesLT].[vProductAndDescription] WHERE Culture = 'EN' FOR JSON AUTO;")
        result = cursor.fetchone()
        cursor.close()
        return result
    
    # Create the class that will be used to return the data from the API
    class Products(Resource):
        def get(self):
            result = query_db()
            json_result = {} if (result == None) else json.loads(result[0])     
            return json_result, 200
    
    # Set the API endpoint to the Products class
    api.add_resource(Products, '/products')
    
    # Start App on default Flask port 5000
    if __name__ == "__main__":
        app.run(debug=True)
    
  3. 它们确认此应用程序在本地运行,并将页面返回到 http://localhost:5000/products

    Screenshot from a web browser of the Flask return page.

    重要

    生成生产应用程序时,请勿使用管理员帐户访问数据库。 有关详细信息,请阅读有关如何为应用程序设置帐户的更多内容。 本文中的代码经过简化,让你能够使用 Azure 中的 Python 和 Kubernetes,快速开始开发应用程序。

    更实际的做法是,你可以使用具有只读权限的包含数据库用户,或者使用连接到具有只读权限的用户分配的托管标识的登录名或包含数据库用户。

    有关详细信息,请查看有关如何使用 Python 和 Azure SQL 数据库创建 API 的完整示例

将应用程序部署到 Docker 容器

容器是在计算系统中保留的受保护空间,可提供隔离和封装。 若要创建容器,请使用清单文件,这是一个文本文件,描述要包含的二进制文件和代码。 然后,使用容器运行时(例如 Docker),你可以创建一个二进制映像,其中包含要运行和引用的所有文件。 在此处,可以“运行”二进制映像,它称为容器,你可以引用容器,如同它是一个完整计算系统那样。 与使用完整虚拟机相比,它占用的存储空间更小,可以更简单地抽象应用程序运行时和环境。 有关详细信息,请参阅容器和 Docker

开发团队首先使用 DockerFile(清单),这个清单对团队要使用的元素进行了分层。 他们首先要创建已安装 pyodbc 库的基本 Python 映像,然后运行包含上一步中的程序和配置文件所需的所有命令。

下面的 Dockerfile 包括以下步骤:

  1. 首先创建已安装 Python 和 pyodbc 的容器二进制文件。
  2. 为应用程序创建工作目录。
  3. 将所有代码从当前目录复制到 WORKDIR
  4. 安装所需的库。
  5. 容器启动后,运行应用程序,并打开所有 TCP/IP 端口。
# syntax=docker/dockerfile:1

# Start with a Container binary that already has Python and pyodbc installed
FROM laudio/pyodbc

# Create a Working directory for the application
WORKDIR /flask2sql

# Copy all of the code from the current directory into the WORKDIR
COPY . .

# Install the libraries that are required
RUN pip install -r ./requirements.txt

# Once the container starts, run the application, and open all TCP/IP ports 
CMD ["python3", "-m" , "flask", "run", "--host=0.0.0.0"]

有了该文件,开发团队转到编码目录中的命令提示符,并运行以下代码,从清单创建二进制映像,然后运行另一个命令来启动容器:

docker build -t flask2sql .
docker run -d -p 5000:5000 -t flask2sql

开发团队再次测试 http://localhost:5000/products 链接,以确保容器可以访问数据库,他们看到以下返回结果:

Screenshot from a web browser of the Flask return page from the Container.

将映像部署到 Docker 注册表

容器目前正在工作,但仅在开发人员的计算机上可用。 开发团队希望将此应用程序映像提供给公司的其余用户,然后提供给 Kubernetes 进行生产部署。

容器映像的存储区域称为存储库,可能同时提供容器映像的公共存储库和专用存储库。 事实上,AdvenureWorks 在 Dockerfile 中使用了 Python 环境的公共映像。

团队希望控制对映像的访问,而不是将其置于 Web 上,他们决定自己托管映像,但在 Azure 中,他们可以完全控制安全性和访问权限。 可在此处详细了解 Azure 容器注册表。

返回到命令行时,开发团队使用 az CLI 来添加容器注册表服务、启用管理帐户、在测试阶段将其设置为匿名“拉取”,并将登录上下文设置为注册表:

az acr create --resource-group ReplaceWith_PoCResourceGroupName --name ReplaceWith_AzureContainerRegistryName --sku Standard
az acr update -n ReplaceWith_AzureContainerRegistryName --admin-enabled true
az acr update --name ReplaceWith_AzureContainerRegistryName --anonymous-pull-enabled
az acr login --name ReplaceWith_AzureContainerRegistryName

此上下文将在后续步骤中使用。

标记本地 Docker 映像,准备进行上传

下一步是将本地应用程序容器映像发送到 Azure 容器注册表 (ACR) 服务,使其在云端可用。

  1. 在以下示例脚本中,开发团队使用 Docker 命令列出计算机上的映像。
  2. 它们使用 az CLI 实用工具列出 ACR 服务中的映像。
  3. 他们使用 Docker 命令,使用在上一步中创建的 ACR 的目标名称来“标记”映像,并设置版本号以进行适当的 DevOps。
  4. 最后,他们再次列出本地映像信息,以确保正确地应用标记。
docker images
az acr list --resource-group ReplaceWith_PoCResourceGroupName --query "[].{acrLoginServer:loginServer}" --output table
docker tag flask2sql ReplaceWith_AzureContainerRegistryName.azurecr.io/azure-flask2sql:v1
docker images

编写并测试代码后,开发团队运行和测试了 Dockerfile、映像和容器,设置了 ACR 服务,应用了所有标记,然后他们可以将映像上传到 ACR 服务。

他们使用 Docker“push”命令发送文件,然后使用 az CLI 实用工具来确保映像已加载:

docker push ReplaceWith_AzureContainerRegistryName.azurecr.io/azure-flask2sql:v1
az acr repository list --name ReplaceWith_AzureContainerRegistryName --output table

部署到 Kubernetes

开发团队只需运行容器,并将应用程序部署到本地和云端环境。 但是,他们希望添加应用程序的多个副本以实现缩放和可用性,添加执行不同任务的其他容器,并为整个解决方案添加监视和仪表化功能。

为了将容器组合到完整的解决方案中,开发团队决定使用 Kubernetes。 Kubernetes 可在本地和所有主要云平台上运行。 Azure 具有适用于 Kubernetes 的完整托管环境,称为 Azure Kubernetes 服务 (AKS)。 阅读 Azure 培训路径上的 Kubernetes 简介,了解有关 AKS 的详细信息。

使用 az CLI 实用工具,开发团队将 AKS 添加到此前创建的同一资源组。 使用单个 az 命令,开发团队完成以下步骤:

  • 在测试阶段添加两个“节点”或计算环境,以实现复原能力
  • 自动生成 SSH 密钥以便访问环境
  • 附加在前面的步骤中创建的 ACR 服务,让 AKS 群集能够找到要用于部署的映像
az aks create --resource-group ReplaceWith_PoCResourceGroupName --name ReplaceWith_AzureKubernetesServiceName --node-count 2 --generate-ssh-keys --attach-acr ReplaceWith_AzureContainerRegistryName

Kubernetes 使用命令行工具来访问和控制名为 kubectl 的群集。 开发团队使用 az CLI 实用工具来下载和安装 kubectl 工具:

az aks install-cli

由于他们当时已与 AKS 建立连接,因此可以要求其发送 SSH 密钥,以便在执行 kubectl 实用工具时使用连接:

az aks get-credentials --resource-group ReplaceWith_PoCResourceGroupName --name ReplaceWith_AzureKubernetesServiceName

这些密钥存储在用户目录中名为 .config 的文件中。 设置安全上下文后,开发团队使用 kubectl get nodes 来显示群集中的节点:

kubectl get nodes

现在,开发团队使用 az CLI 工具来列出 ACR 服务中的映像:

az acr list --resource-group ReplaceWith_PoCResourceGroupName --query "[].{acrLoginServer:loginServer}" --output table

随后他们可以生成 Kubernetes 用于控制部署的清单。 它是以 yaml 格式存储的文本文件。 下面是 flask2sql.yaml 文件中的注释文本:

apiVersion: apps/v1
# The type of commands that will be sent, along with the name of the deployment
kind: Deployment
metadata:
  name: flask2sql
# This section sets the general specifications for the application
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask2sql
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  minReadySeconds: 5 
  template:
    metadata:
      labels:
        app: flask2sql
    spec:
      nodeSelector:
        "kubernetes.io/os": linux
# This section sets the location of the Image(s) in the deployment, and where to find them 
      containers:
      - name: flask2sql
        image:  bwoodyflask2sqlacr.azurecr.io/azure-flask2sql:v1
# Recall that the Flask application uses (by default) TCIP/IP port 5000 for access. This line tells Kubernetes that this "pod" uses that address.
        ports:
        - containerPort: 5000
---
apiVersion: v1
# This is the front-end of the application access, called a "Load Balancer"
kind: Service
metadata:
  name: flask2sql
spec:
  type: LoadBalancer
# this final step then sets the outside exposed port of the service to TCP/IP port 80, but maps it internally to the app's port of 5000
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000
  selector:
    app: flask2sql

定义 flask2sql.yaml 文件后,开发团队可以将应用程序部署到正在运行的 AKS 群集。 他们使用 kubectl apply 命令完成此操作,你可能还记得,该命令仍然具有群集的安全性上下文。 然后发送 kubectl get service 命令,以便在生成群集时监视群集。

kubectl apply -f flask2sql.yaml
kubectl get service flask2sql --watch

稍后,“watch”命令将返回外部 IP 地址。 此时,开发团队按 Ctrl-C 中断监视命令,并记录负载均衡器的外部 IP 地址。

测试应用程序

使用在上一步中获取的 IP 地址(端点),开发团队进行检查,以确保与本地应用程序和 Docker 容器相同的输出:

Screenshot from a web browser of the Flask return page when finally testing the container. The output is the same as before.

清理

创建、编辑、记录和测试应用程序后,开发团队现在可以“拆解”应用程序。 通过在 Azure 中保留单个资源组中的所有内容,很轻松就能使用 az CLI 实用工具删除 PoC 资源组:

az group delete -n ReplaceWith_PoCResourceGroupName -y

注意

如果在另一个资源组中创建 Azure SQL 数据库,而且不再需要它,则可以使用 Azure 门户将其删除。

领导 PoC 项目的团队成员使用 Microsoft Windows 作为工作站,希望保留来自 Kubernetes 机密文件,但将其从系统中删除,作为活动位置。 他们只需将文件复制到 config.old 文本文件,然后将其删除:

copy c:\users\ReplaceWith_YourUserName\.kube\config c:\users\ReplaceWith_YourUserName\.kube\config.old
del c:\users\ReplaceWith_YourUserName\.kube\config