Compare commits

...

20 Commits

Author SHA1 Message Date
  Bruce-Jay 2debe88fa3 Merge branch 'master' of gitea.shuishan.net.cn:10205501411/cloud-computing-course 1 year ago
  Bruce-Jay 64f4f067fb 增加提交方式与修改标题 1 year ago
  林以任 e6f7ef88b1 Merge pull request 'Fix: 修改若干bug;完善报告' (#3) from 10215501422/cloud-computing-course:master into master 1 year ago
  高宇菲 7d3b393f1f 上传文件至 'images' 1 year ago
  高宇菲 b8830da292 删除 'images/schema.png/schema.png' 1 year ago
  高宇菲 8189d73233 上传文件至 'images/schema.png' 1 year ago
  高宇菲 adda243f5a 删除 'images/Snipaste_2023-10-24_22-39-19.png' 1 year ago
  高宇菲 c1695b2119 上传文件至 'images' 1 year ago
  高宇菲 721e6a28bc 更新 'CICD-ucloudUhub.md' 1 year ago
  高宇菲 196704aa63 更新 'CICD-ucloudUhub.md' 1 year ago
  高宇菲 24e0e7bd6b 更新 'CICD-ucloudUhub.md' 1 year ago
  高宇菲 a1292f04a2 Fix: vim --> vi 1 year ago
  高宇菲 0bf33b9ee1 Fix: /etc/profile 文件名写错 1 year ago
  高宇菲 2e2d3d0ca8 Fix: typo 1 year ago
  林以任 80a94de545 Merge pull request '所有‘存储库’都改成‘仓库’' (#2) from 10215501422/cloud-computing-course:master into master 1 year ago
  高宇菲 0dae0bc020 所有‘存储库’都改成‘仓库’ 1 year ago
  Bruce-Jay 8cf78a1666 add Assignment9.md 1 year ago
  Bruce-Jay 48e96d7c55 add Assignment8.md 1 year ago
  Bruce-Jay 43b0b6b9eb add Assignment7 1 year ago
  Bruce-Jay e7e31deff3 第六次实验课 1 year ago
27 changed files with 1217 additions and 0 deletions
Split View
  1. +171
    -0
      Assignment6.md
  2. +259
    -0
      Assignment7.md
  3. +248
    -0
      Assignment8.md
  4. +539
    -0
      CICD-ucloudUhub.md
  5. BIN
      images/action_detail.png
  6. BIN
      images/aliyunlogin.png
  7. BIN
      images/clone_my_repo.png
  8. BIN
      images/detail_info.png
  9. BIN
      images/docker_compose.png
  10. BIN
      images/files.png
  11. BIN
      images/instances.png
  12. BIN
      images/java8.png
  13. BIN
      images/java_version.png
  14. BIN
      images/maven.png
  15. BIN
      images/maven_mirror.png
  16. BIN
      images/maven_version.png
  17. BIN
      images/my_action.png
  18. BIN
      images/my_image_repo.png
  19. BIN
      images/namespace.png
  20. BIN
      images/new_secret.png
  21. BIN
      images/new_secret_detail.png
  22. BIN
      images/openedu_repo.png
  23. BIN
      images/personal_instance.png
  24. BIN
      images/personal_page.png
  25. BIN
      images/schema.png
  26. BIN
      images/secrets.png
  27. BIN
      images/ssh_key.png

+ 171
- 0
Assignment6.md View File

@ -0,0 +1,171 @@
# Assignment 6 指南
## `******注意,实验结束请立即删除负载均衡器和所有云主机,节省费用******`
## `******注意2,实验未结束且短期内不会继续实验,也请删除负载均衡器和所有云主机。下次实验时重新创建******`
## `******提示:勤用保存镜像******`
## 实验内容
- 创建负载均衡器: `实验步骤 一)`
- 创建Flask App服务实例,并连接负载均衡器:`实验步骤 二)`
- 进行服务器压力测试: `实验步骤 三)`
## 实验要求
- 完成所有步骤,并在实验报告([模板下载](file/assignment6/学号-实验六.docx))中完成穿插在本指南中的作业1~作业4)。实验报告上传至https://send2me.cn/7KWOp_fZ/TnSq4qbRuyFdUQ
- 实验报告上传deadline: `4月13日23:59`
## 使用UCloud产品
云主机UHost、负载均衡ULB、私有网络VPC、基础网络UNet
## 需要权限
云主机UHost、负载均衡ULB、基础网络UNet
## 基础知识
`负载均衡:` load balancing,是指将用户请求按照一定的规则,分流到提供相同服务的多个服务实例上,从而减轻单点服务器的压力,是保证服务高并发高可用的技术手段之一。用户、负载均衡器和服务器实例的关系可以简化为下图。这种架构的另一个好处是可以把服务器隐藏在局域网防火墙背后,因为外网直接访问的是负载均衡器。
<kbd>
<img src="img/assignment6/ass6-lb.jpeg">
</kbd>
负载均衡的算法有很多,常见的如轮询,ip地址哈希,最小连接数等,应对不同的需求,感兴趣的同学可以去了解一下。
`压力测试:` 测试一个系统的最大抗压能力,在强负载、高并发的情况下,测试系统所能承受的最大压力,预估系统的瓶颈。例如天猫双11之前,阿里内部会对整个淘宝平台进行全链路压测,预估应对峰值交易的能力。压力测试的工具有很多,今天我们练习使用非常简单的ApacheBench(ab)对http服务器进行压测。
好,废话少说,咱们书归正传!
## 实验步骤
### 一)使用ULB创建一个外网负载均衡器。
#### 1)在产品->网络中选择负载均衡ULB,然后点击创建负载均衡。
#### 2)根据下图配置,选择按时付费。因为我们一会需要从外网访问Flask App,所以选择外网负载均衡。后面涉及默认开放端口5000,请配置开放此端口的防火墙。
<kbd>
<img src="img/assignment6/ass6-createlb.png">
</kbd>
#### 3)创建完毕后,记住ULB的IP地址,点击详情->VServer管理->添加VServer。这里VServer就可以看成是一个负载均衡服务器,负责分流。取任意VServer名称,其他保持默认设置,点击确定。
<kbd>
<img src="img/assignment6/ip.png">
</kbd>
<kbd>
<img src="img/assignment6/ass6-vs.png">
</kbd>
#### 4)点击“服务节点”标签页,当前没有任何实际的节点提供服务。我们接下来创建一些服务节点。
### 二)使用云主机创建Flask App服务实例,并连接负载均衡器。
#### 1)创建一个1核1G的云主机,绑定弹性IP,选择web型防火墙,按时付费(已经很熟练了吧 :dog: :dog:)
#### 2)使用docker file创建一个Flask App的image(比如实验2中的hello world),并且暴露docker容器的5000端口(因为Flask开发模式默认端口就是5000)。创建完毕之后运行docker,将5000端口映射到云主机的80端口。
```
docker run -d --rm -p 80:5000 --name myflask luxuesong/myflaskapp
```
请同学们复习一下docker的安装和使用,以及通过dockerfile创建镜像。如果你意外保存了实验二的镜像,可以从镜像创建云主机,然后直接启动并docker run。
> 如果你在创建镜像的过程中遇到下图报错:
<kbd>
<img src="img/assignment6/error.png">
</kbd>
> 可尝试在命令`docker build`之前运行`DOCKER_BUILDKIT=0`,如下所示
```
DOCKER_BUILDKIT=0 docker build -t sll/myflaskapp .
```
#### 3)打开浏览器,输入 `http://你的云主机外网ip`,测试app是否正常运行。正常则进入下一步。
<kbd>
<img src="img/assignment6/ass6-llq.jpeg">
</kbd>
#### 4)将云主机连到负载均衡器。回到前面添加服务节点的界面,输入端口80,则所有可添加的节点都会出现(以内网ip形式)。将你的云主机对应的内网ip,转移到待添加节点,点击确定。这时候显示健康检查“失败”,不用管它,我们在浏览器中输入 `http://你的负载均衡器ip`,奇迹发生了!我们也能访问flask app!!并且这时候健康检查也变成“正常”。
<kbd>
<img src="img/assignment6/select_node.png">
</kbd>
<kbd>
<img src="img/assignment6/select2.png">
</kbd>
## `**************作业1:请将浏览器中通过负载均衡ip访问flask app的界面,以及负载均衡器中服务节点界面截图,并插入实验报告***************`
#### 5)此时负载均衡器只连接了一个服务实例,等同于单机访问。`为步骤三做准备,请在这一步制作当前云主机的镜像`。
### 三)使用ab进行http服务器压力测试
#### 1)使用步骤二)中制作的镜像,创建三个最低配置的云主机(`和之前一样,只是在云主机创建界面用“自制镜像”,并且不需要弹性ip,取消勾选“购买并绑定”`),可以选择三台主机一次性创建
<kbd>
<img src="img/assignment6/ass6-createuh.png">
</kbd>
<kbd>
<img src="img/assignment6/nei.png">
</kbd>
#### 2)创建完毕后,使用有外网ip的那台云主机,逐个ssh到三台内网机器上(通过内网ip),然后每台机器直接启动docker服务,并运行flask app容器(因为已经全都安装并且build好了 :smiley_cat: :smiley_cat: :smiley_cat:)
<kbd>
<img src="img/assignment6/ass6-intra.jpeg">
</kbd>
<kbd>
<img src="img/assignment6/ass6-intrassh.jpeg">
</kbd>
#### 3)现在让我们把这三个云主机连接到负载均衡器,并且从负载均衡器中删除刚才的具有外网ip的节点(接下来我们要用外网ip节点做压测)。首先我们禁用掉其中的两个节点,只保留一个节点在启用状态。如下图所示。
<kbd>
<img src="img/assignment6/ass6-3s.jpeg">
</kbd>
## `**************作业2:请将此时连接到负载均衡的服务节点(三个)截图,并插入实验报告***************`
#### 4)登录具有外网ip的云主机,安装ApacheBench
```
sudo yum -y install httpd
```
#### 5)运行压测命令。`-c`表示并发数,即同时发生的请求数,`-n`表示请求总数,`http://106.75.216.169/`替换成你的负载均衡器ip。所以这里对我们的flask app并发1000个请求,总共发出10000个请求。结果如下图所示。
```
ab -c 1000 -n 10000 http://106.75.216.169/
```
<kbd>
<img src="img/assignment6/ass6-ab.jpeg">
</kbd>
> 这里我们关注几个重要结果
> 1) Time taken for tests:压测总时间
> 2) Requests per second: 平均每秒处理请求数
> 3) Time per request: 平均每个请求处理时间
#### 6)接下来,请逐渐启用第2和第3个服务器节点,然后再运行上面的压测命令。
## `**************作业3:请将启用一个节点、两个节点、三个节点时的压测结果分别截图,并插入实验报告***************`
注意,由于有网络因素干扰,每个实验你可以多进行几次压测,得到比较正常的结果再截图。
## `**************作业4:假设现在压测结果显示单台机器的Requests per second为30,一天中的请求峰值时间是2个小时,峰值期间总共需要处理5千万个请求,请问需要至少准备多少台机器才可能平稳度过峰值请求?***************`

+ 259
- 0
Assignment7.md View File

@ -0,0 +1,259 @@
# Assignment 7 指南
## `******注意,实验结束请立即删除云主机、UFS文件存储,节省费用******`
## `******注意2,实验未结束且短期内不会继续实验,也请删除所有上述资源。下次实验时重新创建******`
## 实验内容
- 创建文件存储: `实验步骤 一)`
- 创建云主机,并挂载文件存储:`实验步骤 二)`
- 在水杉码园创建一个仓库,并下载至文件存储:`实验步骤 三)`
- 拉取python镜像,在容器内测试环境: `实验步骤 四)`
- 使用python容器训练识别MNIST手写数字的神经网络,并将所有内容同步到水杉码园:`实验步骤 五)`
## 实验要求
- 完成所有步骤,并在实验报告([模板下载](file/assignment7/学号-实验七.docx))中完成穿插在本指南中的作业1~作业5)。实验报告上传至https://send2me.cn/IV_038vw/RMOb1sRcBUqiyw
- 实验报告上传deadline: `4月20日23:59`
## 使用UCloud产品
云主机UHost、文件存储UFS、镜像库UHub、私有网络VPC、基础网络UNet
## 需要权限
云主机UHost、文件存储UFS、镜像库UHub、基础网络UNet
## 基础知识
`MNIST:` MNIST是一个手写数字数据库,包含60000个训练样本和10000个测试样本,是一个能够快速上手的、用于尝试机器学习和模式识别技术的数据集。
## 实验步骤
### 一)创建一个文件存储
#### 1)在产品->存储中选择“文件存储UFS”,然后点击创建文件系统。
#### 2)如下图,存储类型选择SSD性能型,100GB(新版本最低只能设置为500G),按时付费。
<kbd>
<img src="img/assignment7/ass7-createufs.png">
</kbd>
#### 3)创建完毕后,如下图所示在弹窗中点击确定设置挂载点,接着选择一个VPC网络,使得相应的子网是DefaultNetwork,点击确定。这样我们等一下在DefaultNetwork下面创建一个云主机,就能把这个文件存储挂载到云主机上。
<kbd>
<img src="img/assignment7/setGuazai.png">
</kbd>
<kbd>
<img src="img/assignment7/vpc.png">
</kbd>
#### 4)点击“管理挂载”,查看挂载信息,记住文件存储所在的ip地址,第二)步中我们把这个文件存储挂载到云主机上。
<kbd>
<img src="img/assignment7/guazai.png">
</kbd>
<kbd>
<img src="img/assignment7/guazaiIP.png">
</kbd>
## `**************作业1:请将含有文件存储ip地址信息的页面截图,并插入实验报告***************`
### 二)将文件存储挂载到云主机上,使得它在逻辑上成为云主机的一个分区
#### 1)创建一个1核2G(1G可能不够!!!)的云主机(后续需要用到docker,可从带docker的镜像创建主机),绑定弹性IP,按时付费(这个云主机必须在文件存储所挂载的子网中,否则无法和文件存储通信)
#### 2)登录云主机,安装NFS
```
sudo yum install -y nfs-utils
```
NFS(Network File System)是一个能够使得本地主机访问远程主机文件系统的应用程序。因为步骤一)创建的文件存储对于当前的云主机来讲是一个远程存储(网络存储),使用NFS协议才能将其挂载到当前云主机上。
#### 3)在云主机上挂载文件存储,挂载点为/mnt
```
sudo mount -t nfs4 你的文件存储IP地址:/ /mnt
```
#### 4)运行如下命令查看当前云主机的文件系统
```
df -hT
```
你应该看到如下图所示内容
<kbd>
<img src="img/assignment7/ass7-df.png">
</kbd>
## `**************作业2:请将df -hT的运行后界面截图,并插入实验报告***************`
### 三)在水杉码园创建一个仓库,并下载至文件存储
#### 1)登录[水杉在线](https://www.shuishan.net.cn/),并从水杉在线门户进入“水杉码园”。创建一个仓库mnist(你也可以用其他命名,但后续操作请做相应修改),创建完毕后,找到你的仓库ssh地址,备用
<kbd>
<img src="img/assignment7/ass7-createrepo1.png">
</kbd>
<kbd>
<img src="img/assignment7/ass7-createrepo2.png">
</kbd>
#### 2)在云主机上安装git,并配置一下,对应于自己水杉码园的用户名和邮箱
```
sudo yum install -y git
git config --global user.name "51255903039"
git config --global user.email "51255903039@stu.ecnu.edu.cn"
```
#### 3)生成云主机密钥,使用密钥访问水杉码园
```
ssh-keygen -t rsa -C '51255903039@stu.ecnu.edu.cn'
```
不用在提示符中输入任何内容,连摁回车,密钥即生成。可以在~/.ssh/下看到你生成的两个密钥,id_rsa是私钥,id_rsa.pub是公钥。如果你使用root账号,密钥在/root/.ssh/目录下。接下来我们要把公钥给码园,以后从这台云主机访问码园,云主机会把私钥提供给码园进行身份验证。
#### 4)打印并复制公钥的全部内容
```
cat ~/.ssh/id_rsa.pub
```
复制屏幕上出现的公钥内容
#### 5)在码园中创建公钥,并粘贴上述公钥内容
<kbd>
<img src="img/assignment7/ass7-key1.png">
</kbd>
<kbd>
<img src="img/assignment7/ass7-key2.png">
</kbd>
<kbd>
<img src="img/assignment7/ass7-key3.png">
</kbd>
#### 6)在云主机上运行如下命令,取消码园密码访问
```
eval 'ssh-agent -s'
exec ssh-agent bash
ssh-add ~/.ssh/id_rsa
ssh -T git@gitea.shuishan.net.cn
```
如果你看到类似如下输出,说明密钥访问设置成功
<kbd>
<img src="img/assignment7/ass7-key4.png">
</kbd>
#### 7)将mnist仓库下载到文件储存
```
cd /mnt
sudo mkdir mnist
sudo chown xuesong:xuesong mnist //更改mnist文件夹拥有者(即你的云主机登录账号)。假如你使用root账号,这步不需要
cd mnist
git init
git pull git@gitea.shuishan.net.cn:luxuesong_dase_ecnu_edu_cn/mnist.git //将pull后面的内容替换成你仓库的ssh地址
```
#### 8)在mnist下面新建三个目录code,data,output,下一个步骤中会使用。创建完毕后,你的mnist文件夹应该有如下结构。
<kbd>
<img src="img/assignment7/ass7-tree.png">
</kbd>
在步骤四)和五)中,我们将代码放在code文件夹中,数据放在data中,模型放在output中
## `**************作业3:请在mnist目录下运行ls -la命令并截图,插入实验报告***************`
### 四)使用docker拉取python镜像,并进入容器运行python
#### 1)运行docker,登录ucloud的镜像仓库,输入ucloud密码
```
docker login uhub.service.ucloud.cn -u 707661163@qq.com //换成你的ucloud登录邮箱
```
#### 2)拉取python镜像
```
docker pull uhub.service.ucloud.cn/cloud_computing/python:latest
```
#### 3)运行容器,并进入bash
```
docker run -it uhub.service.ucloud.cn/cloud_computing/python /bin/bash
```
#### 4)测试python环境,查看已有包
```
python -V
pip list
```
## `**************作业4:请将进入容器测试python页面截图,插入实验报告***************`
### 五)使用python容器训练MNIST识别模型,最后将所有内容同步到水杉码园
#### 1)按ctrl+d可退出容器,进入主机/mnt/mnist/data目录,下载mnist数据集。
```
wget https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
```
#### 2)此时让我们先把UFS中的这些文件push一把,同步到码园中。在/mnt/mnist下,运行
```
git remote add origin git@gitea.shuishan.net.cn:luxuesong_dase_ecnu_edu_cn/mnist.git //替换成你的码园仓库
git add .
git commit -m "xuesong's first commit"
git push origin master
```
没有报错则成功push,去水杉码园查看你的仓库验证。
#### 3)下载mnist训练代码[mnist.py](file/assignment7/mnist.py),把它放在/mnt/mnist/code/目录下。
#### 4)进入python容器依次安装训练所需的包。
```
pip install -U numpy==1.16.4 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install tensorflow==1.14.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
```
#### 5)准备好数据、代码、环境,开始训练。因为/mnt/mnist目录在容器外部,所以运行时需要将此目录与容器内部的目录进行映射。
```
docker run -v /mnt/mnist:/home/mnist -it uhub.service.ucloud.cn/cloud_computing/python /bin/bash
```
## `**************作业5:上述代码训练的模型,在测试集上精度较低(如下图),请把测试集上的精度提升到95%以上(即运行model.evaluate(x_test, y_test)后,accurray在95%以上),将运行结果截图并插入实验报告***************`
> 提示:你可以尝试增加epoch,也可以尝试更换优化器,其他优化器有Adagrad, RMSprop, Adam等
#### 6)保存训练代码,并push到水杉码园中。
```
git add .
git commit -m "commit source code and model"
git push origin master
```

+ 248
- 0
Assignment8.md View File

@ -0,0 +1,248 @@
# Assignment 8 指南
## `******注意,实验结束请立即删除云主机UHost,云盘,负载均衡ULB和容器云UK8S集群,节省费用******`
## `******注意2,实验未结束且短期内不会继续实验,也请删除上述所有资源。下次实验时重新创建******`
## 实验内容
- 创建一个K8S集群: `实验步骤 一)`
- 创建一个云主机,并安装kubectl:`实验步骤 二)`
- 在K8S集群上部署一个网页应用:`实验步骤 三)`
- 创建网页应用的多个副本:`实验步骤 四)`
- 启动一个Service对外提供服务:`实验步骤 五)`
## 实验要求
- 完成所有步骤,并在实验报告([模板下载](file/assignment8/学号-实验八.docx))中完成穿插在本指南中的作业1~作业5)。实验报告转成“学号-实验八.pdf”,并上传至http://113.31.104.68:3389/index.htm
- 实验报告上传deadline: `12月11日`
## 使用UCloud产品
云主机UHost、容器云UK8S、私有网络VPC
## 需要权限
云主机UHost、镜像库UHub、负载均衡ULB、容器云UK8S、基础网络UNet
## 基础知识
`K8S理论:` 请参考 https://www.yuque.com/serviceup/cloud-native-talks/k8s-roadmap-primary
`K8S实训:` 更多内容请参考 https://kubernetes.io/docs/tutorials/
## 实验步骤
### 一)在“上海教育云”下创建一个K8S集群(“华北一”不支持本次实验!!!)
#### 1)在产品->容器服务中选择“容器云UK8S”,然后点击“创建集群”。
#### 2)无需更改任何配置,设置管理员密码,付费方式改成“按时”(合计费用应为3.49元),点击立即购买->确认支付。
#### 3)这时候你可以吃早饭或者喝杯 :coffee:,等待5~10分钟
#### 4)当集群状态变成“运行”,说明创建完毕。
<kbd>
<img src="img/assignment8/ass8-cluster.png">
</kbd>
#### 5)让我们看看集群里有些什么。点击“详情”->“集群”,可以看到集群里创建了8个节点(云主机),3个master,5个node。再从控制台进入云主机产品界面,你会发现里面创建了8个云主机,对应的就是集群中的8个节点,每个云主机以“uk8s-”开头命名
## `**************作业1:请将K8S“集群”页面截图,并插入实验报告***************`
### 二)在“上海教育云”下创建一个云主机,并安装kubectl(操作kubernetes集群的命令行工具)
#### 1)创建一个1核1G的云主机,绑定弹性IP,按时付费
#### 2)登录云主机,安装kubectl
```
wget https://storage.googleapis.com/kubernetes-release/release/v1.19.0/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin/ (root用户:mv kubectl /usr/bin/)
kubectl version -o json
```
#### 3)如果你看到如下输出说明安装成功
<kbd>
<img src="img/assignment8/ass8-kubeversion.png">
</kbd>
#### 4)将K8S的集群凭证添加到kubectl配置文件中,从而能够操控集群。
> 在概览中查看“外网凭证”,并复制凭证内容。
<kbd>
<img src="img/assignment8/ass8-gailan.png">
</kbd>
<kbd>
<img src="img/assignment8/ass8-pinzheng.png">
</kbd>
> 在云主机中创建~/.kube文件夹,然后创建~/.kube/config文件,并将凭证内容粘贴到该文件中。
#### 5)再次运行`kubectl version -o json`,可以看到除了刚才的client信息,server信息也能打印了。
## `**************作业2:请将到这一步的kubectl version -o json输出信息页面截图,并插入实验报告***************`
#### 6)尝试几个基本命令
```
kubectl cluster-info //打印集群信息
kubectl get nodes //打印节点信息
kubectl get pods //打印pod信息
kubectl get deployments //打印应用部署信息
kubectl get services //打印service信息
```
### 三)在K8S上部署一个应用
#### 1)运行以下命令,部署一个assignment2中的静态网页app
```
kubectl create deployment webapp --image=uhub.service.ucloud.cn/cloud_computing/static_site:latest
```
> webapp是deployment的名字,可以随意。也就是说,我们创建了一个webapp部署,部署的内容是包含一个静态网页的docker;部署过程中打包了一个pod,包含了该应用
#### 2)用上面提到的基本命令查看应用部署信息和pod信息,得到如下图所示信息
<kbd>
<img src="img/assignment8/ass8-deployment.png">
</kbd>
#### 3)这时我们需要用kubectl创建一个代理,使得从云主机可以访问集群(从而访问pod)。打开一个新的Terminal或者XShell窗口,登录云主机。
```
echo -e "\n\e[92mStarting Proxy. After starting it will not output a response. Please click the first Terminal Tab\n"; kubectl proxy
```
> 你应该看到“Starting to serve on 127.0.0.1:8001”,表示代理设置成功
#### 4)访问webapp应用(访问pod)
```
export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME //如果这一步没有输出,说明$POD_NAME没有获得pod名称,可以把kubectl get pods得到的pod名字直接赋给$POD_NAME。
curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/
```
> 可以看到网页的html输出,说明应用部署成功
## `**************作业3:请将上面最后一个命令输出的信息截图,并插入实验报告***************`
### 四)创建多个webapp副本
#### 1)查看pod当前副本信息
```
kubectl get rs
```
> 可以看到只有一个副本,也就是之前我们手动部署的那个
#### 2)查看pod的详细信息
```
kubectl get pods -o wide
```
> 可以看到pod部署的NODE的ip信息,正是集群中的一个node。k8s自动选择了这个node进行部署。
<kbd>
<img src="img/assignment8/ass8-1rs.png">
</kbd>
#### 3)现在让我们用一行命令将副本scale到10个,然后再次查看pod详细信息
```
kubectl scale deployments/webapp --replicas=10
kubectl get pods -o wide
```
> 可以看到此时有10个运行webapp的pod(如果不是10个说明还未全部创建完成,可稍等再次查看),运行在集群中的5个node节点上
<kbd>
<img src="img/assignment8/node.png">
</kbd>
## `**************作业4:请将上述有10个pod的打印信息截图,并插入实验报告***************`
### 五)最后,让我们将webapp暴露给公网,从而可以从外部访问页面
#### 1)创建一个负载均衡service,将webapp暴露给外网
```
kubectl expose deployment webapp --type=LoadBalancer --port=80
```
#### 2)查看当前的service,你可以找到负载均衡服务的外网ip地址,如果显示“pending”,可稍等两秒再次查看
```
kubectl get services
```
<kbd>
<img src="img/assignment8/pending.png">
</kbd>
#### 3)在你自己的电脑上,打开浏览器,访问http://上图显示的负载均衡服务的外网ip地址
<kbd>
<img src="img/assignment8/hellodocker.png">
</kbd>
## `**************作业5:请将kubectl get services输出信息截图、并将浏览器地址栏和网页内容一起截图,并插入实验报告***************`
#### 4)删除service,删除所有pod,最后在网页上删除集群,删除云主机(包括K8S没有自动删除的云主机)。
```
kubectl delete service webapp
kubectl delete deployment webapp
```
> 查看云主机产品中的云盘管理,如果有未删除的云盘,请全部手动删除
<kbd>
<img src="img/assignment8/delete.png">
</kbd>
<kbd>
<img src="img/assignment8/ass8-udisk.png">
</kbd>
> 最后在控制台首页点击资源统计,请删除所有占用的资源,确保除了“私有网络”以外,其他产品分类都显示`当前分类下暂无资源`
<kbd>
<img src="img/assignment8/ass8-resource.png">
</kbd>
<br>
<br>
<br>
#### 5)最后的最后,还有两个消息要送给大家。好消息:这是本学期最后一次作业 :smiley: ;坏消息:期末大作业即将开始 :zap: :zap:

+ 539
- 0
CICD-ucloudUhub.md View File

@ -0,0 +1,539 @@
# 基于 ucloud 云主机和阿里云镜像仓库实现 GitHub 的 CI/CD
GitHub Actions是GitHub的一个持续集成和持续交付(CI/CD)的平台,可以自动化构建、测试和部署Pipelines。
#### Workflow(工作流)
Workflow其实就是一个可配置的自动化过程,会运行一个或多个Job。Workflow定义在仓库的 .github/workflows文件夹的一个YML或者YAML文件中,并在仓库的Event触发时运行,也可以配置成手动触发运行,或者定时触发。一个Workflow中甚至可以引用另一个Workflow。
可以简单理解,一个YML配置文件就是一个Workflow,仓库可以在.github/workflows文件夹下拥有多个YML文件,即拥有多Workflows。每个Workflow可以执行一组不同的步骤。例如,一个仓库中有两个Workflows,就可以有一个Workflow用来构建和测试PR(Pull Request),另一个Workflow用来每次发布Release版本时自动部署应用。
#### Event(事件)
Event是仓库中的一个用来触发Workflow运行的特殊活动,有不同类型,如某人创建了一个PR、新建了一个Issue等,这些都是可以用来触发Workflow运行的Event。
#### Job(工作)
Job是Workflow中的一组步骤,在同一个Runner(运行者)上运行。每个步骤要么是将执行的Shell脚本,要么是将运行的Action。各步骤是按顺序执行的,并且相互依赖。由于各步骤是在同一个Runner上运行,因此可以将数据从一个步骤共享到另一个步骤。可以配置Job与其他Job的依赖关系;默认情况下,Job之间没有依赖关系,并且彼此并行运行。当一个Job依赖于另一个Job时,它将等待从属Job完成,然后才能运行。
#### Action
Action是GitHub Actions平台的自定义应用程序,用于执行复杂但经常重复的任务。Action可以减少在Workflow的YML文件中编写的重复代码。Action可以从GitHub上拉取仓库代码,为构建环境设置正确的工具链。我们可以编写自己的Action,也可以在GitHub Marketplace中寻找适合使用的Action。
#### Runner(运行者)
Runner是在Workflow被触发时运行它们的服务器。每个Runner一次可以运行一个Job。GitHub提供了Ubuntu Linux、Windows和macOS Runner来运行Workflow。每个Workflow都在全新的预先配置好的虚拟机中执行。如果我们需要不同的操作系统或者特定的硬件配置,可以用自己托管的Runner来代替GitHub提供的Runner。
## 实验目的和实验环境
#### 【实验目的】
(1)了解 GitHub Actions。
(2)初步了解 Workflow 语法并自行构建。
(3)实现 Spring Cloud 项目全自动化打包部署。
(4)熟悉整个 CI/CD 的原理。
#### 【实验环境】
(1)CentOS:7.9 及以上
(2)Docker:20.10.7 版。
(3)docker-compose:1.29.2 版,build 5becea4c。
(4)maven 3.9.5
(5)java openjdk-1.8.0
(6)本次实验建议使用 vscode 连接云主机,这样就可以直接在云主机上编辑,而不用总是上传文件。(这个可以自行上网学习,很方便,参考文档 https://blog.csdn.net/m0_49448331/article/details/126030161)
## 实验步骤
#### 1.复制项目代码
将 https://github.com/OpenEduTech/DaseDevOps/tree/master 复制到自己的账户下后,会生成DaseDevops仓库。注意:下面所有图中显示的devops_demo仓库其实对应的是DaseDevops仓库下的dasedevops_spring_demo项目。
![image-20231111152635356](./images/openedu_repo.png)
随后,在自己的云主机任意目录下(我这里是 /root )执行 git clone 克隆仓库。使用 ssh 克隆。在克隆之前,可能会报没有 git 命令的错误,需要你手动安装一下 `yum install -y git`
请重点检查:是 fork 后的仓库,而不是 OpenEduTech 的仓库!!!不然会无法 push
![image-20231112145610434](./images/clone_my_repo.png)
在云主机上,生成本机密钥,用于免密认证。引号内的部分需要更换成你自己 github 绑定的邮箱。
```
cd .ssh
ssh-keygen -t rsa -C "102155014xx@stu.ecnu.edu.cn"
```
一路回车,待成功生成密钥后,执行命令 `cat id_rsa.pub` 将打印出的公钥复制到 github 中。
在 github 上配置云主机的 ssh key,点击头像,
![image-20231112145800806](./images/ssh_key.png)
密钥 Title 设置为 action,内容复制进去,这样就可以直接在云主机上免密 git push 了。
#### 2.建立服务器A环境
这里使用的是GitHub托管的Runner,因此服务器A的环境其实已经由GitHub处理好了,不需要我们做任何操作。如果使用自托管的Runner,感兴趣的同学可以参考相关网页。
#### 3.建立服务器B环境
首先,创建一个 centOS 云主机,1 核 2G 配置,20M 带宽流量计费,数据盘设置为 50 G
安装Docker(参照https://gitea.shuishan.net.cn/xslu_dase_ecnu_edu_cn/cloud-computing-course/src/branch/master/Assignment2.md),
安装docker-compose。
##### docker-compose 安装过程:
通过 curl 下载 docker compose
```bash
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```
添加可执行权限
```bash
sudo chmod +x /usr/local/bin/docker-compose
```
将文件 copy 到 /usr/bin/ 目录下
```bash
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
```
查看版本
```
docker-compose --version
```
显示以下信息说明安装成功。
![image-20231110221616117](./images/docker_compose.png)
##### 安装 jdk
请注意,本次实验仅支持手动安装,不支持使用 yum 安装!!
浏览器访问 oracle 官网 https://www.oracle.com/java/technologies/downloads/#license-lightbox 下拉到 Java SE8,下载 x64 版本的压缩包
![image-20231112152329365](./images/java8.png)
在云主机的 /usr 目录下创建 java/ 文件夹
```
cd /usr
mkdir java
cd java
```
把下载好的文件上传到 /usr/java 下
因为文件有 100M 大小,建议等待一段时间,等上传完成以后进行后续操作。
解压
```
tar -zxvf jdk-8u391-linux-x64.tar.gz
```
解压以后,配置环境变量
```
vi /etc/profile
```
在文件末尾添加
```
export JAVA_HOME=/usr/java/jdk1.8.0_391
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib
```
更新环境变量
```
source /etc/profile
```
输入 java -version 应该可以看到安装成功的消息。
![image-20231112155129640](./images/java_version.png)
##### 安装 maven
浏览器访问 maven 的下载页面 https://maven.apache.org/download.cgi
点击链接下载
![image-20231112145016939](./images/maven.png)
之后上传到 ucloud 云主机的 /usr/local 目录下,进入 /usr/local 目录,解压
```bash
tar -xvf apache-maven-3.9.5-bin.tar.gz
```
执行 `vi /etc/profile` 配置环境变量
```
MAVEN_HOME=/usr/local/apache-maven-3.9.5
export PATH=${MAVEN_HOME}/bin:${PATH}
```
重载环境变量
```
source /etc/profile
```
查看是否安装成功,输入 `mvn -v` 命令,显示相关信息
![image-20231112151248101](./images/maven_version.png)
由于 maven 官方源在国内速度比较慢,我们需要配置阿里 maven 源。cd 到 maven 的安装目录下
修改 conf 文件夹下的 settings.xml
```
sudo vi conf/settings.xml
```
修改 mirrors 下没有被注释的那一部分
![image-20231112165227354](./images/maven_mirror.png)
我这边是已经修改过的,同学们需要根据以下代码修改原有的代码
```
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
```
需要把原有的 `<block>true</block>` 去掉
#### 4.建立 ~~ucloud~~ 阿里云容器镜像服务
本来是想通过 ucloud 使用容器镜像服务的,但是奈何 ucloud 镜像仓库无法通过 github action 的 workflow 进行登录并且操作。无奈之下只能使用阿里云的 [aliyun/acr-login](https://github.com/aliyun/acr-login) 进行 yml 文件登录到 docker 的操作。
为什么不能通过 ucloud 创建容器镜像服务然后再通过登录阿里云镜像仓库然后把镜像推送到 ucloud 中呢?不行。就像 github 中无法直接把自己的代码推送到别人的仓库中一样。
所以为了完成本次实验,需要麻烦大家再在阿里云上创建一个容器镜像服务。
首先,访问 https://www.aliyun.com/ 点击右上角的 “注册/登录” 按钮,完成注册与登录操作
![image-20231111104809589](./images/aliyunlogin.png)
可以直接使用支付宝扫码登录
登录成功后,访问实例列表 https://cr.console.aliyun.com/cn-shanghai/instances 点击创建个人实例。
需要通过 RAM 授权,按照流程点击确认即可。
![image-20231111105335421](./images/instances.png)
![image-20231111105406205](./images/personal_instance.png)
创建完个人版实例以后,需要设置 registry 登录密码。
然后点击 命名空间 -> 创建命名空间 -> 给自己的命名空间起一个名字吧
推荐使用自己的姓名拼音之间加 "-" 号进行命名。如果出现重名,就再加一些符号。
![image-20231111105841358](./images/namespace.png)
![image-20231112155904281](./images/personal_page.png)
### **需要把 默认仓库类型 设置为公开。**
## `作业 1: 创建完成以后,进入个人实例-命名空间页面,截图插入实验报告。`
#### 5.接下来,编写本项目的 workflow 文件。在项目根目录下新建 .github/workflows 文件夹将以下代码保存在 docker-publish.yml
```yml
name: SpringCloud CI/CD with Docker
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
MODULE_1: gateway
MODULE_2: hello
MODULE_3: login
MODULE_4: provider_one
MODULE_5: provider_two
MODULE_6: provider_three
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '8'
cache: 'maven'
- name: Build with Maven
run: |
whoami
pwd
cd dasedevops_spring_demo
mvn -B package --file pom.xml
pwd
- name: Login to Aliyun Container Registry (ACR)
uses: aliyun/acr-login@v1
with:
login-server: https://registry.cn-shanghai.aliyuncs.com
region-id: cn-shanghai # 3
username: "${{ secrets.ACR_USERNAME }}"
password: "${{ secrets.ACR_PASSWORD }}"
- name: Build and push image
run: |
docker build -t ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_1:latest ./dasedevops_spring_demo/$MODULE_1
docker push ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_1:latest
docker build -t ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_2:latest ./dasedevops_spring_demo/$MODULE_2
docker push ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_2:latest
docker build -t ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_3:latest ./dasedevops_spring_demo/$MODULE_3
docker push ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_3:latest
docker build -t ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_4:latest ./dasedevops_spring_demo/$MODULE_4
docker push ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_4:latest
docker build -t ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_5:latest ./dasedevops_spring_demo/$MODULE_5
docker push ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_5:latest
docker build -t ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_6:latest ./dasedevops_spring_demo/$MODULE_6
docker push ${{ secrets.ALI_NAMESPACE_URL }}/$MODULE_6:latest
- name: Copy single file to remote
uses: garygrossgarten/github-action-scp@release
with:
local: ./dasedevops_spring_demo/docker-compose.yml
remote: scp/devops_demo/docker-compose.yml
host: ${{ secrets.HOST }}
username: ${{ secrets.USER_NAME }}
password: ${{ secrets.USER_PASSWORD }}
port: ${{ secrets.PORT }}
depoly:
needs: [ build ]
name: Docker Pull And Docker-compose Run
runs-on: ubuntu-latest
steps:
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER_NAME }}
password: ${{ secrets.USER_PASSWORD }}
port: ${{ secrets.PORT }}
script: |
docker-compose -f scp/devops_demo/docker-compose.yml up -d
docker-compose -f scp/devops_demo/docker-compose.yml stop
docker-compose -f scp/devops_demo/docker-compose.yml pull
docker-compose -f scp/devops_demo/docker-compose.yml up -d
docker image prune -f
```
相关 secrets 解释:
为什么需要用到 github secrets? 我们都知道,很多情况下,项目有一些隐秘信息,不能直接配置在项目内,包括但不仅限于: github token, 各种账号的用户名密码,私钥信息,各种网站的 api key, app key, secret key 等等。这时候就需要用到 secrets。所以根据 secrets 的特性,一经设置就不可查看!所以设置 secrets 时务必仔细确认!
secrets.ALI_NAMESPACE_URL: 对应阿里云容器镜像服务的 “所在地域 + 命名空间” ,我这里是 “registry.cn-shanghai.aliyuncs.com/easonlin-devops-practice” ,前面的 registry.cn-shanghai.aliyuncs.com 是地域,后面的 easonlin-devops-practice 是命名空间。地域和命名空间很重要,在我们登录阿里云 registry 和拉取、推送镜像时都会用到。
![image-20231111163606733](./images/detail_info.png)
secrets.ACR_USERNAME:阿里云容器服务registry的用户名。
secrets.ACR_PASSWORD:阿里云容器服务registry的用户密码。
secrets.HOST:服务器B的IP地址,就是 ucloud 云主机的公有 ip
secrets.USER_NAME:服务器B的登录用户名,默认为 root
secrets.USER_PASSWORD:服务器B的登录密码。
secrets.PORT:服务器B的SSH开放端口,默认是22。
这些 secrets都可以在代码仓库的“Setting →Secrets → Actions”界面进行设置,使用自己账号的阿里云容器镜像服务中的参数。
![image-20231111161358868](./images/secrets.png)
新建 secret:点击 New repository secret 按钮
![image-20231111161513709](./images/new_secret.png)
输入 secret name 与 value 即可
![image-20231111161537636](./images/new_secret_detail.png)
随后,进入微服务文件夹 dasedevops_spring_demo 下,进入以下六个文件夹的 Dockerfile 进行编辑。
<img src="./images/files.png" alt="files.png" style="zoom:50%;" />
注意到了吗,这六个文件夹就和 workflow 中的六个 MODULE 命名相同。
```
env:
MODULE_1: gateway
MODULE_2: hello
MODULE_3: login
MODULE_4: provider_one
MODULE_5: provider_two
MODULE_6: provider_three
```
编辑 DockerFile 也是为了分别将这六个 module 打包成镜像,上传到镜像仓库中。这么看,这个 yml 文件是不是也不那么复杂?🐶
gateway微服务的Dockerfile文件内容如下:
**在六个文件夹下分别创建名为dockerfile的文件**
```dockerfile
FROM openjdk:8
MAINTAINER ningzhicheng
VOLUME /tmp
ADD ./target/*.jar /gateway.jar
ENTRYPOINT ["java","-jar","/gateway.jar"]
EXPOSE 8080
```
hello微服务的Dockerfile文件内容如下:
```dockerfile
FROM openjdk:8
MAINTAINER ningzhicheng
VOLUME /tmp
ADD ./target/*.jar /hello.jar
ENTRYPOINT ["java","-jar","/hello.jar"]
EXPOSE 8001
```
login微服务的Dockerfile文件内容如下:
```dockerfile
FROM openjdk:8
MAINTAINER ningzhicheng
VOLUME /tmp
ADD ./target/*.jar /login.jar
ENTRYPOINT ["java","-jar","/login.jar"]
EXPOSE 8000
```
provider_one微服务的Dockerfile文件内容如下:
```dockerfile
FROM openjdk:8
MAINTAINER ningzhicheng
VOLUME /tmp
ADD ./target/*.jar /provider_one.jar
ENTRYPOINT ["java","-jar","/provider_one.jar"]
EXPOSE 8666
```
provider_two微服务的Dockerfile文件内容如下:
```dockerfile
FROM openjdk:8
MAINTAINER ningzhicheng
VOLUME /tmp
ADD ./target/*.jar /provider_two.jar
ENTRYPOINT ["java","-jar","/provider_two.jar"]
EXPOSE 8667
```
provider_three微服务的Dockerfile文件内容如下:
```dockerfile
FROM openjdk:8
MAINTAINER ningzhicheng
VOLUME /tmp
ADD ./target/*.jar /provider_three.jar
ENTRYPOINT ["java","-jar","/provider_three.jar"]
EXPOSE 8668
```
#### 6.编写 docker-compose.yml 文件
在 dasedevops_spring_demo 下新建一个 docker-compose.yml 文件,将以下内容复制到文件中
```dockerfile
version: "3"
services:
providerOne:
image: ${{ secrets.ALI_NAMESPACE_URL }}/provider_one:latest
ports:
- "8666:8666"
gateway:
image: ${{ secrets.ALI_NAMESPACE_URL }}/gateway:latest
ports:
- "8080:8080"
login:
image: ${{ secrets.ALI_NAMESPACE_URL }}/login:latest
ports:
- "8000:8000"
hello:
image: ${{ secrets.ALI_NAMESPACE_URL }}/hello:latest
ports:
- "8001:8001"
providerTwo:
image: ${{ secrets.ALI_NAMESPACE_URL }}/provider_two:latest
ports:
- "8667:8667"
providerThree:
image: ${{ secrets.ALI_NAMESPACE_URL }}/provider_three:latest
ports:
- "8668:8668"
```
文件目录如下:
![image](./images/schema.png)
随后将所有文件提交到暂存区,创建提交信息,推送提交。
```
git add .
git commit -m "test"
git push
```
查看自己仓库的 Actions 界面
![image-20231112165619307](./images/my_action.png)
## `作业 2: 将成功的截图(示例如下)插入到实验报告中。`
![image-20231112165720555](./images/action_detail.png)
## `作业 3:将阿里云容器镜像服务-个人实例-镜像仓库页面截图,插入实验报告。`
示例如下图,必须包括这六个仓库。
![image-20231112170627251](./images/my_image_repo.png)
## `本次实验已结束,请同学们立即释放所有 ucloud 云主机资源!`
作业请提交到坚果云 https://send2me.cn/OMVsV_xl/QLS56Ze4wBqJ4Q

BIN
images/action_detail.png View File

Before After
Width: 2166  |  Height: 1174  |  Size: 100 KiB

BIN
images/aliyunlogin.png View File

Before After
Width: 1430  |  Height: 965  |  Size: 228 KiB

BIN
images/clone_my_repo.png View File

Before After
Width: 2398  |  Height: 1609  |  Size: 304 KiB

BIN
images/detail_info.png View File

Before After
Width: 1589  |  Height: 966  |  Size: 146 KiB

BIN
images/docker_compose.png View File

Before After
Width: 686  |  Height: 69  |  Size: 7.8 KiB

BIN
images/files.png View File

Before After
Width: 503  |  Height: 863  |  Size: 56 KiB

BIN
images/instances.png View File

Before After
Width: 1910  |  Height: 768  |  Size: 129 KiB

BIN
images/java8.png View File

Before After
Width: 2307  |  Height: 1697  |  Size: 281 KiB

BIN
images/java_version.png View File

Before After
Width: 921  |  Height: 125  |  Size: 26 KiB

BIN
images/maven.png View File

Before After
Width: 1792  |  Height: 1290  |  Size: 228 KiB

BIN
images/maven_mirror.png View File

Before After
Width: 1613  |  Height: 1638  |  Size: 194 KiB

BIN
images/maven_version.png View File

Before After
Width: 1274  |  Height: 185  |  Size: 49 KiB

BIN
images/my_action.png View File

Before After
Width: 2987  |  Height: 1753  |  Size: 322 KiB

BIN
images/my_image_repo.png View File

Before After
Width: 3301  |  Height: 1397  |  Size: 201 KiB

BIN
images/namespace.png View File

Before After
Width: 1886  |  Height: 752  |  Size: 103 KiB

BIN
images/new_secret.png View File

Before After
Width: 1542  |  Height: 865  |  Size: 93 KiB

BIN
images/new_secret_detail.png View File

Before After
Width: 1474  |  Height: 800  |  Size: 27 KiB

BIN
images/openedu_repo.png View File

Before After
Width: 3316  |  Height: 1417  |  Size: 288 KiB

BIN
images/personal_instance.png View File

Before After
Width: 1852  |  Height: 1280  |  Size: 258 KiB

BIN
images/personal_page.png View File

Before After
Width: 3423  |  Height: 1087  |  Size: 59 KiB

BIN
images/schema.png View File

Before After
Width: 438  |  Height: 725  |  Size: 33 KiB

BIN
images/secrets.png View File

Before After
Width: 3473  |  Height: 1949  |  Size: 302 KiB

BIN
images/ssh_key.png View File

Before After
Width: 2286  |  Height: 1619  |  Size: 293 KiB

Loading…
Cancel
Save