2023年12月

前言

大家好,我是 BNTang, 在上一节文章中,我给大家详细的介绍了如何将我开发好的项目打包为微信小程序并且发布到微信小程序商店

趁热打铁,在来一篇文章,给大家详细的介绍如何将项目打包成APP。

正文

打包 App 也是一样的,首先需要配置关于 App 应用的基础信息,打开
manifest.json

配置 App图标

选择 App 图标配置,选择一张即可,下面的尺寸都是自动生成而来的(建议使用 1024 * 1024):

配置 App启动界面

如果你有启动页面,你就可以在这个配置项当中进行配置,没有就不用配了。

配置 App模块

如果你用到了模块当中的内容,勾选上,然后打包进去即可,没有用到就算了。

配置 App权限

就是看你应用 App 中的权限,比如说你项目中用到了蓝牙,读取通讯录等等,就在这个配置中勾选上对应的权限即可。

配置 App常用其它设置

关于这个呢,我介绍里面的两个记好了,分别是
minSdkVersion

targetSdkVersion
,这两个是必须要配置的,其他的可以不用管。

  • 那么这个怎么配置呢,在这个配置项的右侧有一个参考文档,点击进去
  • 你就会看到这个文档,详细的内容大家可以自己去看看

大概意思就是说支持的安卓版本,最低支持的版本是多少,最高支持的版本是多少,这个大家可以根据自己的项目需求进行配置。

我这里配置为 21 与 28,然后再将下面的支持CPU类型勾选一下:

一般情况下只需要勾选前面两个就可以了,
x86
是模拟器的,
arm64-v8a
是真机的,如果你想要支持模拟器,那么就勾选上
x86

好了,大致就是这些,现在就可以点击发行了。

安装 App 云打包插件:

接下来的就是耐心等待,打包成功之后控制台会输出如下图信息:

点击打开所在目录,双击
.apk
文件就可以安装到模拟器当中:

然后就可以双击这个程序运行起来:

或者找到工程目录中的
unpackage/release/apk

之后将这个文件发送给其它人进行安装即可。

End

  • 如果你有任何问题或建议,欢迎在下方留言,我会尽快回复
  • 如果你觉得本文对你有帮助,欢迎点赞、收藏,你的支持是我写作的最大动力

本文分享自华为云社区《
一步一步教你写kubernetes sidecar
》,作者: 张俭。

什么是sidecar?

kubernetes-sidecar-what-is.png

sidecar,直译为边车。 如上图所示,边车就是加装在摩托车旁来达到拓展功能的目的,比如行驶更加稳定,可以拉更多的人和货物,坐在边车上的人可以给驾驶员指路等。边车模式通过给应用服务加装一个“边车”来达到控制和逻辑的分离的目的。

对于微服务来讲,我们可以用边车模式来做诸如 日志收集、服务注册、服务发现、限流、鉴权等不需要业务服务实现的控制面板能力。通常和边车模式比较的就是像spring-cloud那样的sdk模式,像上面提到的这些能力都通过sdk实现。

kubernetes-sidecar-what-can-do.png

这两种实现模式各有优劣,sidecar模式会引入额外的性能损耗以及延时,但传统的sdk模式会让代码变得臃肿并且升级复杂,控制面能力和业务面能力不能分开升级。

本文的代码已经上传到
gitee

sidecar 实现原理

介绍了sidecar的诸多功能,但是,sidecar是如何做到这些能力的呢?

原来,在kubernetes中,一个pod是部署的最小单元,但一个pod里面,允许运行多个container(容器),多个container(容器)之间共享存储卷和网络栈。这样子,我们就可以多container来做sidecar,或者init-container(初始化容器)来调整挂载卷的权限

kubernetes-sidecar-inside.png

日志收集sidecar

日志收集sidecar的原理是利用多个container间可以共用挂载卷的原理实现的,通过将应用程序的日志路径挂出,用另一个程序访问路径下的日志来实现日志收集,这里用cat来替代了日志收集,部署yaml模板如下

apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
volumes:
- name: shared-logs
emptyDir: {}

containers:
-name: nginx
image: ttbb
/nginx:mate
volumeMounts:
- name: shared-logs
mountPath:
/opt/sh/openresty/nginx/logs- name: sidecar-container
image: ttbb
/basecommand: ["sh","-c","while true; do cat /opt/sh/openresty/nginx/logs/nginx.pid; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath:
/opt/sh/openresty/nginx/logs

使用kubectl create -f 创建pod,通过kubectl logs命令就可以看到sidecar-container打印的日志输出

kubectl logs webserver sidecar-container

转发请求sidecar

这一节我们来实现,一个给应用程序转发请求的sidecar,应用程序代码如下

use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};

fn main() {
let listener
= TcpListener::bind("127.0.0.1:7878").unwrap();for stream inlistener.incoming() {
let stream
=stream.unwrap();

handle_connection(stream);
}
println
!("Hello, world!");
}

fn handle_connection(mut stream: TcpStream) {
let mut buffer
= [0; 1024];

stream.read(
&mut buffer).unwrap();

let contents
= "Hello";

let response
= format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
contents.len(),
contents
);

println
!("receive a request!");
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}

我们再来写一个sidecar,它会每15秒向应用程序发出请求

use std::thread;
use std::time::Duration;

fn main() {
loop {
thread::sleep(Duration::from_secs(
15));
let response
= reqwest::blocking::get("http://localhost:7878").unwrap();
println
!("{}", response.text().unwrap())
}
}

通过仓库下的
intput/build.sh
脚本构造镜像,运行yaml如下

apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: input-server
image: sidecar
-examples:input-http-server- name: input-sidecar
image: sidecar
-examples:sidecar-input

通过查看kubectl logs input input-http-server可以看到input-http-server收到了请求

receive a request!receive a request!

拦截请求sidecar

应用程序代码,它会每15s向
localhost
发出请求

package com.shoothzj.sidecar

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._

import scala.concurrent.{ExecutionContextExecutor, Future}
import scala.util.{Failure, Success}
objectHttpClient {
def main(args: Array[String]): Unit
={while (true) {
Thread.sleep(15_000L)
implicit val system: ActorSystem[Nothing] = ActorSystem(Behaviors.empty, "SingleRequest")//needed for the future flatMap/onComplete in the end implicit val executionContext: ExecutionContextExecutor =system.executionContext

val responseFuture: Future[HttpResponse]
= Http().singleRequest(HttpRequest(uri = "http://localhost:7979/hello"))

responseFuture
.onComplete {
case Success(res) =>println(res)case Failure(_) => sys.error("something wrong")
}
}
}
}

我们再来写一个sidecar,它会拦截http请求并打印日志

package com.shoothzj.sidecar

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._

import scala.concurrent.ExecutionContextExecutor
import scala.io.StdIn
objectHttpServer {

def main(args: Array[String]): Unit
={implicit val system: ActorSystem[Nothing] = ActorSystem(Behaviors.empty, "my-system")//needed for the future flatMap/onComplete in the end implicit val executionContext: ExecutionContextExecutor =system.executionContext

val route
=path("hello") {get{
println(
"receive a request")
complete(HttpEntity(ContentTypes.`text
/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}

val bindingFuture
= Http().newServerAt("localhost", 7979).bind(route)while (true) {
Thread.sleep(15_000L)
}
}
}

通过仓库下的
output/build.sh
脚本构造镜像,运行yaml如下

apiVersion: v1
kind: Pod
metadata:
name: output
spec:
volumes:
- name: shared-logs
emptyDir: {}

containers:
- name: output-workload
image: sidecar
-examples:output-workload
imagePullPolicy: Never
- name: sidecar-output
image: sidecar
-examples:sidecar-output
imagePullPolicy: Never

通过查看kubectl logs output output-workload可以看到output-sidecar收到了请求

HttpResponse(200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:15:47 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:16:02 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:16:17 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:16:32 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:16:47 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:17:02 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:17:17 GMT),HttpEntity.Strict(text/html; charset=UTF-8,31 bytes total),HttpProtocol(HTTP/1.1))
HttpResponse(
200 OK,List(Server: akka-http/10.2.9, Date: Tue, 29 Mar 2022 00:17:32 GMT),HttpEntity.Strict(text/html; charset=

点击关注,第一时间了解华为云新鲜技术~

今天是2023年最后一个工作日,hr 总监找上我协商赔偿一事,忆往昔三年前,公司刚融资1个亿,意气风发,博主入职即为公司巅峰,高级开发岗,14薪,各种福利,加班另算加班费,业务主要服务于众多500强集团公司,高并发,solr,redis,分布式mysql, app应用 + bs管理模型进行快速迭代, 那时公司还很年轻,人人都充满了干劲,刚走过了6个春秋,仅产研中心就有近80人, 什么神盾局,机制组,架构组眼花缭乱,本人也从一个新领域的小白成长到几乎能独立负责某个运营线的老鸟,从被动接受各种需求,会议上的唯唯诺诺,到指点江山,激昂文字,条例清晰阐述设计方案,溯源客户真实需求,稳准狠的找准产品设计的漏洞,可谓收获满满,日常开发中,也封装了数不胜数的低代码块,拆分并整合了很多耦合性高的业务代码,针对客户在不同场景下的不同需求,设计了新一套的抽奖算法,完善了job 动态发布机制.., 三年以来, 每次月底发版的留守名单中,常常有我,好多次半夜2,3点被打电话叫醒,处理了一个又一个紧急高危线上bug,那个时候我还很年轻,充满了干劲,其实从疫情第二年开始,外部环境就发生了可见的变化,路边门店纷纷集体关门歇业,持续的贸易战也让众多企业抬不起头,正如几乎刻进DNA记忆中的那段英语听力对话:

山路弯弯,峰回路转,‘前方转弯’几个大字一次次地冲击着他的眼球,也渐渐叩醒了他的心扉:原来,不是路已到了尽头,而是该转弯了。路在脚下,更在心中,心随路转,心路常宽。学会转弯是人生的智慧,挫折往往是转折,危机同时也是转机.

如上个东家一样,公司迷失在疯狂的扩张之路上,误判形式,一个又一个决策与社会的规律性发展背道而驰,走到如今这一步并不奇怪,年中一轮40%的裁员,加上年底一波抄底裁员,公司日常需求的产研人员已捉襟见肘,来几个线上单子或者请个假,项目进度就要停滞。目测已在山穷水尽之前苦苦支撑,所以能省则省,才有的 hr 在名单内逐个谈话,协商赔偿,几经拉扯仍没有结果,毕竟给的太少了,和之前一波名单的标准相比,相差太多,而且之前的标准也不高,差强人意罢了,又懒得和公司耗下去,目前还在你来我往的拉锯之中,谈话从来都是一个博弈的过程,包括面试也是,相互试探底牌,或者直接梭哈,现在就是梭哈拼结果,有的人可能会说员工不要和公司争,掰扯不过大腿,我就笑了,就是有这种想法的人太多,才导致越多越多的公司肆无忌惮的不断的降低底线来剥削员工,

A: 环境不好,公司经营不善,希望员工能理解公司的不容易。

B: 对,再差的环境,大A都有个别涨停股,为啥不能逆流而上,是不是决策出了问题,方向搞错了

A:  公司辉煌的时候,你也享受了真金白银14薪,现在经营问题,也要一起来承担

B:  这可不认同,14薪是写进招聘岗位上的,没有这个怎么吸引人面试,公司辉煌的时候,我加班加点累死累活工作,没见涨1毛钱, 现在公司低谷了,为啥要员工分担,

A:公司目前没钱,只有这么多,不可能为了某个人去更改标准

B:不患寡而患不均,之前都是这个标准,现在我们好欺负就随口改标准是吧,决策层的无能导致的问题为啥要让底层执行者来买单

博弈仍在继续,不过对于程序员来说,重要的是成长,包括心里上的,技术上的,并不是一定要争什么,更不是胡搅蛮缠,只是该得的必须争, 博主较为腼腆内向,经过多年社会毒打已不惧任何PUA,虽然情商并不如何高,甚至有些愚笨,也算走了一条自己的路子出来,虽然满是荆棘,易不惧

程序员有什么副业?

1. 接私活,

这个日常谈论的最多,相信很多园子里的大佬也都在做,一些微信小程序,公众号,简易bs的管理网站,上手快,做起来也不难,只要日常摸鱼时间多,搞搞还是不错的,缺点是业务可能不固定,单子并不是常有,要有自己的人脉等渠道,想我这样的人,天天傻呵呵的,精准2点一线的独居生活,工作之外0社交,几乎没有啥渠道, 毕竟信息差是永远留给有准备的人.

2. 自媒体

自媒体大多都是流量为王,前期集赞活跃人数比较难,公众号,订阅号,B站,小红书,抖音,快手,大鱼号等等 都可以发布视频,文档, 我刚好搞了一年的b站小游戏攻略, 说来惭愧,每个月投5,6个视频,如今有600多个粉丝,现在1万播放只有1块钱,所有的视频平台都在大砍收益,低流量基本都靠情怀,人人都想一下子来个爆款,但不是每个人都有这种气运,做之前要有准备,起码要有点子耐心, 公众号也有,但是推流太重要了,前期不仅没收入,可能要花点钱搞推广,除非是那种自带光坏的大佬,不过他们干啥都有人关注,有一众追随者,常人只能老老实实的一步一个脚印

3. 淘宝网店

网店打理其实挺累的,我之前就体验过兼职客服,非常累,尤其是京东自营店,有30秒必回复的限制,当执掌三个店以上,一下子十几个消息进来,几乎手忙脚乱,巴不得多长2只手, 淘宝网店的话要谈厂家代理,谈周边的快递商,有同事从1688上进货,现在维护了1年多,卖小商品,收入也不算稳定,要么东西多东西全,不然浏览量都上不去,除了实体,还可以卖课,卖资料,卖资源,卖游戏币,选择很多,之前有个大学室友卖会员卡,刚好赶上爆火的网播剧,一个星期挣了2万多块钱, 怎么说了,卖啥都挣钱,卖啥也都不挣钱, 很多都要靠前期的慢慢积攒人气

4. 肝游戏代练

小年轻们可以选择搞游戏代练,这个需要体力上的投入,以及对游戏的热爱,富贵险中求,赶上爆火的新游戏, 卖注册的账号都能赚钱,日常毒奶粉DNF代练的人最多,还有好多直播肝游戏的,究竟收益如何,我也不清楚,这一块属于盲区

程序员怎么面对行业萧条?选择非常多

1.继承亿万家产的家族企业,投胎是一门玄学

2.积极备战,考公考编,毕竟这是宇宙的尽头,只要是私企,最后都有可能歇业

3.积极备题,面试, 努力提升自我,迎接下一轮的挑战,只要我不倒,就没有什么能打倒我

4.自主创业,做大老板,做大做强,再创辉煌, 赢取白富美,走上人生巅峰

5.回乡躺平,提前享受退休人生

当华发早生,腰腿颈椎不如之前灵活,不在一口气冲5楼,一份工作的考量的标准可能会变,不再是钱/人/环境,还有其他需要考量的事物,只愿能守住一份本心,愿你我走过半生,不论身处何地,精气神常在,归来仍是少年,

毕竟我才28啊

原创/朱季谦

2023年12月初,傍晚,在深圳的小南山看了一场落日。

那晚我们坐在山顶的草地上,拍下了这张照片——仿佛在秋天的枝头上,结出一颗红透的夕阳。

image

这一天很快就会随着夜幕的降临,化作记忆的碎片,然而,总会有一些难忘的痕迹,在逐渐落满灰尘的回忆里,熠熠生辉。这些痕迹,或许是一张随手拍下的照片,或许是聊天记录里的只言片语,又或许,只是朋友圈里一个普通动态......直到很久以后,突然翻到关于这一天的记录,死去的记忆总会从四面八方赶回来,透过这张照片,我一定会想起,那天曾和喜欢的人坐在山顶上一起看过的夕阳。

这难道不是刻舟求剑的另一种体现吗?

2023年就如那天的落日一样,渐渐沉入了岁月的大海。

在这艘岁月的船上,总得刻一点什么文字,方便日后再想起这一年时,不至于一片空白。

下面就分段写一下这一年印象深刻的事情。

一、获得腾讯云社区2022年度最佳作者

在今年年初的时候,很荣幸获得腾讯云社区颁发的“腾讯云社区2022年度最佳作者奖”,这是我写原创编程技术博客以来获得过的第一块奖杯,可以说,意义非凡。

它像是技术写作道路上的一块小里程碑,给了我不少的鼓励。

想起当初刚开始写技术类文章时,就像脚穿千斤鞋,举步维艰,无从下手——虽然在此之前,我已写过近百万字的网络小说和短篇小说随笔等,但是,第一次开始去写技术类文章,还是比较困难的。

现在回过头去看第一篇技术文,可以说很是稚嫩。

虽然现在写得仍是一般,但经过这两年的刻意练习,至少已经不像最初那样举步维艰和无从下手,相反,逐渐有一种得心应手的感觉。

或许,这就是刻意练习之后的熟能生巧,当然,若是展望未来的话,更希望能在熟能生巧的基础上,熟能生“新”。
image

二、拿到驾照

若是问我在大学的时候,比较遗憾的事情之一,考驾照肯定算一件。

以致于想买车的时候,却连驾照都没有考。

想起当时在深圳报考驾照的时候,还是很担心会踩雷,还好,特别幸运的是,遇到一个很好的教练,在2022年11月底报名,在2023年3月初就顺利拿到驾照。

我在年初有个目的之一是,今年一定要拿到驾照,算是按照目标完成了。
image

三、跟上ChatGPT潮流

今年上半年,可能最具爆炸性的新闻就是GPT模型了。

3月份那会儿,在自己的云服务器上通过Docker 部署一套前端仿写Chat GPT页面而内部真实通过ChatGPT 密钥调用外网接口的应用。这样一番操作下来,家里的数台电脑都无需再通过外网即可一键使用GPT。

后来,按照同样方式,给办公司开发团队部署了一套这样的Docker,使得内部团队早早就在工作当中用上了GPT。

直到现在,这套部署仍然运行在我的云服务器上,成为日常工作编程的绝佳助手。

当然,在这个过程当中,2月和3月份那会红利期,通过教人部署以及其他相关提供服务的途径,做了一些变相的尝试,虽然赚的不多,但总归是一次副业尝试。

四、大数据开发技术栈提升

今年比较幸运,有机会参与到内部大数据图计算前沿项目的开发,围绕这个项目,这近一年里将Spark、HBase、Hive、Hadoop、Sqoop等相关的大数据技术栈都在实际开发过程中掌握,算是在后端开发的基础上,拓展大数据领域的开发。

当然,在这个过程当中,还阅读了Spark Core以及Graphx相关的源码,同时还把阅读的心得写成文章,发布到社区,例如《
图解Spark Graphx基于connectedComponents函数实现连通图底层原理
》、《
图解Spark Graphx实现顶点关联邻接顶点的collectNeighbors函数原理
》这两篇,其中,《图解Spark Graphx基于connectedComponents函数实现连通图底层原理》获得了NebulaGraph 技术社区 2023 年年度征文奖。
image

五、2023年各个博客平台的写作数据

之前写过一个总结这些年写作的感悟:完成比完美更重要。

特别对于新手而言,最好的写作方式,就是不断去写,带着“不要脸”和“老子就是造翔也要写完的心态”去写。写到脑子不够用了,就去看书、看视频等等做各种知识的输入。

写出个上百万字后,你就会发现,当初手里写出的那一坨翔,在多年以后,正逐渐变成文章里所有发光的起源。

每次遇到卡文时,我都是这样告诉自己的,硬着头皮写下去,只有不断写完,不断总结,下一篇才能写得更好。

而不是一上来,就要写出惊天地泣鬼神的完美作品。

这两年开始坚持写技术博客,总体而言,有收获,有不足。

今年记录下来,希望明年的这个时候,对比之下,有一个更好的成长。

5.1、腾讯云社区

说实话,在腾讯云社区退步了些,上半年名次最好的时候是到三四十名左右,中间有两三个月没怎么发文,排名就一直下降。在众多平台里,我算是比较喜欢腾讯云社区,比较有温度,遇到问题,能够及时得到相应的解决,还有很多值得参与的活动。

与此同时,在这里遇到很多优秀的技术写作者,从他们身上,找到了不少榜样的动力。
image

5.2、CSDN社区

今年比较有突破一点是,入围了CSDN的2023年博客之星,同时关注量超1000了,希望明年这个时候,争取可以拿到TOP名次。
image

5.3、博客园

最开始技术写作平台,目前园子正经历一段苦难期,还是真心希望它能继续做下去。
image

5.4、思否

今年开始在SegmentFault思否写东西,很开心之前有一篇《Dubbo2.7的Dubbo SPI实现原理细节》得到了SegmentFault 思否写作挑战赛:写文章领取技术图书活动奖品,算是一次不错的进步。

希望再接再厉,争取今年可以有更多突破。

image

写了这么多年东西之后,还有一件比较有成就感的事情是,网上搜自己的名字,发现满屏搜出来的,都是自己这些年的产出及相关信息。

在大学的时候,写过很多短篇小说,还有一些长篇小说。

遗憾的是,大学毕业后,开始程序员打工仔生涯之后,就很少写故事了,时间都花在学习各种技术上。

然而,这两年,特别是这一年以来,想写小说的欲望又开始慢慢浮现。或许是经历了一些亲人的离世,明白人总会有一死,那就在生命的终点前,去做一些自己热爱的事情。

我在今年上半年,化名接受过一次谷雨实验室-腾讯新闻一场关于副业的采访,当时有提到自己业余在做的事情。(沈颜云是我正在写的悬疑长篇里的主角名,当时小姐姐问我这篇采访用什么名字好,我就想用自己故事里的主角名了。)
image

这两本小说,今年一直在存稿,计划是明年顺利投网站走分成签约。

结果无论如何,都希望能继续写下去,无论是技术类写作,还是故事类写作。

六、展望2024年

关于2024年,有以下几个规划:

6.1、技术写作方面:输出同系列内容,希望能写出一本完整的技术电子书。

6.2、故事写作方面:任意一本小说投稿签约成功,连载完结。

6.3、扎实技术的深度能力,阅读更多的框架底层源码,总结成内容输出。

6.4、提高演讲能力和管理能力。

6.5、坚持阅读。

6.6、往出书的方向努力。

背景

有时兴致来了就喜欢瞎鼓捣,几年前还是学生时买过学生优惠的云服务器,但没钱续费关停后就不了了之,近期看到有活动又重新入手了
但问题就来了,之前好不容易搭建上去的各种服务,现在又得重新来一遍
几年前还是学生时可能对这类环境搭建还比较感兴趣
现在人老了,精力不够了,做啥都考虑效率问题
如果几年后又重新买了,岂不是又得重新来一遍?

所以啊,还是得搞个一键式操作,来把这类基础、重复且低效的准备工作改造成自动化

诉求

覆盖我日常使用的服务有:

  • 个人博客、笔记平台
    • 方便维护、查阅我过往积累的博客和笔记
  • NextCloud
    • 个人云网盘,也支持在线文档编辑、查阅(如在线 office)
  • UI 组件使用说明平台
    • 方便我查阅过往封装的通用 UI 组件的使用文档
  • Jenkins
    • 方便我自动化管理各个云服务

那么,该怎么做呢?

准备工作

如果是第一次搞,那么还是有些准备工作,就绪后,随便在一台新的云服务执行下自动化脚本完事

1. 资料、代码都上 github

  • 把博客笔记平台的各个 md 文档、图片资源等都存放到 github 管理
  • 把组件使用平台的项目代码也 github 管理
  • 把创建 docker 的相关配置文件也 github 管理

既然要方便一键式部署,就把各种资料都存储到网上,免费且稳定的应该就是 github 了,这样后续就可以写个自动化脚本去自动拉取各个资料了

2. 编写 docker-compose.yml 和各服务的 Dockerfile

各个云服务通过 docker 来部署,环境搭建就可以做到一份配置,到处部署的效果了。

目录结构说明

├─ doc # 博客、笔记平台,用 nginx 做容器
│ ├─ Dockerfile
│ ├─ nginx.conf
├─ jenkins # jenkins 平台,预置好 node, pnpm 等环境
│ ├─ Dockerfile
├─ nginx # 监听默认端口(80,443),根据二级域名做反向代理
│ ├─ Dockerfile
│ ├─ nginx.conf
├─ uidoc # 组件库使用说明平台,用 nginx 做容器
│ ├─ Dockerfile
│ ├─ nginx.conf
├─ docker-compose.yml # 上述容器的统一管理、通信配置

jenkins 容器的 Dokcerfile

# 使用基于 Debian 的 Jenkins 镜像作为基础
FROM jenkins/jenkins:lts

# 切换到 root 用户
USER root

# 安装 Node.js
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs

# 安装 pnpm
RUN npm install -g pnpm

# 安装 yarn
RUN npm install -g yarn

# 切换回 Jenkins 用户
USER jenkins

EXPOSE 8080

nginx 容器的反向代理配置

server {
    listen  80;
    listen [::]:80;
    server_name *.dasu.fun;
    client_max_body_size 1024M;

    location / {
      # 正则匹配二级域名,并赋值给变量 $domain
      if ($http_host ~* "^(.*?)\.dasu\.fun$") {
        set $domain $1;
      }
      # 根据二级域名,做反向代理转发
      if ($domain ~* "jenkins") {
        proxy_pass http://192.168.5.104:8080;
      }
      if ($domain ~* "blog") {
        proxy_pass http://192.168.5.105;
      }
      if ($domain ~* "uidoc") {
        proxy_pass http://192.168.5.106;
      }
      if ($domain ~* "nextcloud") {
        proxy_pass http://192.168.5.108;
      }
      if ($domain ~* "doc") {
        proxy_pass http://192.168.5.110;
      }
      proxy_set_header Host	$host;
      proxy_set_header X-Real-IP	$remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://192.168.5.110;
    }
}

docker-compose.yml 配置

version: "3"

services:
  nginx:
    build: ./nginx/ # 使用位于 ./nginx/ 目录中的 Dockerfile 来构建容器镜像
    ports:
      - "80:80" # 将容器的 80 端口映射到主机的 80 端口
    restart: always # 容器停止后自动重启
    networks:
      n_nginx:
        ipv4_address: 192.168.5.103 # 为容器分配指定的 IPv4 地址

  jenkins:
    build: ./jenkins/ # 使用位于 ./jenkins/ 目录中的 Dockerfile 来构建容器镜像
    ports:
      - "9001:8080" # 将容器的 8080 端口映射到主机的 9001 端口
      - "50000:50000" # 将容器的 50000 端口映射到主机的 50000 端口
    user: root # 在容器中以 root 用户身份运行
    restart: always # 容器停止后自动重启
    volumes:
      - /root/Doc/:/var/jenkins_home/doc/codes/ # 将主机的 /root/Doc/ 目录挂载到容器的 /var/jenkins_home/doc/codes/ 目录
      - /root/uidoc/:/var/jenkins_home/uidoc/codes/
      - /root/.ssh/:/root/.ssh/:ro # 将主机的 /root/.ssh/ 目录挂载到容器的 /root/.ssh/ 目录,并设置为只读
      - /etc/localtime:/etc/localtime:ro # 将主机的 /etc/localtime 文件挂载到容器的 /etc/localtime 文件,并设置为只读
    networks:
      n_nginx:
        ipv4_address: 192.168.5.104 # 为容器分配指定的 IPv4 地址

  uidoc:
    build: ./uidoc/ # 使用位于 ./uidoc/ 目录中的 Dockerfile 来构建容器镜像
    ports:
      - "9002:80" # 将容器的 80 端口映射到主机的 9002 端口
    restart: always # 容器停止后自动重启
    volumes:
      - /root/uidoc/dist/:/usr/share/nginx/html/
    networks:
      n_nginx:
        ipv4_address: 192.168.5.106 # 为容器分配指定的 IPv4 地址

  db:
    image: postgres # 使用 PostgreSQL 镜像
    restart: always # 容器停止后自动重启
    environment:
      - POSTGRES_PASSWORD=222zaqXSW! # 设置 PostgreSQL 数据库的密码
      - POSTGRES_USER=postgres # 设置 PostgreSQL 数据库的用户名
    volumes:
      - /root/postgres/data:/var/lib/postgresql/data # 将主机的 /root/postgres/data 目录挂载到容器的 /var/lib/postgresql/data 目录
    expose:
      - "5432" # 暴露容器的 5432 端口给其他容器使用
    networks:
      n_nginx:
        ipv4_address: 192.168.5.107 # 为容器分配指定的 IPv4 地址

  nextcloud:
    image: nextcloud # 使用 Nextcloud 镜像
    restart: always # 容器停止后自动重启
    ports:
      - 9003:80 # 将容器的 80 端口映射到主机的 9003 端口
    depends_on:
      - db # 依赖于 db 服务,确保数据库服务在 Nextcloud 服务启动之前已经启动
    environment:
      - POSTGRES_HOST=db # 设置 Nextcloud 使用的 PostgreSQL 数据库的主机为 db
      - POSTGRES_DB=postgres # 设置 Nextcloud 使用的数据库名称
      - POSTGRES_USER=postgres # 设置 Nextcloud 使用的数据库用户名
      - POSTGRES_PASSWORD=222zaqXSW! # 设置 Nextcloud 使用的数据库密码
    volumes:
      - nextcloud:/var/www/html # 将名为 "nextcloud" 的卷挂载到容器的 /var/www/html 目录
    networks:
      n_nginx:
        ipv4_address: 192.168.5.108 # 为容器分配指定的 IPv4 地址

  doc:
    build: ./doc/
    ports:
      - "9005:80"
    restart: always
    volumes:
      - /root/Doc/dist/:/usr/share/nginx/html/
    networks:
      n_nginx:
        ipv4_address: 192.168.5.110 # 为容器分配指定的 IPv4 地址

networks:
  n_nginx:
    driver: bridge # 使用桥接网络模式
    ipam:
      config:
        - subnet: 192.168.5.0/24 # 定义网络的子网地址范围为 192.168.5.0/24,宿主机一般会是该网段的 .1,所以不要将网段设置为 1

# 命名卷是多容器共享卷,具有持久化能力
volumes:
  nextcloud:

3. 购买域名、备案和配置 DNS 解析

购买和备案自行参考域名购买的平台指引
由于我使用到多个云服务,而且是通过二级域名来区分,因此需要配置下各个二级域名的解析,如:

主机记录(二级域名) 记录值(云服务器 IP)
uidoc 59.110.12.xx
doc 59.110.12.xx
netcloud 59.110.12.xx
jenkins 59.110.12.xx
blog 59.110.12.xx
www 59.110.12.xx

4. 云服务器放开入端口

如果没有购买域名,或者需要直接用 ip+port 访问不同服务,那么需要放开对应的端口,以阿里云为例,在域名控制台-安全组-访问规则-入方向里配置:

自动化脚本一键式部署

1. 云服务生成 ssh

由于所有的资料都传到 github 上了,因此需要先把云服务器的 ssh 配置到 github 上,以便服务器有权限拉取 github 项目

  • ssh-keygen -t rsa -b 4096 -C "xxx@qq.com"
  • cat .ssh/id_rsa.pub
  • 将第二步输出的公钥复制到 github 的 ssh 配置

2. 在服务器上执行脚本

cat << 'EOF' > setup.sh

#!/bin/bash

# 函数:打印日志
log() {
  echo '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'
  echo "$1"
}

# 更新软件库
sudo yum update -y
# 安装 Docker 环境
log "开始安装 Docker 环境..."
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak
sudo systemctl start docker
sudo usermod -aG docker $USER
docker -v
log "Docker 环境安装完成。"

# 安装 Docker Compose 环境
log "开始安装 Docker Compose 环境..."
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
log "Docker Compose 环境安装完成。"
docker-compose --version

# 安装 Git 环境
log "开始安装 Git 环境..."
sudo yum install -y git
log "Git 环境安装完成。"
git --version

# 安装 nvm 和 Node.js 环境
log "开始安装 nvm 和 Node.js 环境..."
curl -o- https://gitee.com/mirrors/nvm/raw/v0.39.0/install.sh | bash
source ~/.bashrc
nvm --version
nvm install --lts
npm config set registry https://registry.npm.taobao.org
log "nvm 和 Node.js 环境安装完成。"
node -v

# 安装 Whistle 环境
log "开始安装 Whistle 环境..."
npm install whistle -g
log "Whistle 环境安装完成。"
#w2 start

# 安装 pnpm
log "开始 pnpm..."
npm install -g pnpm
pnpm -v

# 安装 yarn
log "开始 yarn..."
npm install -g yarn
yarn -v


# 拉取 github 仓库
log "拉取 github 仓库..."

cd /root/
mkdir blog
mkdir github
mkdir postgres

echo -e "Host github.com\n  StrictHostKeyChecking no" >> ~/.ssh/config
git clone git@github.com:woshidasusu/dockers.git

cd /root/blog
git clone git@github.com:woshidasusu/woshidasusu.github.io.git

cd /root/
git clone git@github.com:woshidasusu/Doc.git
cd Doc
pnpm install
pnpm run build


cd /root/
git clone git@github.com:woshidasusu/uidoc.git
cd uidoc
npm install
npm run build


log "所有环境安装完成。"
docker -v
docker-compose --version
node -v


EOF

  • 执行后,会在当前目录下生成一份 setup.sh 文件,继续执行:
  • chmod +x setup.sh
    • 将文件设置成可执行
  • bash setup.sh
    • 执行脚本

脚本会自动去安装 docker, docker-compose, git, nvm, node, whistle 以及拉取 github 的项目
注:有些下载源是 github 的可能会失败,如果失败了需要手动执行,通常是 docker-compose 和 nvm 可能出现安装失败

docker-compose up -d

进入上述脚本下载的 dockers 目录,在该目录执行:

  • docker-compose up -d

执行结束后,各服务容器就会创建并运行起来了,这时候浏览器访问相关服务试试看就完事了


搞完这些后,我可以随便格式化云盘,每次重新搭时,先配置个 ssh,再执行下脚本,服务就都自动搭建完毕,舒服~