项目发布流程
war包发布
-
服务器上创建新的docker容器(新建一个tomcat包),名称以功能用途命名(eg:java-hello)。
* mkdir java-hello
-
登录对应git,在settings-CI/CD-variables下设置新增包(eg:java-hello)路径
-
切换项目到需要自动发布分支(eg:application-hello.yml )
-
修改项目.gitlab-ci.yml文件,新增build与run,修改监控分支与路径
.gitlab-ci.yml
variables:
MAVEN_OPTS: "-Djava.awt.headless=true -Dmaven.repo.local=./.m2/repository"
cache:
paths:
- ./.m2/repository
stages:
- build
- run
image: maven:3-jdk-8
build:
retry: 1
only:
- hello
stage: build
tags:
- java-runner
script:
- mvn clean package -T 1C -Dmaven.compile.fork=true -Dmaven.test.skip=true -Phello
cache:
paths:
- target/$PACKAGE
run:
retry: 1
only:
- hello
stage: run
tags:
- java-runner
script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $DEPLOY_SERVER >> ~/.ssh/know_hosts
- chmod 644 ~/.ssh/know_hosts
- scp -o StrictHostKeyChecking=no -P 22 target/$PACKAGE root@$DEPLOY_SERVER:$DEPLOY_HELLO
- ssh -o StrictHostKeyChecking=no -p 22 root@$DEPLOY_SERVER "cd $DEPLOY_HELLO; chmod +x $DEPLOY_SHELL; ./$DEPLOY_SHELL"
build_rel:
retry: 1
only:
- release #监控分支
stage: build
tags:
- java-runner
script:
- mvn clean package -T 1C -Dmaven.compile.fork=true -Dmaven.test.skip=true -Prel #rel为对应环境
cache:
paths:
- target/$PACKAGE
run_rel:
retry: 1
only:
- release #监控分支
stage: run
tags:
- java-runner
script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $DEPLOY_SERVER >> ~/.ssh/know_hosts
- chmod 644 ~/.ssh/know_hosts
- scp -o StrictHostKeyChecking=no -P 22 target/$PACKAGE root@$DEPLOY_SERVER:$DEPLOY_RELEASE
- ssh -o StrictHostKeyChecking=no -p 22 root@$DEPLOY_SERVER "cd $DEPLOY_RELEASE; chmod +x $DEPLOY_SHELL; ./$DEPLOY_SHELL"
# 修改root@$ + git配置对应路径
api-redeploy.sh
#!/bin/bash
docker_container_name=java-hello #docker容器名称
java_package_name=java-hello.war #项目名称
rm -rf ./webapps/ROOT
unzip -q $java_package_name -d webapps/ROOT
docker restart $docker_container_name
- 将war包上传到java-hello/tomcat下
- 上传war包后通过命令运行docker
docker run -d --name=java-hello --restart=always -e TZ="Asia/Shanghai" -v /disk/logs/docker/apps-logs/java-hello/logs:/home/logs -v /disk/logs/docker/apps-logs/java-hello/tomcat:/usr/local/tomcat -v /disk/logs/docker/apps-logs/java-hello/export:/exprot -v /etc/localtime:/etc/localtime -p 18088:8080 tomcat:9.0.26-jdk8
name 对应docker名(包名)
/disk/logs/docker/apps-logs/java-hello/logs 对应报下log日志包
/disk/logs/docker/apps-logs/java-hello/tomcat tomcat运行包
/disk/logs/docker/apps-logs/java-hello/export 导出包
-p 18088:8080 18088为对外映射端口,8080为默认启动端口
jar包发布
- 项目打可运行jar包
Dockerfile 文件
FROM openjdk:8
MAINTAINER Lee
VOLUME ["/etc/localtime:/etc/localtime:ro"]
ADD demo.jar app.jar
RUN echo 'Asia/Shanghai' >/etc/timezone
EXPOSE 8080
CMD ["java","-jar", "/app.jar", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8080"]
api-redeploy-jar.sh
#!/bin/bash
docker_container_name=java-hello
docker_image_name=java-hello
docker_mapping_name=java-hello
docker stop $docker_container_name
docker rm -f $docker_container_name
docker rmi $docker_image_name
docker build -t $docker_image_name .
docker run -d -p 18055:8080 --name $docker_mapping_name $docker_image_name
- Dockerfile 、api-redeploy-jar.sh、可运行jar包上传linux
- 在上传的Dockerfile ,可运行jar包文件下执行以下命令
docker build -t 镜像名 . #注意末尾的"."不要忘了
docker run -d -p 8089:8089 --name jy_gentle gentle
# -d 后台运行、
# 最后一个 gentle 是引用的镜像的名字、
# --name jy_gentle 给容器取名为 jy_gentle (取名参数前面是两短横线)、
# -p 8089:8089 端口映射,注意是小写 p
# 前一个 8089 是对外浏览器上访问的端口,后一个 8089 是容器内工程本身的端口,两者可不一样
docker-compose发布方式
- 将所需发布项目打包,打包方式为jar包
- 在服务器上创建一个新的docker容器,名称建议以功能用途命名便于理解
- 将打包的jar包上传到新建的docker容器下
- 创建docker-compose.yml,将docker-compose上传到docker容器下。
docker-compose.yml
version: '1'
services1:
java-hello: #服务名称
image: java-hello #镜像名称
container_name: java-hello #容器名称
restart: "always" #重启策略,能够使服务保持始终运行,生产环境推荐使用
ports:
- 8089:8080 #暴露端口信息
volumes:
- /etc/localtime:/etc/localtime:ro #挂载文件
services2:
java-hello1: #服务名称
image: java-hello1 #镜像名称
container_name: java-hello1 #容器名称
restart: "always" #重启策略,能够使服务保持始终运行,生产环境推荐使用
ports:
- 8090:8080 #暴露端口信息
volumes:
- /etc/localtime:/etc/localtime:ro #挂载文件
- 建Dockerfile文件,并上传到docker容器下
Dockerfile文件
FROM openjdk:8 #jdk版本
MAINTAINER Lee #作者
VOLUME ["/etc/localtime:/etc/localtime:ro"] #挂在路径
ADD demo.jar app.jar #项目jar包
RUN echo 'Asia/Shanghai' >/etc/timezone
EXPOSE 8080
CMD ["java","-jar", "/app.jar", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8080"]
startup.sh运行脚本
#!/bin/bash
docker build -t java-hello .
docker-compose -f docker-compose.yml up -d
- 在docker-compose.yml所在路径下执行docker-compose up命令,compose就会自动构建镜像并使用镜像启动容器
所有可能用到命令
- docker ps -a: 查看docker运行与端口占用
- docker logs -f -t --tail 1000 docker名 :查看日志
- docker stop dockerID : 停止对应docker
- docker start dockerID : 运行对应dokcer
- docker rm dockerID : 杀死对应docker容器
- docker rmi dockerID: 杀死对应镜像
- mkdir 包名
- cp -r /disk/logs/docker/apps-logs/empty/* /disk/logs/docker/apps-logs/包名/tomcat/
GIT CI/CD下定义变量
DEPLOY_SERVER :127.0.0.1
DEPLOY_SHELL : api-redeploy.sh
PACKAGE : demo.war
DEPLOY_SHOW : /disk/logs/docker/apps-logs/java-hello/tomcat
job内定义作业流程的参数列表
Keyword | Required | Description |
---|---|---|
script | yes | 定义在runner中执行的命令 |
extends | no | Defines a configuration entry that this job is going to inherit from |
include | no | Defines a configuration entry that allows this job to include external YAML files |
image | no | Use docker image, covered in Using Docker Images |
services | no | Use docker services, covered in Using Docker Images |
stage | no | 定义job属于哪个阶段,默认test阶段 |
type | no | stage别名 |
variables | no | 定义job层次的变量 |
only | no | 定义哪些分支或tag的修改触发该流程 |
except | no | 定义哪些分支或tag的修改不触发该流程 |
tags | no | 定义哪个标签的runner来执行,该标签指runner配置时的名称,不是Git的tag分支 |
allow_failure | no | Allow job to fail. Failed job doesn’t contribute to commit status |
when | no | Define when to run job. Can be on_success, on_failure, always or manual |
dependencies | no | 定义该job依赖于哪项job的结果,用于把之前job的附件传进来 |
artifacts | no | 定义job产生的附件,可用于下载和保存以及传递,没有该项设置产生的过程文件都会被删除 |
cache | no | 定义缓存的文件或文件夹,如果是在job外定义则为全局变量 |
before_script | no | 定义job执行前的操作 |
after_script | no | 定义job执行后的操作 |
environment | no | Defines a name of environment to which deployment is done by this job |
coverage | no | Define code coverage settings for a given job |
retry | no | 定义任务失败后的重复执行次数或时间 |
parallel | no | 定义并行的任务数量,限于2~50 |
trigger | no | Defines a downstream pipeline trigger |
出现过的问题
- springboot通过外置war包启动时404
@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DemoApplication.class);
}
jar包和war包启动区别:
jar包:执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式Servlet容器
war包: 先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器
Servlet 3.0+规则:
1 服务器启动(web应用启动),会创建当前web应用里面所有jar包里面的ServletContainerlnitializer实例
2 ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下
3 还可以使用@HandlesTypes注解,在应用启动的时候加载指定的类。
外部Tomcat流程以及原理:
① 启动Tomcat
② 据上述描述的Servlet3.0+规则,可以在Spring的web模块里面找到有个文件名为javax.servlet.ServletContainerInitializer的文件,而文件的内容为org.springframework.web.SpringServletContainerInitializer,用于加载SpringServletContainerInitializer类
③ SpringServletContainerInitializer定义
在上面一段长长的注释中可以看到,SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer这个类型的类都传入到onStartup方法的Set参数中,并通过反射为这些WebApplicationInitializer类型的类创建实例;
④ 方法最后,每一个WebApplicationInitilizer实现调用自己onstartup方法
⑤ 而WebApplicationInitializer有个抽象实现类SpringBootServletInitializer(记住我们继承了该抽象类),则会调用每一个WebApplicationInitializer实例(包括SpringBootServletInitializer)的onStartup方法:
SpringBootServletInitializer实例执行onStartup方法的时候会通过createRootApplicationContext方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。
- springboot是通过内嵌tomcat启动,在使用外置tomcat启动war是缺少web.xml
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
protected Log logger; // Don't initialize early
private boolean registerErrorPageFilter = true;
/**
* Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if
* error page mappings should be handled via the server and not Spring Boot.
* @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered.
*/
protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
this.registerErrorPageFilter = registerErrorPageFilter;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootAppContext = createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
public interface WebApplicationInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initializing this web application. See
* examples {@linkplain WebApplicationInitializer above}.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
* implementations present on the application classpath.
* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
* Servlet 3.0+ containers will automatically scan the classpath for implementations
* of Spring's {@code WebApplicationInitializer} interface and provide the set of all
* such types to the {@code webAppInitializerClasses} parameter of this method.
* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
* this method is effectively a no-op. An INFO-level log message will be issued notifying
* the user that the {@code ServletContainerInitializer} has indeed been invoked but that
* no {@code WebApplicationInitializer} implementations were found.
* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
* they will be instantiated (and <em>sorted</em> if the @{@link
* org.springframework.core.annotation.Order @Order} annotation is present or
* the {@link org.springframework.core.Ordered Ordered} interface has been
* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
* method will be invoked on each instance, delegating the {@code ServletContext} such
* that each instance may register and configure servlets such as Spring's
* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
* or any other Servlet API componentry such as filters.
* @param webAppInitializerClasses all implementations of
* {@link WebApplicationInitializer} found on the application classpath
* @param servletContext the servlet context to be initialized
* @see WebApplicationInitializer#onStartup(ServletContext)
* @see AnnotationAwareOrderComparator
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {