近期在学习IBM全栈应用开发微学士课程,故此记录学习笔记。

1. 应用程序:静态页面

恭喜您成为 “Best Cars” 汽车经销商的首席云应用程序开发员。作为热身任务,您需要运行并测试其主要 Django 应用程序。

Django 应用程序将主要用于用户管理和身份验证、管理汽车型号和品牌,以及路由其他 MongoDB 服务以获取经销商和客户评论。您将在毕业设计课程中分阶段构建此 Django 应用程序和相关服务。

将更改推送到 Git 仓库的步骤:

1
2
3
4
5
git config --global user.email "[email protected]"
git config --global user.name "name"
git add .
git commit -m"Adding temporary changes to Github"
git push

ibm-developer-skills-network/xrwvm-fullstack_developer_capstone - GitHub

导航至此仓库,并创建一个包含本项目所需的基本启动代码的版本库分叉(fork)。

1.1. 在开发服务器上运行 Django 应用程序

观察 Django 应用程序骨架结构的文件夹结构。您会看到服务器文件夹下有三个子文件夹:

  • djangoapp:包含 Django 应用程序。
  • djangoproj:包含项目配置。
  • frontend:HTML 和 CSS 以及 React 前端。

接下来,让我们设置 Python 运行时并测试应用程序。

1
2
3
4
cd xrwvm-fullstack_developer_capstone/server
pip install virtualenv
virtualenv djangoenv
source djangoenv/bin/activate

在虚拟环境中安装必要的 Python 软件包。软件包名称已在 requirements.txt 中提供。

1
python3 -m pip install -U -r requirements.txt

server/djangoproj/settings.pyTEMPLATES 下,您会发现 DIRS 是一个空列表。在列表中添加 os.path.join(BASE_DIR,'frontend/static'),以便 Django 应用程序识别前端静态文件。设置如下:

1
2
3
'DIRS': [
os.path.join(BASE_DIR,'frontend/static')
],

在同一文件 server/djangoproj/settings.py 中,在文件底部添加 Django 应用程序查找静态文件的目录。

1
2
3
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'frontend/static')
]

执行迁移,创建必要的表格。

1
python3 manage.py makemigrations

运行迁移以激活应用程序的模型。

1
python3 manage.py migrate

启动本地开发服务器。

1
python3 manage.py runserver

在文件编辑器中打开 server/djangoproj/settings.py,设置 ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS 以反映 Django 应用程序的根 URL。

请不要在末尾添加 /。

1
2
ALLOWED_HOSTS=['localhost','<your application URL here>']
CSRF_TRUSTED_ORIGINS=['<your application URL here>']

1.2. 添加 “About Us” 页面

在编辑器中打开 server/frontend/static/About.html。同一文件夹中还有样式表 style.css。在 About.html 中的 <head> 标签中链接样式表,以便在 HTML 文件中使用。

1
2
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/bootstrap.min.css">

在名为 about-header<div> 标记中粘贴以下内容。

1
2
<h1>About Us</h1>
Welcome to Best Cars dealership, home to the best cars in North America. We deal in sale of domestic and imported cars at reasonable prices.

将图片 person.png 改为真人图片,并修改 About.html 中的所有文字,使其看起来更真实。根据您的喜好更改样式。

转到 djangoproj/urls.py,在 urlpatterns 中添加以下内容:

1
path('about/', TemplateView.as_view(template_name="About.html")),

1.3. 添加 “Contact Us” 页面

server/frontend/static 文件夹下添加一个名为 Contact.html 的新文件。

添加样式表链接。

添加标题导航栏,将 Contact Us 作为活动链接。

在文件中写入联系信息内容。您可以发挥创意,编写自己的信息。使用 css 创建样式。

contact 路径添加到 djangproj/urls.py

1
path('contact/', TemplateView.as_view(template_name="Contact.html")),

Django 服务器会自动重启。

2. 应用程序:用户管理

现在,您已经构建并部署了最初的 Django 应用程序。下一步,经销商的管理员将查看应用程序,以识别用户并根据角色(如匿名用户或注册用户)管理其访问权限。为此,您需要在应用程序中添加身份验证和授权,即用户管理。在本课中,您需要执行以下任务来添加用户管理功能:

  • 为应用程序创建超级用户。
  • 构建客户端并进行配置。
  • 检查客户端配置。
  • 添加登录视图以处理登录请求。
  • 添加注销视图以处理注销请求。
  • 添加注册视图来处理注册请求。

2.1. 为您的应用创建超级用户

运行以下命令创建超级用户:

1
python manage.py createsuperuser

输入用户名、电子邮件和密码后,您应该会看到 Superuser created successfully 的信息,表明超级用户已创建。执行以下命令运行服务器:

1
python manage.py runserver

请在端口 8000 上启动应用程序,并在 URL 末尾添加 /admin,以进入 Django 管理用户界面。使用刚刚为超级用户创建的凭据登录管理员站点。单击 AUTHENTICATION AND AUTHORIZATION 部分下的 Users。您应该可以查看刚刚创建的超级用户。

2.2. 构建客户端并进行配置

打开新终端,切换到客户端目录:

1
cd /server/frontend

安装所有必需的软件包:

1
npm install

运行以下命令构建客户端:

1
npm run build

在编辑器中打开 server/djangoproj/settings.py。在 TEMPLATES 下可以找到 DIRS。在列表中添加 os.path.join(BASE_DIR,'frontend/build'),以便 Django 应用程序识别前端。设置如下:

1
2
3
4
5
'DIRS': [
os.path.join(BASE_DIR, 'frontend/static'),
os.path.join(BASE_DIR, 'frontend/build'),
os.path.join(BASE_DIR, 'frontend/build/static'),
],

server/djangoproj/settings.py 中,将 Django 应用程序查找静态文件的目录添加到名为 STATICFILES_DIRS 的列表中。设置如下:

1
2
3
4
5
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'frontend/static'),
os.path.join(BASE_DIR, 'frontend/build'),
os.path.join(BASE_DIR, 'frontend/build/static'),
]

2.3. 添加新的登录视图

接下来,您需要创建一个新的登录 Django 视图来处理登录请求。打开 djangoapp/views.py,取消顶部的导入语句。观察登录视图以验证用户身份。用户登录后,视图会返回一个包含用户名和状态的 JSON 对象。

打开 server/djangoapp/urls.py,取消注释顶部的导入语句:

1
2
3
4
from django.urls import path
from django.conf.urls.static import static
from django.conf import settings
from . import views

通过取消对 server/djangoapp/urls.py 中路径条目的注释,为登录视图配置路由,如下所示:

1
path(route='login', view=views.login_user, name='login'),

如下所示,在 server/djangoproj/urls.py 中添加路径条目,为登录页面配置路由。登录页面是通过 /server/frontend/src/App.js 中配置的路由呈现的 React 页面。

1
path('login/', TemplateView.as_view(template_name="index.html")),

2.4. 添加注销功能

您需要创建一个新的注销 Django 视图来处理注销请求。

打开 djangoapp/views.py,添加一个新的注销视图来处理注销请求。用户注销后,视图应返回一个包含用户名的 JSON 对象。

1
2
3
4
def logout_request(request):
logout(request)
data = {"userName": ""}
return JsonResponse(data)

djangoapp/urls.py 中添加路径条目,为注销视图配置路由:

1
path(route='logout', view=views.logout_request, name='logout'),

server/frontend/static/Home.html 中的空白处加入以下代码,以处理用户退出登录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const logout = async (e) => {
let logout_url = window.location.origin+"/djangoapp/logout";
const res = await fetch(logout_url, {
method: "GET",
});

const json = await res.json();
if (json) {
let username = sessionStorage.getItem('username');
sessionStorage.removeItem('username');
window.location.href = window.location.origin;
window.location.reload();
alert("Logging out "+username+"...")
}
else {
alert("The user could not be logged out.")
}
};

打开一个新的终端,运行以下命令:

1
2
cd /server/frontend
npm run build

如果会话没有过期,您将从上一步登录。如果没有,请登录,然后单击 Logout

2.5. 添加注册功能

您需要创建一个新的注册 Django 视图来处理注册请求。

打开 djangoapp/views.py,添加一个新的注册视图来处理注册请求。用户注册时,应创建一个用户对象,并登录该用户。它应返回一个包含用户名的 JSON 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@csrf_exempt
def registration(request):
context = {}

data = json.loads(request.body)
username = data['userName']
password = data['password']
first_name = data['firstName']
last_name = data['lastName']
email = data['email']
username_exist = False
email_exist = False
try:
# Check if user already exists
User.objects.get(username=username)
username_exist = True
except:
# If not, simply log this is a new user
logger.debug("{} is new user".format(username))

# If it is a new user
if not username_exist:
# Create user in auth_user table
user = User.objects.create_user(username=username, first_name=first_name, last_name=last_name,password=password, email=email)
# Login the user and redirect to list page
login(request, user)
data = {"userName":username,"status":"Authenticated"}
return JsonResponse(data)
else :
data = {"userName":username,"error":"Already Registered"}
return JsonResponse(data)

djangoapp/urls.py 中添加路径条目,为注册视图配置路由:

1
path(route='register', view=views.registration, name='register'),

frontend/src/components/Register 中创建一个名为 Register.jsx 的文件。我们已经提供了该页面要使用的 CSS。在 Register.jsx 中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import React, { useState } from "react";
import "./Register.css";
import user_icon from "../assets/person.png"
import email_icon from "../assets/email.png"
import password_icon from "../assets/password.png"
import close_icon from "../assets/close.png"

const Register = () => {

const [userName, setUserName] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setlastName] = useState("");


const gohome = ()=> {
window.location.href = window.location.origin;
}

const register = async (e) => {
e.preventDefault();

let register_url = window.location.origin+"/djangoapp/register";

const res = await fetch(register_url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"userName": userName,
"password": password,
"firstName":firstName,
"lastName":lastName,
"email":email
}),
});

const json = await res.json();
if (json.status) {
sessionStorage.setItem('username', json.userName);
window.location.href = window.location.origin;
}
else if (json.error === "Already Registered") {
alert("The user with same username is already registered");
window.location.href = window.location.origin;
}
};

return(
<div className="register_container" style={{width: "50%"}}>
<div className="header" style={{display: "flex",flexDirection: "row", justifyContent: "space-between"}}>
<span className="text" style={{flexGrow:"1"}}>SignUp</span>
<div style={{display: "flex",flexDirection: "row", justifySelf: "end", alignSelf: "start" }}>
<a href="/" onClick={()=>{gohome()}} style={{justifyContent: "space-between", alignItems:"flex-end"}}>
<img style={{width:"1cm"}} src={close_icon} alt="X"/>
</a>
</div>
<hr/>
</div>

<form onSubmit={register}>
<div className="inputs">
<div className="input">
<img src={user_icon} className="img_icon" alt='Username'/>
<input type="text" name="username" placeholder="Username" className="input_field" onChange={(e) => setUserName(e.target.value)}/>
</div>
<div>
<img src={user_icon} className="img_icon" alt='First Name'/>
<input type="text" name="first_name" placeholder="First Name" className="input_field" onChange={(e) => setFirstName(e.target.value)}/>
</div>

<div>
<img src={user_icon} className="img_icon" alt='Last Name'/>
<input type="text" name="last_name" placeholder="Last Name" className="input_field" onChange={(e) => setlastName(e.target.value)}/>
</div>

<div>
<img src={email_icon} className="img_icon" alt='Email'/>
<input type="email" name="email" placeholder="email" className="input_field" onChange={(e) => setEmail(e.target.value)}/>
</div>

<div className="input">
<img src={password_icon} className="img_icon" alt='password'/>
<input name="psw" type="password" placeholder="Password" className="input_field" onChange={(e) => setPassword(e.target.value)}/>
</div>

</div>
<div className="submit_panel">
<input className="submit" type="submit" value="Register"/>
</div>
</form>
</div>
)
}

export default Register;

frontend/src/App.js 中配置路由:

1
<Route path="/register" element={<Register />} />

在之前用于运行 npm run build 的终端中,再次运行以反映最新更改。

在编辑器中打开 server/djangoproj/urls.py,在 urlpatterns 中添加以下路径。路由已在 App.js 中配置:

1
path('register/', TemplateView.as_view(template_name="index.html")),

如果已登录,请注销,然后点击 Register 链接。您将看到如下所示的注册页面。

3. 后端服务

在本模块中,您将在 Express 应用程序中实现一些与 MongoDB 进行交易的端点。然后使用 Docker 将 Mongo 和 Express 服务器容器化并运行。此外,您还将使用 Django 模型设置 CarMakeCarModel,并填充数据库。然后,将情感分析器部署到 IBM 代码引擎。最后,您将创建代理服务来访问这些外部服务。

您在上一个模块中创建的 Django 应用程序需要与数据库通信。在本模块中,您将创建一个容器化的 Node.js 应用程序,使用 MongoDB 作为后端为 API 端点提供服务。

您将在 Express 应用程序中编写这些后端服务,并使用 Docker 将其容器化。

您将查看和测试以下端点:

  • /fetchReviews/dealer/29
  • /fetchDealers
  • /fetchDealer/3
  • /fetchDealers/Kansas

3.1. 使用 Express-Mongo 实现应用程序接口端点

切换到包含数据文件的目录:

1
cd /server/database

在在线课程实验项目中,我们为您提供了两个用于 ReviewsDealerships 实体的模式文件,以及包含要加载到 MongoDB 并通过端点提供的经销商和评论数据的 JSON 文件。

  • server/database/data/dealerships.json
  • server/database/data/reviews.json

Node 应用程序将使用 mongoose 与 MongoDB 交互。评论和经销商的模式分别在 review.jsdealership.js 中定义。

查看 server/database/app.js 中的内容。它将提供以下端点:

  • fetchReviews(用于获取所有评论)
  • fetchReviews/dealer/:id(用于获取特定经销商的评论)
  • fetchDealers(用于获取所有经销商的评论)
  • fetchDealers/:state(用于获取某一州的所有经销商信息)
  • fetchDealer/:id(按 ID 查找经销商)
  • insert_review(用于插入评论)

有些端点已经为您实施。请利用这些想法和先前的学习成果,实施尚未实施的端点。

3.1.1. 与 Mongoose 合作提供 API 端点

运行以下命令来构建 Docker 应用程序。记住,每次更改 app.js 时都要这样做。

请确保你的计算机里有安装 Docker。

运行 Docker 还需要开启 Hyper-V,可以上网找教程。

1
docker build . -t nodeapp

我们创建了用于运行两个容器的 docker-compose.yml,一个用于 Mongo,另一个用于 Node 应用程序。运行以下命令来运行服务器:

1
docker-compose up

3.1.2. 创建应用程序接口端点 URL

实现 server/database/app.js 中尚未实施的以下端点(可以直接参考已经被实现的端点写法):

  • fetchDealers

    1
    2
    3
    4
    5
    6
    7
    8
    app.get('/fetchDealers', async (req, res) => {
    try {
    const documents = await Dealerships.find();
    res.json(documents);
    } catch (error) {
    res.status(500).json({ error: 'Error fetching documents' });
    }
    });
  • fetchDealers/:state

    1
    2
    3
    4
    5
    6
    7
    8
    app.get('/fetchDealers/:state', async (req, res) => {
    try {
    const documents = await Dealerships.find({state: req.params.state});
    res.json(documents);
    } catch (error) {
    res.status(500).json({ error: 'Error fetching documents' });
    }
    });
  • fetchDealer/:id

    1
    2
    3
    4
    5
    6
    7
    8
    app.get('/fetchDealer/:id', async (req, res) => {
    try {
    const documents = await Dealerships.find({id: req.params.id});
    res.json(documents);
    } catch (error) {
    res.status(500).json({ error: 'Error fetching documents' });
    }
    });

写完后,停止上一个任务中启动的 Docker 应用程序,接着再次执行 docker builddocker-compose 命令。

3030 端口中测试以下端点:

  • /fetchReviews/dealer/29
  • /fetchDealers
  • /fetchDealer/3
  • /fetchDealers/Kansas

3.2. 构建 CarModelCarMake Django 模型

您已经创建了一个经销商和与 CRUD API 相关的评论。接下来,您需要为经销商的库存创建数据模型和服务。

每个经销商都管理着不同车型和品牌的汽车库存,这些都是相对静态的数据,因此适合本地存储在 Django 中。要集成外部经销商和审查数据,您需要从 Django 应用程序调用 API,并在 Django 视图中处理 API 结果,然后通过 React 页面呈现。此类 Django 视图使用代理服务根据用户请求从外部资源获取数据,并使用 React 组件进行渲染。

在本课中,您需要执行以下任务来添加汽车模型、与汽车相关的模型和视图以及代理服务:

  • 创建 CarModelCarMake Django 模型
  • 在管理网站注册 CarModelCarMake 模型
  • 创建带有相关汽车品牌和经销商的新汽车模型对象

3.2.1. 建立 CarModelCarMake 模型的步骤

您需要在 server/djangoapp/models.py 中创建两个新模型:

  • 一个 CarMake 模型,用于保存汽车品牌的一些数据。
  • 一个 CarModel 模型,用于保存汽车模型的一些数据。

创建汽车制造商 Django 模型 class CarMake(models.Model)

  • 名称
  • 描述
  • 你想包含在汽车品牌中的任何其他字段
  • 打印汽车品牌对象的 __str__ 方法
1
2
3
4
5
6
class CarMake(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()

def __str__(self):
return self.name

创建汽车模型 Django 模型 class CarModel(models.Model)

  • CarMake 模型的多对一关系(使用外键字段,一个汽车品牌可以有多个汽车型号)
  • 经销商 ID(整数字段)指 Cloudant 数据库中创建的经销商
  • 名称
  • 类型(带选择参数的 CharField,用于提供有限的选择,如轿车、SUV 和旅行车)
  • 年份(日期字段)
  • 您希望包含在汽车模型中的任何其他字段
  • 打印汽车品牌和车型对象的 __str__ 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CarModel(models.Model):
car_make = models.ForeignKey(CarMake, on_delete=models.CASCADE) # Many-to-One relationship
name = models.CharField(max_length=100)
CAR_TYPES = [
('SEDAN', 'Sedan'),
('SUV', 'SUV'),
('WAGON', 'Wagon'),
# Add more choices as required
]
type = models.CharField(max_length=10, choices=CAR_TYPES, default='SUV')
year = models.IntegerField(default=2023,
validators=[
MaxValueValidator(2023),
MinValueValidator(2015)
])

def __str__(self):
return self.name

您需要在管理网站上注册 CarMakeCarModel,这样就可以方便地管理它们的内容(即执行 CRUD 操作):

1
2
3
4
5
6
7
# djangoapp/admin.py

from django.contrib import admin
from .models import CarMake, CarModel

admin.site.register(CarMake)
admin.site.register(CarModel)

为模型运行迁移:

1
2
python manage.py makemigrations
python manage.py migrate --run-syncdb

--run-syncdb 允许在不迁移的情况下为应用程序创建表格。

3.2.2. 在管理网站注册 CarMakeCarModel 模型的步骤

打开 djangoapp/views.py,在文件开头的其他导入语句之后导入 CarMakeCarModel,然后添加一个方法来获取汽车列表,方法中包含以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from .models import CarMake, CarModel

# ...

def get_cars(request):
count = CarMake.objects.filter().count()
print(count)
if(count == 0):
initiate()
car_models = CarModel.objects.select_related('car_make')
cars = []
for car_model in car_models:
cars.append({"CarModel": car_model.name, "CarMake": car_model.car_make.name})
return JsonResponse({"CarModels":cars})

打开 server/djangoapp/urls.py,在其中添加 get_cars 的路径:

1
path(route='get_cars', view=views.get_cars, name ='getcars'),

打开 server/djangoapp/populate.py,然后粘贴以下代码,以便在数据库中填充数据。如果 CarModel 为空,则在第一次调用 get_cars 时填充数据。如果您想手动添加数据,请跳过此步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from .models import CarMake, CarModel

def initiate():
car_make_data = [
{"name":"NISSAN", "description":"Great cars. Japanese technology"},
{"name":"Mercedes", "description":"Great cars. German technology"},
{"name":"Audi", "description":"Great cars. German technology"},
{"name":"Kia", "description":"Great cars. Korean technology"},
{"name":"Toyota", "description":"Great cars. Japanese technology"},
]

car_make_instances = []
for data in car_make_data:
car_make_instances.append(CarMake.objects.create(name=data['name'], description=data['description']))


# Create CarModel instances with the corresponding CarMake instances
car_model_data = [
{"name":"Pathfinder", "type":"SUV", "year": 2023, "car_make":car_make_instances[0]},
{"name":"Qashqai", "type":"SUV", "year": 2023, "car_make":car_make_instances[0]},
{"name":"XTRAIL", "type":"SUV", "year": 2023, "car_make":car_make_instances[0]},
{"name":"A-Class", "type":"SUV", "year": 2023, "car_make":car_make_instances[1]},
{"name":"C-Class", "type":"SUV", "year": 2023, "car_make":car_make_instances[1]},
{"name":"E-Class", "type":"SUV", "year": 2023, "car_make":car_make_instances[1]},
{"name":"A4", "type":"SUV", "year": 2023, "car_make":car_make_instances[2]},
{"name":"A5", "type":"SUV", "year": 2023, "car_make":car_make_instances[2]},
{"name":"A6", "type":"SUV", "year": 2023, "car_make":car_make_instances[2]},
{"name":"Sorrento", "type":"SUV", "year": 2023, "car_make":car_make_instances[3]},
{"name":"Carnival", "type":"SUV", "year": 2023, "car_make":car_make_instances[3]},
{"name":"Cerato", "type":"Sedan", "year": 2023, "car_make":car_make_instances[3]},
{"name":"Corolla", "type":"Sedan", "year": 2023, "car_make":car_make_instances[4]},
{"name":"Camry", "type":"Sedan", "year": 2023, "car_make":car_make_instances[4]},
{"name":"Kluger", "type":"SUV", "year": 2023, "car_make":car_make_instances[4]},
]

for data in car_model_data:
CarModel.objects.create(name=data['name'], car_make=data['car_make'], type=data['type'], year=data['year'])

如果您想手动添加汽车品牌和型号,也可以进入管理页面,根据自己的意愿添加。请注意,在项目后期,您只能从这些表格中选择一个品牌和车型发表评论。

进入 127.0.0.1:8000/djangoapp/get_cars 来查看已添加的汽车列表。

3.3. 创建后台 API 的 Django 代理服务

在之前的实验中,您创建了 CarModelCarMake 的 Django 模型,这些模型驻留在本地 SQLite 存储库中。您还获得了由 Express API 端点提供服务的经销商和评论模型 Mongo DB。

现在,您需要集成这些模型和服务,以管理经销商和评论等所有实体。

要集成外部经销商和评论数据,您需要从 Django 应用程序调用 API 端点,并在 Django 视图中处理 API 结果。这些 Django 视图可以看作是终端用户的代理服务,因为它们会根据用户的请求从外部资源获取数据。

在本实验中,您将创建此类 Django 视图作为代理服务。

3.3.1. 运行 Mongo 服务器

后端 Mongo Express 服务器需要在实验室环境中的一个终端启动并运行。在这一阶段,服务器代码已经实现了所有的终端点。运行 docker-compose up 来启动服务器(端口 3030)。

打开 djangoapp/.env,将您的后台网址替换为上一步在记事本中复制的后台网址:

确保末尾的 / 没有被复制。

1
backend_url=your backend url

3.3.2. 创建与后端交互的函数

在之前的实验中,您已经创建了用于 fetchReviewsfetchDealers 的 API 端点。现在实现一个方法来从 Django 应用程序访问这些端点。

在 Django 中,有许多方法可以进行 HTTP 请求。这里我们使用一个非常流行且易于使用的 Python 库,名为 requests

打开 djangoapp/restapis.py,添加 get_request 方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_request(endpoint, **kwargs):
params = ""
if(kwargs):
for key,value in kwargs.items():
params=params+key+"="+value+"&"

request_url = backend_url+endpoint+"?"+params

print("GET from {} ".format(request_url))
try:
# Call get method of requests library with URL and parameters
response = requests.get(request_url)
return response.json()
except:
# If any error occurs
print("Network exception occurred")

3.3.3. 启动 Code Engine

以下为 Code Engine 相关。该课程采用的是 IBM 的 Skills Network 实验环境,Code Engine 被直接嵌入在这个实验环境中。

创建一个项目,启动 Code Engine。Code Engine 环境需要一段时间来准备。您将在设置面板中看到进度状态。

Code Engine 设置完成后,您可以看到它已激活。单击 Code Engine CLI,在下面的终端中开始预配置的 CLI。

3.3.4. 将情感分析作为微服务部署在 Code Engine 上

在 Code Engine CLI 中,更改为 server/djangoapp/microservices 目录:

1
cd xrwvm-fullstack_developer_capstone/server/djangoapp/microservices

我们为您提供了使用 NLTK 进行情感分析的 sentiment_analyzer.py。我们还为您提供了一个 Dockerfile,您将使用它在代码引擎中部署此服务,并将其作为微服务使用。

运行以下命令,用 Docker 构建情感分析应用程序(请注意,代码引擎实例是瞬时的,并附在你的实验室空间用户名上):

1
docker build . -t us.icr.io/${SN_ICR_NAMESPACE}/senti_analyzer

运行以下命令推送 Docker 镜像:

1
docker push us.icr.io/${SN_ICR_NAMESPACE}/senti_analyzer

在 Code Engine 上部署 senti_analyzer 应用程序:

1
ibmcloud ce application create --name sentianalyzer --image us.icr.io/${SN_ICR_NAMESPACE}/senti_analyzer --registry-secret icr-secret --port 5000

连接到生成的 URL 以访问微服务,并检查部署是否成功。如果应用程序部署验证成功,请在浏览器中将 /analyze/Fantastic 服务附加到 URL,查看是否返回正值。

打开 djangoapp/.env,将 Code Engine 部署 URL 替换为上文获得的部署 URL:

1
sentiment_analyzer_url=your code engine deployment url

URL 最后的 / 要去掉。

如果想要在本地环境中部署该微服务,可以进入 /server/djangoapp/microservices 目录,启动 Docker:

1
2
docker build -t sentiment_app .
docker run -p 5050:5050 my_microservice

然后要在 .env 文件中注释掉 sentiment_analyzer_url 一行。

更新 djangoapp/restapis.py,并在其中添加以下函数,以使用微服务分析情感:

1
2
3
4
5
6
7
8
9
def analyze_review_sentiments(text):
request_url = sentiment_analyzer_url+"/analyze/"+text
try:
# Call get method of requests library with URL and parameters
response = requests.get(request_url)
return response.json()
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
print("Network exception occurred")

3.3.5. 创建 Django 视图以获取经销商

使用以下代码更新 djangoapp/views.py 中的 get_dealerships 视图方法。它将使用您在 restapis.py 中通过 /fetchDealers 端点实现的 get_request

1
2
3
4
5
6
7
def get_dealerships(request, state="All"):
if(state == "All"):
endpoint = "/fetchDealers"
else:
endpoint = "/fetchDealers/"+state
dealerships = get_request(endpoint)
return JsonResponse({"status":200,"dealers":dealerships})

url.py 中的 get_dealerships 视图方法配置路由:

1
2
path(route='get_dealers', view=views.get_dealerships, name='get_dealers'),
path(route='get_dealers/<str:state>', view=views.get_dealerships, name='get_dealers_by_state'),

views.py 中创建一个以 dealer_id 为参数的 get_dealer_details 方法,并添加一个映射 urls.py。它将使用你在 restapis.py 中实现的 get_request,并传递 /fetchDealer/<dealer id> 端点。

1
2
3
4
5
6
7
def get_dealer_details(request, dealer_id):
if(dealer_id):
endpoint = "/fetchDealer/"+str(dealer_id)
dealership = get_request(endpoint)
return JsonResponse({"status":200,"dealer":dealership})
else:
return JsonResponse({"status":400,"message":"Bad Request"})

同样配置路由:

1
path(route='dealer/<int:dealer_id>', view=views.get_dealer_details, name='dealer_details'),

views.py 中创建以 dealer_id 为参数的 get_dealer_reviews 方法,并添加 urls.py 映射。它将使用在 restapis.py 中实现的 get_request,并传递 /fetchReviews/dealer/<dealer id> 端点。它还将调用 restapis.py 中的 analyze_review_sentiments,以消费微服务并确定每条评论的情感,然后在 review_detail 字典中设置值,并作为 JsonResponse 返回。

情感属性的值将由情感分析微服务决定。它可以是正面的、中性的或负面的。

1
2
3
4
5
6
7
8
9
10
11
12
def get_dealer_reviews(request, dealer_id):
# if dealer id has been provided
if(dealer_id):
endpoint = "/fetchReviews/dealer/"+str(dealer_id)
reviews = get_request(endpoint)
for review_detail in reviews:
response = analyze_review_sentiments(review_detail['review'])
print(response)
review_detail['sentiment'] = response['sentiment']
return JsonResponse({"status":200,"reviews":reviews})
else:
return JsonResponse({"status":400,"message":"Bad Request"})
1
path(route='reviews/dealer/<int:dealer_id>', view=views.get_dealer_reviews, name='dealer_details'),

3.3.6. 创建 Django 视图以发布经销商评论

你已经学会了如何进行各种 GET 调用,现在来创建 Django 视图以发布经销商评论:打开 restapis.py,添加一个 post_review 方法,该方法将接收一个数据字典,并在后台调用 add_review。字典将以键值对的形式接收经销商评论所需的所有值。

1
2
3
4
5
6
7
8
def post_review(data_dict):
request_url = backend_url+"/insert_review"
try:
response = requests.post(request_url,json=data_dict)
print(response.json())
return response.json()
except:
print("Network exception occurred")

打开 views.py,创建一个新的 def add_review(request): 方法来处理评论文章请求。在 add_review 视图方法中:

  • 首先检查用户是否通过身份验证,因为只有通过身份验证的用户才能为经销商发布评论。
  • 使用字典调用 post_request 方法。
  • post_request 的结果返回 add_review 视图方法。您可以打印帖子响应。
  • 以 JSON 格式返回成功状态和信息。
  • url.py 中为 add_review 视图配置路由。
1
2
3
4
5
6
7
8
9
10
def add_review(request):
if(request.user.is_anonymous == False):
data = json.loads(request.body)
try:
response = post_review(data)
return JsonResponse({"status":200})
except:
return JsonResponse({"status":401,"message":"Error in posting review"})
else:
return JsonResponse({"status":403,"message":"Unauthorized"})
1
path(route='add_review', view=views.add_review, name='add_review'),

restapis.py 导入方法,以便在 views.py 中使用:

1
from .restapis import get_request, analyze_review_sentiments, post_review

4. 应用程序:动态页面

在本模块中,您将使用 React 组件添加动态页面,以列出经销商、按州过滤经销商、查看经销商详细信息并添加经销商评论。

  • 创建前台页面,向终端用户展示后台服务。
  • 创建一个组件来列出经销商。
  • 开发一个经销商详情和评论组件。
  • 创建评论提交页面。

您在上一个模块中创建了所有必要的后台服务(Django 视图和 API 端点),用于管理经销商、评论和汽车。接下来,是时候创建一些风格化的前端 React 页面来向终端用户展示这些服务结果了。在本学习模块中,您需要执行以下任务将前端添加到应用程序中:

  • 创建一个经销商组件,列出所有经销商
  • 创建一个经销商详细信息组件,显示特定经销商的评论
  • 创建评论提交页面

4.1. 为经销商页面添加和设置 React 组件

打开 frontend/src/App.js,导入 Dealer 组件并将其与其他组件一起添加到顶部。该组件已为您创建,并使用表格元素列出经销商。您可以随意更改外观和感觉。

1
import Dealers from './components/Dealers/Dealers';

/dealers 添加路由,以呈现 Dealers 组件:

1
<Route path="/dealers" element={<Dealers/>} />

打开 server/djangoproj/urls.py,在其中添加 DealersDealer 的路由:

1
path('dealers/', TemplateView.as_view(template_name="index.html")),

打开一个新终端,像以前一样创建前端。进入 127.0.0.1:8000/dealers 测试 get_dealers 视图。

4.2. 添加 React 组件 Dealer 显示评论

打开 frontend/src/App.js,导入 Dealer React 组件的路由并将其添加到其他路由中。这样,当您点击经销商表上的链接时,就会呈现一个经销商特定的 React 页面以及评论。

该页面还有一个链接,可以让已登录的用户发表评论。您将在下一个任务中添加发布评论页面。

1
import Dealer from "./components/Dealers/Dealer"
1
<Route path="/dealer/:id" element={<Dealer/>} />

在之前创建前端的同一终端再次创建前端。

打开 server/djangoproj/urls.py,加入以下代码,添加显示经销商页面的路径:

1
path('dealer/<int:dealer_id>',TemplateView.as_view(template_name="index.html")),

刷新应用程序,进入 View Reviews 页面,点击任何经销商的名称链接,查看其评论。

4.3. 创建经销商详情或评论页面

通过身份验证的用户应能点击该链接并为经销商添加评论。我们将添加一个评论提交页面。

打开并查看 frontend/src/components/Dealers/PostReview.jsx。根据需要对外观和感觉进行修改。

导入 PostReview 组件,并在 frontend/src/App.js 中添加 postreview/<dealer id> 路由。

1
import PostReview from "./components/Dealers/PostReview"
1
<Route path="/postreview/:id" element={<PostReview/>} />

转到构建前端的终端,再次运行构建。

打开 server/djangoprojs/urls.py,在其中加入以下代码,以添加审阅后页面的路径:

1
path('postreview/<int:dealer_id>',TemplateView.as_view(template_name="index.html")),

使用已注册的用户名和密码登录。您将看到 Post Review 链接。向经销商添加评论,测试该链接。

5. CI/CD、容器化和部署到 Kubernetes

在本模块中,您将为您创建的所有 JavaScript 和 Python 文件设置一个 CI/CD 操作流程。然后,您将运行所有服务器端组件,包括 Docker 容器中的 Express-Mongo 服务器和代码引擎上的情感分析器无服务器部署。最后,您将构建前端 React 应用程序,并在 Kubernetes 上部署 Django 应用程序。

  • 配置 Django 应用程序并将其部署到 Kubernetes。

恭喜您在添加静态页面和用户管理后测试了应用程序。下一步是为源代码设置持续集成和持续交付(CI/CD)。如果有多人参与项目,这一点尤为重要。持续集成(CI)为开发人员提供了一种协作方式,而持续交付(CD)则提供了一种不间断地向客户交付变更的方式。在本模块中,您将:

  • 了解所提供的 GitHub 工作流程(workflow)模板中的工作流程
  • 了解所提供的 GitHub 工作流程模板中的 Linting 作业
  • 启用 GitHub Actions 并运行 Linting 工作流

Linting 是一种自动检查源代码中是否存在编程和样式错误的方法,通常通过使用 Lint 工具(也称为 Linter)来实现。Lint 工具是一种基础的静态代码分析器。

5.1. 添加持续集成和持续部署功能

您的团队正在壮大!管理层决定招聘前端和后端工程师,以确保路线图上的功能能在未来版本中及时开发出来。然而,这意味着多名工程师需要在版本库上并行工作。您的任务是确保推送到主分支的代码符合团队的编码风格,并且没有语法错误。

在本实验中,你将为版本库添加语法检查功能,在开发人员创建拉取请求或将分支合并到默认主分支时自动检查语法错误。在开始实验之前,我们先来了解一下 GitHub Actions。

5.1.1. GitHub Actions

GitHub Actions 提供了一种事件驱动的方式来自动执行项目中的任务。您可以监听多种事件。下面是几个例子:

  • push:当有人向版本库分支(branch)推送时运行任务。
  • pull_request:当有人创建拉取请求(PR)时运行任务。您还可以在某些活动发生时启动任务,例如:
    • PR 已打开
    • PR 已关闭
    • PR 被重新打开
  • create:当有人创建分支或标记时运行任务。
  • delete:当有人删除分支或标记时运行任务。
  • manually:手动启动任务。

在本实验中,您将使用以下一个或多个组件:

  • Workflows(工作流):您可以添加到存储库的作业集合。
  • Events:启动工作流的活动。
  • Jobs(作业):一个或多个步骤的序列。作业默认并行运行。
  • Steps:可在作业中运行的单个任务。一个步骤可以是一个操作或命令。
  • Actions:工作流的最小模块。

下面为您提供了一个工作流程模板。让我们来研究一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
name: 'Lint Code'
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
jobs:
lint_python:
name: Lint Python Files
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.12
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
- name: Print working directory
run: pwd
- name: Run Linter
run: |
pwd
# This command finds all Python files recursively and runs flake8 on them
find . -name "*.py" -exec flake8 {} +
echo "Linted all the python files successfully"
lint_js:
name: Lint JavaScript Files
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 14
- name: Install JSHint
run: npm install jshint --global
- name: Run Linter
run: |
# This command finds all JavaScript files recursively and runs JSHint on them
find ./server/database -name "*.js" -exec jshint {} +
echo "Linted all the js files successfully"
  1. 第一行命名工作流程。
  2. 下一行定义了工作流的运行时间。工作流应在开发人员向主分支推送变更或创建 PR 时运行。这两种方式的捕获方式如下:
    • 推送到主分支(主分支或主分支)时运行:
      1
      2
      push:
      branches: [master, main]
    • 在主分支(主分支或主分支)上创建 PR 时运行:
      1
      2
      pull_request:
      branches: [master, main]
  3. 然后,您将定义所有工作。本工作流程中有两个任务:
    • lint_python: 检查 Python 函数
    • lint_js: 检查 JavaScript 函数

5.1.2. GitHub Jobs

让我们逐一看看这些作业:

  1. lint_python

    • 使用 actions/setup-python@v4 操作设置该操作的 Python 运行时。
    • 使用 pip install 安装所有依赖项。
    • 在服务器目录下的所有文件中递归运行 Linting 命令 flake8 *.py
    • 打印一条提示信息,说明 Linting 已成功完成。
  2. lint_function_js

    • 使用 actions/setup-node@v3 操作设置要运行的 Node.js 运行时。
    • 安装所有 JSHint 内核 npm install jshint
    • 在数据库目录中的所有 .js 文件上递归运行 Linting 命令。
    • 打印一条提示信息,说明 Linting 已成功完成。

5.1.3. 启动 GitHub Actions

要启用 GitHub Actions,请登录 GitHub 并打开已 Fork 的仓库。然后,转到 Actions 选项卡,点击 Set up a workflow yourself。将上述 Lint 代码粘贴到 main.yml 中并提交。再次打开 Actions 选项卡,就会看到提交已自动启动了检查工作流程。

您可以单击工作流程运行,查看单个作业和每个作业的日志。工作流程成功完成后,你会看到绿色的 ,表示工作顺利。红叉表示在检查代码时发现了错误。

查看这些提示,解决你可能遇到的常见 Linting 错误。

  1. Flake-8 Lint(Python)Linting 错误:

    1. 如果收到下列一个或多个错误:

      1
      2
      E117 over-indented
      E128 continuation line under-indented for visual indent

      解决方法:

      • 确认所有代码都保持适当的缩进——既不会缩进过小,也不会缩进过大。
      • 注意:使用文本编辑器确保准确执行。
    2. 1
      E501 line too long (xxx > 79 characters)

      解决办法:

      • 将代码分成多行,确保每行最多不超过 79 个字符。
    3. 1
      F401 'xxx' imported but unused

      解决方法:

      • 核实后续代码段中是否使用了提及的实体或变量(xxx)。如果未使用实体或变量,则删除包含该实体或变量的行。

      这里有个小坑,djangoapp/views.py 文件中每个函数中的 request 参数绝对不能删除,建议在当前函数内 print 一下 request 参数来回避该错误。部分不能被删除但同时也没被用到的变量同理。

    4. 1
      W292 no newline at end of file

      解决方法:

      • 在文件的最终代码后插入新行,并将光标置于垂直窗格的最左侧(不向右缩进)。
    5. 1
      E302 expected 2 blank lines, found 1

      解决方案:

      • 确保每对相邻函数之间正好有两行空行(不能多也不能少)。
    6. 1
      E231 missing whitespace after ':'

      解决方法:

      • 确保在所有字典键值对中的分号后留一个空格。
      • 例如,如果现有代码为 "a":"b",请将其更改为 "a": "b"
    7. 1
      E275 missing whitespace after keyword

      解决办法:

      • 确保每个关键字后都有一个空格。
      • 例如,如果您的现有代码是 if("condition"):,请将其改为 if ("condition"):
    8. 1
      E722 do not use bare 'except'

      解决方法:

      • 使用 except Exception: 而不是 except 作为捕获异常和全面处理异常的最佳实践。
      • 例如,如果现有代码是:
        1
        2
        except:
        print("Error")
        您可以将其改为:
        1
        2
        except Exception as e:
        print(f"Error: {e}")
  2. JS Hint(JavaScript)Linting 错误:

    1. 如果收到下列一个或多个错误:

      1
      2
      3
      4
      5
      'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
      'arrow function syntax (=>)' is only available in ES6 (use 'esversion: 6').
      'async functions' is only available in ES8 (use 'esversion: 8').
      'let' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
      'template literal syntax' is only available in ES6 (use 'esversion: 6').

      解决方法:

      • 在报告此错误的文件开头添加以下一行:
        1
        /*jshint esversion: 8 */
    2. 1
      ['xxxxxx'] is better written in dot notation.

      解决方法:

      • 当字典或 JSON 键值对的格式为 key[value] 时,就会出现这个问题。要解决这个问题,请将格式切换为 key.value

5.2. 容器化并部署到 Kubernetes

根据最新的技术发展趋势,并为了避免供应商锁定,贵公司的管理团队希望将经销商应用程序部署到多个云中。该应用程序目前在 Code Engine 上运行,但您被告知并非所有云提供商都提供托管 Code Engine 服务。所有大型云提供商都有托管和管理容器的方法,因此您被要求将容器作为缓解这一问题的可能方法。在对应用程序进行容器化时,过程包括将应用程序与其相关的环境变量、配置文件、库和软件依赖关系打包。其结果是一个容器映像,可以在容器平台上运行。您还需要使用 Kubernetes 来管理容器化部署。

Kubernetes 是一个开源容器编排平台,可以自动部署、管理和扩展应用程序。

在本模块中,您将:

  • 为应用程序添加在容器中运行的功能
  • 为应用程序添加部署工件,以便 Kubernetes 对其进行管理

注意:在开始实验之前,请按照步骤检查和删除以前持续存在的会话,以避免在运行实验时出现任何问题。

请运行以下命令:

1
kubectl get deployments

如果您发现经部署 dealership 已经存在,请删除它:

1
kubectl delete deployment dealership

请运行以下命令:

1
ibmcloud cr images

如果有任何 dealership 镜像,请使用删除它:

1
ibmcloud cr image-rm us.icr.io/<your sn labs namespace>/dealership:latest && docker rmi us.icr.io/<your sn labs namespace>/dealership:latest

请输入您的 SN Labs 命名空间,以代替 <your sn labs namespace>。(SN Labs 命名空间是 IBM Skills Network 内置的)

如果您不记得自己的命名空间,可以使用以下任一命令获取:

  • oc 项目
  • ibmcloud cr 命名空间(请使用格式为 sn-labs-$USERNAME 的命名空间)
  • 请退出 SN Labs 并清除浏览器缓存和 cookie。
  • 请重新启动实验室并按以下步骤操作。

5.2.1. 设置环境

  1. 如果实验室环境已重置,请克隆您的 Git 仓库。
  2. 打开一个新终端,切换到 server/database 目录,然后按照之前的操作运行 Mongo Express 服务器。
  3. 如果部署在 Code Engine 上的情感分析器微服务不可用,请重新部署并更新所需的 URL。
  4. 打开另一个新终端。切换到 server/frontend 目录。运行以下命令创建前端:
    1
    2
    npm install
    npm run build

5.2.2. 添加 Dockerfile

server 目录下创建一个 Dockerfile。文件中应列出以下步骤:

  1. 添加基础镜像。
  2. 添加 requirements.txt 文件。
  3. 安装并更新 Python。
  4. 更改工作目录。
  5. 公开端口。
  6. 运行命令启动应用程序。

下面是一个示例文件,供您开始使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FROM python:3.12.0-slim-bookworm

ENV PYTHONBUFFERED 1
ENV PYTHONWRITEBYTECODE 1

ENV APP=/app

# Change the workdir.
WORKDIR $APP

# Install the requirements
COPY requirements.txt $APP

RUN pip3 install -r requirements.txt

# Copy the rest of the files
COPY . $APP

EXPOSE 8000

RUN chmod +x /app/entrypoint.sh

ENTRYPOINT ["/bin/bash","/app/entrypoint.sh"]

CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "djangoproj.wsgi"]

请注意,Dockerfile 中倒数第二个命令指的是 entrypoint.sh。在 server 目录中创建该文件。该文件应包含以下内容:

1
2
3
4
5
6
7
8
#!/bin/sh

# Make migrations and migrate the database.
echo "Making migrations and migrating the database. "
python manage.py makemigrations --noinput
python manage.py migrate --run-syncdb --noinput
python manage.py collectstatic --noinput
exec "$@"

5.2.3. 构建镜像并将其推送到容器注册表

您必须记住如何构建镜像并将其推送到 IBM Cloud Image Registry (ICR)。您需要在这里执行相同的操作,然后在 Kubernetes 部署文件中引用该镜像。

请导出 SN Labs 命名空间,并在控制台中打印出来,如下所示:

1
2
MY_NAMESPACE=$(ibmcloud cr namespaces | grep sn-labs-)
echo $MY_NAMESPACE

使用当前目录下的 Dockerfile 执行 docker 构建:

1
docker build -t us.icr.io/$MY_NAMESPACE/dealership .

接下来,将镜像推送到容器注册表:

1
docker push us.icr.io/$MY_NAMESPACE/dealership

5.2.4. 添加部署工件

server 目录下创建 deployment.yaml 文件,以创建部署和服务。文件应如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: dealership
name: dealership
spec:
replicas: 1
selector:
matchLabels:
run: dealership
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
run: dealership
spec:
containers:
- image: us.icr.io/your-name-space/dealership:latest
imagePullPolicy: Always
name: dealership
ports:
- containerPort: 8000
protocol: TCP
restartPolicy: Always

请在上述文件中输入您的 SN Labs 命名空间,以代替 your-name-space

5.2.5. 部署应用程序

使用以下命令和部署文件创建部署:

1
kubectl apply -f deployment.yaml

通常,我们会在部署中添加一个服务;不过,我们将在此环境中使用端口转发功能来查看正在运行的应用程序:

1
kubectl port-forward deployment.apps/dealership 8000:8000

注意:如果出现任何错误,请稍等片刻,然后重新运行该命令。