一、引言
最近公司在海外上云服务器,作者自己也搞了云服务器去搭建部署系统,方便了解整体架构和系统的生命周期,排查解决问题可以从原理侧进行分析实验。虽然用的云不是同一个,但是原理都是相通的。
二、选型
作者选用的是腾讯云,没别的原因,就是便宜加牌子大。
阿里云肯定是更好一些的,不管是服务售后还是服务器内核和操作系统都是比较活跃的,毕竟作者以前公司用的就是阿里云,了解一些。
不过以前都是和运维沟通排查问题,作者自己只能看到一些服务器监控和运维的截图,这对于了解整个云服务架构体系的生命流程是不太友好的。
操作系统选择了CentOS7.6-Docker20,毕竟linux的底层是必须的,目前的容器环境也是服务的基础。
基本参数如下,没必要太好
- CPU - 2核 内存 - 4GB
- 系统盘 - SSD云硬盘 60GB管理快照
- 流量包 - 500GB/月(带宽:5Mbps)
三、系统搭建部署
作者准备把springboot用Maven打包,jar包拿到docker容器运行
3.1、后端
后端是SpringBoot,SSM框架,代码就不贴了,作者写了个小程序给家里人用的。
3.2、打包
这一步很麻烦,打出来的包很小,作者当时还没意识到问题,本地java -jar运行一下,报错了。显示no main manifest attribute, in /**.jar,问了一下chatGpt,这就很扯,做了这么久的SpringBoot,他里面的application怎么可能不是字段设置为主类的呢。
作者有找了网上的一些文章,有的说是打包的时候没有设置入口类,pom的build重新设置一下。
<build>
<plugins>
<!-- maven-compiler-plugin 插件配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- maven-jar-plugin 插件配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>**.Application</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 其他插件... -->
</plugins>
</build>
然后出现了新的错误:Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
跟chatgpt拉扯了许久,还是没找到正确的pom,又找了朋友看以前做的项目,其实很简单的po,完全依赖spring自动打包发现
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
打包之后有60几M大小,之前只有几百k,所以很明显了,再次本地运行java -jar,显示成功,通过Localhost也可以访问里面的接口
3.3 部署
部署系统jar之前,需要先把数据库部署好,不然根本运行不起来。
1、部署mysql
这里安装的的是mysql5.7,习惯了这个版本
下载MySQL 5.7的Docker镜像:docker pull mysql:5.7
下载完成后,可以运行以下命令来创建并运行MySQL容器:docker run --name mysql -e MYSQL_ROOT_PASSWORD=<password> -p 3306:3306 -d mysql:5.7
创建一个名为`mysql`的容器,并将MySQL的默认端口3306映射到主机的3306端口。将`<password>`替换密码。
检查MySQL容器是否正在运行:docker ps -a
如果看到`mysql`容器正在运行,说明MySQL已经成功安装并运行在Docker中。
如果MySQL容器未运行,启动它:docker start mysql
登录到MySQL:docker exec -it mysql mysql -u root -p
检查MySQL服务器的运行状态:service mysql status
启动它:service mysql start
作者在这中间还走了一些弯路,一开始不想设置用户名密码,结果导致他自动生成了一串,作者还不知道密码是什么,又进去修改密码,最后还生成了两个root账号,一个权限是*一个是localhost,导致后面jar运行出了问题。
2、部署服务
腾讯云界面上有个SFTP的文件可视化,作者一开始不知道,还准备搞市面上的一些文件传输软件,毕竟jar包需要传输到远程服务器。
首先要创建一个Dockerfile,这个文件没有后缀的,在服务器窗口建好文件夹之后
输入:vi Dockerfile
从vi界面搭建i编辑,自动创建文件,写完内容之后点击esc退出编辑状态,再点击:wq退出文件并且保存。
Dockerfile里面内容:
# 基础镜像使用java
FROM openjdk:8-jdk-alpine
# VOLUME 指定了临时文件目录为/tmp。
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp
ADD test-provider-service-0.0.1.jar /test-provider-service.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/test-provider-service.jar"]
1. `FROM openjdk:8-jdk-alpine`:指定使用OpenJDK 8的Alpine Linux作为基础镜像。
2. `VOLUME /tmp`:指定将主机的`/var/lib/docker`目录与容器的`/tmp`目录进行挂载,用作临时文件目录。
3. `ADD test-provider-service-0.0.1.jar /test-provider-service.jar`:将当前目录下的`test-provider-service-0.0.1.jar`文件复制到容器的根目录,并将其重命名为`test-provider-service.jar`。
4. `ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/test-provider-service.jar"]`:指定容器启动时要执行的命令。这里是运行Java应用程序的命令,使用`java`命令执行`test-provider-service.jar`文件。
然后把jar传入服务器,jar要和Dockerfile在一个文件夹里面。
创建docker镜像:docker build -t ** .
这个.是指使用当前文件夹,所以要先cd到jar所在的文件夹,**就是指容器的名字。
运行容器:docker run -d -p 5006:5006**
用的是宿主机5006端口,容器的5006也是作者springboot用的端口,默认springboot是用8080的,这个要在配置文件设一下
3、问题排查解决
3.1、root拒绝
docker ps -a看一下容器状态,诶嘿,退出来了exit,说明jar有问题,但是docker logs ** 有看不到日志,那就加个日志打印。
public class Application {
public static void main(String[] args) {
try{
SpringApplication.run(Application.class, args);
} catch (Throwable t) {
System.out.println(t);
throw t;
}
}
}
先得把之前的容器和镜像删除,然后重新生成,删除容器需要用容器id或者名称,在docker ps -a中可以看到:docker rm **
删除镜像:docker rmi **
爆出来的是:access denied for user 'root'@'ip' (using password: YES)
一开始还以为是配置错了账号密码,在本地又跑了一下,但是很明显没有问题,
看了一下mysql,在MySQL的user表中,每个用户都有一个唯一的组合键,由用户名(User)和主机名(Host)组成。这意味着即使用户名相同,只要主机名不同,就会被视为不同的用户。我猜测的是root这个%会拦截所有的root访问,因为在本地用workBench去链接显示的也是拒绝。但是%的root不是我设置的密码,所以得加一个新的用户密码。
root | localhost
root | %
创建一个具有相同权限的新用户,但允许从任何主机访问MySQL服务器:
CREATE USER 'new_user'@'%' IDENTIFIED BY 'password';GRANT ALL PRIVILEGES ON *.* TO 'new_user'@'%' WITH GRANT OPTION;
3.2、链接mysql失败
再次运行镜像爆出了不一样的错误:
Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]: Unsatisfied dependency expressed through method 'sqlSessionFactory' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [com/alibaba/druid/spring/boot/autoconfigure/DruidDataSourceAutoConfigure.class]: Invocation of init method failed; nested exception is com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
很明显是mysql的链接问题,但是为什么会出现这种问题,问一下chatgot
说的很官方,没有什么实际作用,但是原理是相同的,要么是驱动程序问题,要么是网络问题,如果是驱动程序应该不可能出现本地运行成功,服务器失败。
那么就要思考服务器的docker容器和本地运行到底有什么区别,作者想到的是mysql和服务jar运行在两个容器当中,本地访问服务器的mysql是通过公网ip,那么在服务器jar里面的配置是使用127.0.0.1(localhost)去链接mysql,那么是不是不能直接使用localhost去链接。
因为云服务器的容器不一定在同一个宿主机上面,即使在同一个宿主机,分配端口维度也可能是容器,这个就看每个云厂商怎么设置的规则了。
带着这样的猜测,作者把配置文件改为使用内网ip,结果成功了。
四、总结
通过自己安装部署云服务器上的系统,作者对于整个系统架构有了更多了解,实践出真知,对于技术工具实验和底层原理排查都有帮助。