Java程序员马意浓在互联网公司维护老旧电商后台系统。
渴望学习新技术的他在工作中无缘Docker和K8s。
他开始自学Vue3并使用SpringBoot3完成了一个前后端分离的Web应用系统,并打算将其用Docker容器化后用K8s上云。
在k8s中解决前后端分离的web应用的CORS问题的思路清楚了。
马意浓接下来要面对的,是如何实现这个思路。
马意浓在网上找不到在k8s中部署前后端web应用时deployment配置和service配置的现成代码样例,所以他只能靠不断问AIGC,一点点尝试和摸索。
这时,昔日的同事全绽园的微信蹦了出来。
「意浓,我在朋友圈里看到你发的求助帖了。你打算怎么解决?」
「我也没想好。要是你有兴趣,后天中午咱们远程视频一下?」
「好的。」
马意浓为了能把所面临的问题,跟全绽园说清楚,在接下来的时间里,就仔细梳理所编写的配置和思路。
到了第三天早上,他还没有梳理完,就跟全绽园说,原订中午的视频会议推迟。等他梳理完了,再喊他。
到了第五天晚上,马意浓凭借仔细梳理配置和思路,以及AIGC的帮助,竟然自己把问题解决了!
他双手握拳,兴奋地高喊:「欧耶!」
他迫不及待地给全绽园发微信:「已经把ingress nginx controller配置对了!那个CORS问题搞定了!😃」
过了一会儿,全绽园回复:「恭喜啊!👏」
「为了把问题给你讲清楚,仔细研究了一下问题,结果就找到解决方案了。😂」
「哈哈!我幸运地充当了小黄鸭。🐥」
马意浓很开心自己能有这么一位乐于助人的好兄弟。他高兴地发了一条朋友圈。
「亲历了一次小黄鸭调试法。刚知道他们管这叫小黄鸭。」
✅「小黄鸭调试法(Rubber Duck Debugging),是一种常用的调试方法。」
「当你遇到问题无法解决时,尝试对着一只橡皮鸭子逐步讲解。通常在讲解问题的过程中,你会得到解决方案。」如图1。
马意浓趁着现在思路清晰,赶紧记录了一些重要的笔记。
他在vscode中,用PlantUML插件,画了这个前后端分离的web应用部署到k8s中的架构图。如图2。
马意浓把这次web应用成功部署到k8s的所有代码改动,推送到github的wubin28账号下的shopping-list-web-app-2024-for-wsl2代码库中的for-docker-desktop-k8s分支里。
通过代码对比,他在笔记中记录了这次代码改动的三部分内容。
📙在back-end文件夹的ShoppingListApplicationConfig.java文件中,将.allowedOrigins("[hxxp://localhost:8080](<http://localhost:8080/>)")
,改为.allowedOrigins(System.getenv("ALLOWED_ORIGIN"))
。
其中环境变量ALLOWED_ORIGIN
的值,在deployment-shopping-list-api.yml文件中配置。
📙在back-end文件夹的application.properties文件中,将spring.datasource.url=jdbc:postgresql://localhost:5432/shoppingList
,改为spring.datasource.url=jdbc:postgresql://${DB_HOST}:5432/shoppingList
。
其中环境变量${DB_HOST}
的值,也在deployment-shopping-list-api.yml文件中配置。
📙在front-end文件夹的ShoppingList.vue文件中,将[hxxp://localhost:8081](<http://localhost:8081/>)
,改为%%API_URL%%
。
其中环境变量%%API_URL%%
的值,在deployment-shopping-list-front-end.yml文件中配置。
📙为了让环境变量%%API_URL%%
在前端app中生效,对front-end文件夹中的Dockerfile也做了一些改动。另外也增加了entrypoint.sh文件。
在infrastructure文件夹中增加了postgres、shopping-list-api和shopping-list-front-end这三个微服务的deployment和service配置文件。
最后增加了ingress nginx controller的配置文件ingress.yml。
deployment-postgres.yml
service-postgres.yml
deployment-shopping-list-api.yml
service-shopping-list-api.yml
deployment-shopping-list-front-end.yml
service-shopping-list-front-end.yml
ingress.yml
马意浓在笔记上,将之前运行过的命令记录下来,以便以后再次运行。
# check out the branch with updated code for k8s
git clone hxxps://github.com/wubin28/shopping-list-web-app-2024-for-wsl2.git
git checkout for-docker-desktop-k8s
# start up db
cd ../infrastructure
docker compose up postgres pgadmin
# generate the jar
cd ../back-end
export DB_HOST=localhost
./gradlew clean build
# build back-end docker image
docker buildx build --build-arg JAR_FILE=build/libs/shoppinglist-0.0.1-SNAPSHOT.jar -t <docker-hub-username>/shopping-list-api:v1.1.local.k8s .
docker image ls
# push the back-end docker image to docker hub
docker push <docker-hub-username>/shopping-list-api:v1.1.local.k8s
# 若将来后端app的v1.1.local.k8s版本出现问题时,为便于查看所对应的代码,运行命令在git代码库打同名tag
git tag -a v1.1.local.k8s -m "v1.1.local.k8s"
cd ../front-end
docker buildx build --platform linux/amd64 -t <docker-hub-username>/shopping-list-front-end:v1.1.local.k8s.amd64 .
docker image ls
docker inspect <docker-hub-username>/shopping-list-front-end:v1.1.local.k8s.amd64 | grep "Architecture"
# push the front-end docker image to docker hub
docker push <docker-hub-username>/shopping-list-front-end:v1.1.local.k8s.amd64
与在Ubuntu中用sdkman安装不同版本的jdk一样,在k8s中,可以使用包管理器helm安装ingress-nginx。
在命令中使用$NAMESPACE的好处,是将来清理现场时,能方便地用一行命令,来删除之前在k8s上创建的所有与购物清单web应用相关的资源。
# run the web app on k8s
export NAMESPACE=shopping-list-web-app
kubectl create namespace $NAMESPACE
# install package manager helm on k8s. please enter password when installing it
curl hxxps://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] <https://baltocdn.com/helm/stable/debian/> all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
# install ingress-nginx using helm
helm repo add ingress-nginx-repo <https://kubernetes.github.io/ingress-nginx>
helm repo update
helm install ingress-nginx-release ingress-nginx-repo/ingress-nginx \\
-n $NAMESPACE \\
--set controller.service.annotations."service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path"=/healthz
# check the installed ingress-nginx in helm
helm list -n $NAMESPACE
# check the external ip of the ingress-nginx-release-controller
kubectl get services -o wide -n $NAMESPACE
# watch the status of the ingress-nginx-release-controller
kubectl get service -n $NAMESPACE ingress-nginx-release-controller --output wide --watch
cd ../infrastructure
# 部署postgres的deployment
kubectl apply -f ./deployment-postgres.yml --namespace $NAMESPACE
# 验证image是否正确:
kubectl get deployments -o wide -n $NAMESPACE
# 验证pod是否正常启动
kubectl get pods -o wide -n $NAMESPACE
# 如果pod启动异常查看出错信息
kubectl describe pod <pod name, such as first-pod> -n $NAMESPACE
# 如果pod启动异常查看最后一个container的出错信息
kubectl logs <pod name, such as first-pod> --previous -n $NAMESPACE
# 部署postgres的service
kubectl apply -f ./service-postgres.yml --namespace $NAMESPACE
# 验证服务是否正常启动
kubectl get services -o wide -n $NAMESPACE
# 部署shopping-list-api的deployment
kubectl apply -f ./deployment-shopping-list-api.yml --namespace $NAMESPACE
# 验证image是否正确
kubectl get deployments -o wide -n $NAMESPACE
# 验证pod是否正常启动
kubectl get pods -o wide -n $NAMESPACE
# 部署shopping-list-api的service
kubectl apply -f ./service-shopping-list-api.yml --namespace $NAMESPACE
# 验证服务是否正常启动
kubectl get services -o wide -n $NAMESPACE
# 部署shopping-list-front-end的deployment
kubectl apply -f ./deployment-shopping-list-front-end.yml --namespace $NAMESPACE
# 验证image是否正确
kubectl get deployments -o wide -n $NAMESPACE
# 验证pod是否正常启动
kubectl get pods -o wide -n $NAMESPACE
# 部署shopping-list-front-end的service
kubectl apply -f ./service-shopping-list-front-end.yml --namespace $NAMESPACE
# 验证服务是否正常启动
kubectl get services -o wide -n $NAMESPACE
# 部署ingress
kubectl apply -f ./ingress.yml --namespace $NAMESPACE
# 查看ingress的状态
kubectl get ingresses -n $NAMESPACE
# 查看ingress的详情
kubectl describe ingress shopping-list-ingress -n $NAMESPACE
# Check external ip and port of ingress nginx controller: localhost
kubectl get services -o wide -n $NAMESPACE
等运行完上面的命令,将web应用部署到k8s后,马意浓终于可以打开浏览器,要在k8s上使用购物清单web应用了。
他在浏览器地址栏输入localhost
,按下回车。
网页显示出购物清单页面。他添加了一个购物项a banana,并按Add按钮。
但这个购物项还是没有如愿显示在屏幕下方。
他打开浏览器的Developer Tools,发现CORS错误又出现了。
Access to XMLHttpRequest at 'hxxp://20.72.130.209/api/v1/shopping-items' from origin 'hxxp://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
他在出错信息中,看到了20.72.130.209这个地址。这是他以前在某个云厂商那里试用k8s时配置上的。
现在,他已经把前后端分离的web应用,部署到本地Docker Desktop里,所以这个IP地址,应该改为localhost
。
他查看了一下代码,先把deployment-shopping-list-front-end.yml文件中的API_URL值,改为localhost
。
然后他又把deployment-shopping-list-api.yml文字中的ALLOWED_ORIGIN的值,改为localhost
。
因为改了配置文件,所以需要重新apply这两个deployment配置。
不过,相对应的service配置因为没有改变,所以无须重新apply。
从这一点,就能看出k8s将微服务的部署分为deployment配置和service配置这两步的好处。
# 部署shopping-list-api的deployment
kubectl apply -f ./deployment-shopping-list-api.yml --namespace $NAMESPACE
# 验证image是否正确
kubectl get deployments -o wide -n $NAMESPACE
# 验证pod是否正常启动
kubectl get pods -o wide -n $NAMESPACE
# 部署shopping-list-front-end的deployment
kubectl apply -f ./deployment-shopping-list-front-end.yml -n $NAMESPACE
# 验证image是否正确
kubectl get deployments -o wide -n $NAMESPACE
# 验证pod是否正常启动
kubectl get pods -o wide -n $NAMESPACE
运行完这些命令后,马意浓又在浏览器地址栏输入localhost
,按下回车。
CORS错误依旧显示出来。
✅之前的经验告诉他,这应该是浏览器缓存在捣乱。
他把Chrome浏览器的cache清除了一下。然后再次刷新网页。
🎉🎉🎉这次,CORS错误消失了!购物清单web应用终于在k8s里成功运行!
马意浓没有忘记,在成功部署web应用后,他又运行kubectl命令,回顾了一下context、cluster、node、pod和container这些k8s的概念。
# check out the k8s concepts
# contexts
kubectl config get-contexts
kubectl config current-context
# clusters
kubectl config get-clusters
# nodes
kubectl get nodes
kubectl describe nodes
# pods
kubectl get pods -n <namespace>
kubectl get pods --all-namespaces
# containers
# Kubectl itself doesn't provide a direct way to list all containers across all pods, but you can use it to get pod details, which include container information. For a specific pod:
kubectl describe pod <pod-name> -n <namespace>
因为之前在k8s里,执行为购物清单web应用创建资源的kubectl命令,都带有-n $NAMESPACE
参数,所以在清理现场时,马意浓就可以用一条命令删除所有相关资源。
在运行那行删除资源的命令之前和之后,他还没忘了运行了查看资源的命令,以验证资源真的被删除干净了。
# check all resources in the namespace before tearing down
kubectl get namespace $NAMESPACE
kubectl api-resources --verbs=list --namespaced=true -o name | xargs -n 1 kubectl get -n $NAMESPACE
# Tear down
kubectl delete namespace $NAMESPACE
# make sure all resources in the namespace have been deleted
kubectl get namespace $NAMESPACE
kubectl api-resources --verbs=list --namespaced=true -o name | xargs -n 1 kubectl get -n $NAMESPACE
当最终把前后端分离的web应用成功部署到Docker Desktopk8s集群上,并能顺利使用后,马意浓把整个容器化上云之旅,写成系列文章,分享给其他程序员,作为对包括全绽园在内的所有帮过他的人的感谢。🙏🙏🙏
全文完。
❤️欲读系列故事的全集内容,可搜用户“程序员吾真本”,找到“2024程序员容器化上云之旅”专栏阅读。
😃你能否跟着马意浓一步步做下来?在阅读中有任何疑问,欢迎在留言区留言。我会一一回复。
❤️如果喜欢本文,那么点赞和留言,并转发给身边有需要的朋友,就是对我的最大支持😃🤝🙏。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。