Jenkins+Github+Docker实现自动化构建运行Jar

介绍

  • docker:简单的说,如果你用docker构建一个rabbitMQ集群之类的,将其打包成一个镜像.或者你直接拉取别人配置好的镜像,可以直接在 本机0配置的运行一个安装有mq集群容器(类似虚拟机).

  • Jenkins:可以从git或github等代码源,自动拉取代码,进行你想要的测试/构建操作,或shell脚本等.

  • 简单介绍下我要实现的效果:

  1. 在docker容器中运行jenkins.

  2. jenkins自动监听我的Github项目,当我提交新的代码时,

  3. jenkins自动拉取该代码到本地,使用maven打包后,关闭上个版本的该项目,然后使用java jar命令运行最新的jar(spring boot).

Docker安装及基本操作

  • Docker (CE)小企业或个人,此处是CE
  • Docker (EE)企业级
  • 安装及启动
CentOS
前置环境安装
    yum install -y yum-utils 
      device-mapper-persistent-data 
      lvm2

设置稳定的存储库
    yum-config-manager 
        --add-repo 
        https://download.docker.com/linux/centos/docker-ce.repo
    非必须 开启edge和test存储库库等  这样可以禁用yum-config-manager--disable docker-ce-edge
    yum-config-manager --enable docker-ce-edge 
    yum-config-manager --enable docker-ce-test

安装
    yum install docker-ce

启动docker(配置文件 /etc/sysconfig/docker)
    systemctl start docker

开机启动等
    systemctl enable docker
    systemctl disable docker
    chkconfig docker on

测试(这个命令下载一个helloword镜像并在容器中运行。容器运行时,会打印一条信息消息。)
    docker run hello-world

卸载
    yum remove docker-ce
    rm -rf /var/lib/docker # 删除镜像容器等
  • 常用命令(使用容器或镜像id时,无需全部id,只需前几个字符即可,也可使用 容器镜像名:版本号 操作)
重要:xx表示不同的命令如,pull、run等。可以查看该命令的帮助,所有参数
docker xx --help

查看本机的镜像,可通过该方法查看到镜像id等信息.
docker images 

获取镜像 name:镜像名  [:tag]:版本,默认为最新的(也就是会自己加上一个参数:latest)
docker pull [options] name[:tag]

删除镜像,需要删除其下所有容器
docker rmi <镜像id>

运行镜像,构建出一个容器. -d表示后台运行. 
docker run -d image 
-p 8080:80  进行端口映射,将nginx的80端口映射到主机的8080端口上,也就是别人访问8080,可以访问到自己的80

查看目前正在运行的容器
docker ps

查看所有容器
docker ps -a

停止容器
docker stop <容器id>

删除容器
docker rm <容器id>

启动一个运行(run)过的容器
docker start <容器id>

在运行的容器中执行命令 
docker exec [options] container command [arg...]
例如:   
docker exec -it <容器id> bash
可以进入一个容器,和虚拟机中一样。也就是容器内部

挂载目录:将宿主机的文件共享给容器
docker run -d --name=test -v /opt/test:/usr/databases docker-test 
test是容器的名字,需唯一;
-v表示创建一个数据卷并挂载到容器里,
示例表示把宿主机的/opt/test目录挂载到容器的/usr/databases目录下;
docker-test是镜像的名字

查看容器当前信息,可在该命令的 Mounts信息中,找到挂载目录信息
docker inspect <容器id>



制作镜像
以下就是 打包镜像tomcat和jpress.war
在某个目录创建文件 Dockerfile 编辑输入如下内容:
    # (继承自哪个镜像)注意,注释不能添加在和命令同行,会报错
    from images(镜像名)   
    # (维护人员信息)
    MAINTAINER ZX [email protected]  
    # (同一目录下要打包成镜像的文件,拷贝到tomcat的运行目录下)
    COPY jpress.war  /usr/local/tomcat/webapps  

此外,有更多命令
RUN : 执行容器中操作系统的命令(例如是CentOS,可以执行 cp mv 等等.)  
EXPOSE : 指定该容器暴露的端口,可以通过多个EXPOSE暴露多个端口
ENV : 设置环境变量 例如 ENV JAVA_HOME /var/java
ADD : 将本地文件拷贝到容器中.例如 /var/xxx.tar.gz  /var 
CMD : 容器启动时执行的命令,最多一条. 
其他命令自行搜索.

然后在目录下使用  
即可创建镜像,注意, . 是当前目录的意思
docker build .   
下面这句 -t是创建镜像并命名, :laster是版本号 ,因为上面的镜像没有命名
docker build  . -t jpress:latest

运行容器
-d表示后台运行 -p表示设置端口映射, jpress是镜像名
docker run -d -p  8888:8080 jpress

运行mysql镜像
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=xxx  images(镜像名)
  • 使用阿里云的Docker镜像加速器
管理控制台 -> 容器镜像服务 -> 镜像加速器 -> 获取到其分配的加速地址
修改 /etc/docker/daemon.json 文件,增加如下,没有时创建
  {
    "registry-mirrors": ["加速地址"]
  }
  • 可在阿里云免费开通 容器镜像服务,创建仓库,将镜像推送过去

Jenkins 持续集成

  • 前置

    • JDK8
    • Docker
  • 使用docker下载镜像jenkinsci/blueocean,并启动运行Jenkins容器

docker run 
      -u root 
      -d 
      -p 8080:8080 
      -v jenkins-data:/var/jenkins_home 
      -v /var/run/docker.sock:/var/run/docker.sock 
      jenkinsci/blueocean

官方文档中还有一个 --rm . 但是提示 -d 和 --rm 相互冲突
其容器中的/var/jenkins_home 被挂载到了我本地的 /var/lib/docker/volumes/jenkins-data/_data 目录下
  • 进入Jenkins容器执行命令
docker exec -it <容器id> bash
  • 查看容器输出的日志
docker logs <容器id> [-f(滚动的)]
  • 或者自行下载war包运行
java -jar jenkins.war --httpPort=8080
  • 可以创建一个基于Jenkins镜像的整合maven的新镜像(jdk在
    jenkinsci/blueocean中已经有一个oepn_jdk(目前没发现有什么问题))
下载maven 
wget http://mirror.bit.edu.cn/apache/maven/maven-3/3.5.2/binaries/apache-maven-3.5.2-bin.tar.gz

Dockerfile文件内容
     #继承自jenkins镜像
     FROM jenkinsci/blueocean:latest  
     #维护人员信息
     MAINTAINER ZX [email protected]  
     # 拷贝本地的maven解压包到镜像的/usr下
     ADD apache-maven-3.5.2-bin.tar.gz /var
     # 下载并解压jdk和maven,使用RUN表示执行当前系统(CentOS)命令
     RUN cd /var 
     tar -zxvf  apache-maven-3.5.2-bin.tar.gz 
     # 设置环境变量
     ENV MAVEN_HOME /var/apache-maven-3.5.2
     ENV PATH $MAVEN_HOME/bin:$PATH
     # 开放的端口
     EXPOSE 8080 
     EXPOSE 8081
     EXPOSE 8082 
     EXPOSE 8083 
     EXPOSE 8084 
     EXPOSE 8085 

构建镜像     
docker build . -t <仓库/镜像名:tag>
例如
docker build . -t zzzxxx/jenkins-maven-jdk:1.0

运行该镜像,端口映射可自行调整.
docker run 
          -u root 
          -d 
          -p 8080:8080 
          -p 8081:8081 
          -p 8082:8082 
          -p 8083:8083 
          -p 8084:8084 
          -p 8085:8085 
          -v jenkins-data:/var/jenkins_home 
          -v /var/run/docker.sock:/var/run/docker.sock 
          zzzxxx/jenkins-maven-jdk:1.0

bug:在RUN的最后一句contOS命令后多加了个 ,导致后面的ENV没有有效指定.

为防止maven构建过慢,可自行在/var/maven/conf/setting.xml中增加阿里云镜像
        <mirror>
          <id>nexus-aliyun</id>
          <mirrorOf>*</mirrorOf>
          <name>Nexus aliyun</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public</url>
        </mirror>
  • 启动时会在日志中输出密码,第一次配置时需要用到,也可以根据它的提示,去容器中的该目录找到

  • 启动完成后,访问对应的8080端口,即可.再完成它的一系列简单的配置(安装他推荐的所有插件)

  • 在 系统管理 -> 全局工具配置 中 设置maven和jdk目录等(可按它的选择自动下载maven) 默认集成的jdk等可用echo $JAVA_HOME 输出对应路径

未分类

  • 它的运行后所有数据默认保存的主目录的.jenkins中(例如 /root/.jenkins).可通过在启动前指定 JENKINS_HOME环境变量修改
vim /ect/profile
export JENKINS_HOME=xx
source /etc/profile
  • 安装插件 Maven Integration plugin 和 Github Plugin(默认已经安装)

  • 系统管理–>插件管理–>可选插件->搜索.安装即可 (如果搜不到,可查看已安装插件中是否已经安装.) 如果插件安装失败,可在已安装插件中卸载后,重试. 卸载插件执行后,需要重启jenkins

  • 在docker容器中jenkins的主目录在/var/jenkins_home文件夹中(可用echo $JENKINS_HOME查看).

  • 其中pull下来的项目在./workspace中的对应任务名的文件中

  • jenkins + github配置,实现jenkins能在push到github后,自动pull代码进行打包测试运行

    • github个人页面 -> setting -> developer setting -> personal access tokens -> Generate new token

未分类

未分类

未分类

该token就是OAuth2协议中的access_token.第三方应用(jenkinds)可通过该令牌获取你允许它进行的一些权限.
选择创建令牌.并勾选 repo 和 admin:repo_hook权限.并自行保存好生成的token.
这两个权限主要就是访问你的仓库,并设置你的仓库的监听器(钩子,可以理解为监听器.监听你的push等)

  • 在github上选择你要部署的项目 -> setting -> webHooks -> add webHooks

未分类

未分类

这个钩子,可以配置你要监听的事件.当该事件发生后,会请求你配置的那个url.
此处在Payload URL处填写
http://<你的jenkins的ip>:<端口号>:/github-webhook/
然后使用默认的Just the push event.监听push事件即可.
点击增加

  • jenkins页面: 系统管理->系统设置->GitHub->add Github Server

未分类

未分类

api url中输入 https://api.github.com
然后选择增加 Credentials. 选择类型为Secret text.在secret中输入之前的token即可.
然后测试连接.当返回
Credentials verified for user BrightStarry, rate limit: 4997
表示成功

  • jenkins: 创建一个新的maven项目任务

未分类

未分类

未分类

未分类

未分类

未分类

输入完任务名后,点击确认.
在配置页面,选择github Project.输入你要持续集成的项目的github url

在源码管理处,选择git.输入github项目的git地址.
然后点击 add Credentials,创建一个帐号.需要输入你的github帐号密码.然后在下拉款选择该帐号.
在下方还可选择要监听的分支

在触发器处,选择监听github的刚才配置的github hook.

在Build处指定pom.xml路径(通常就在根目录)和要执行的maven命令.

  • 最后需要在Post Setps中设置执行shell,我尝试很多次,都无法直接使用java -jar命令运行jar.(会将末尾的&省略,无法后台启动).
    • 可以在shell窗口中,增加如下(BUILD_ID=dontKillMe 是防止jenkins杀死我们的后台进程)

未分类

BUILD_ID=dontKillMe
java -jar $JENKINS_HOME/workspace/maven测试/target/zx-test.jar &
  • 或者使用sh脚本执行(这样可以不用手动停止上一个版本的正在运行的jar)
    • 写在jenkins要执行的shell窗口中的脚本 先停止前一个版本的jar.然后再用最新的jar替换掉之前的jar. 然后运行最新的jar
#!/bin/bash 
cd /var
sh stop.sh
cp $JENKINS_HOME/workspace/任务名/target/生成的jar名.jar /var
echo "开始启动"
BUILD_ID=dontKillMe 
java -jar /var/zx-test.jar &
  • stop.sh 停止前一个版本的jar(pid=ps -ef xxx这句的意思是,通过若干过滤找到对应jar的pid记录.$1表示输出后的记录的第一个参数) 在/var中创建该脚本
echo "正在停止之前的jar"
pid=`ps -ef | grep zx-test.jar | grep -v grep | awk '{print $1}'`
if [ -n "$pid" ]
then
echo "kill -9 的pid:" $pid
kill -9 $pid
fi
  • 至此,已经实现,指定github中的某个项目,一旦push上去,jenkins自动pull,执行maven命令构建,并执行shell运行jar.

  • 实现完成后,我才突然发现.如果要让项目自动运行在单独的docker容器中,最好不要让jenkins运行在docker容器中. 直接使用本地的jenkins,打包后,将jar单独封装成一个镜像,或者挂载jar目录到一个容器中.运行该容器即可.

Jenkins 与 GitLab 的自动化构建之旅

一、Gitlab 的安装及仓库创建

1.1 下载 Gitlab 安装包

1).官网下载速度较慢 建议先行下载

国内的源里面可以找到最新的版本,请单击这里查看。

2).安装依赖

sudo apt-get install curl openssh-server ca-certificates postfix

3).配置 postfix 邮箱

未分类

选择 Internet Site (F12) Enter 下一步

未分类

这里设置FQDN 使用默认即可。

1.2 安装 Gitlab

在终端执行:sudo dpkg -i gitlab-ce9.5.4ce.0amd64.deb 进行安装。

未分类

出现 It looks like… 表示安装成功!

1.3 安装 Git 工具

未分类

1.4 生成密钥文件

使用 ssh-keygen 生成密钥文件 .ssh/id_rsa.pub。

未分类

未分类

这里生成的两个秘钥很重要,会在后面 Gitlab 的仓库配置与 Jenkins 的构建免密连接时候用到。

二、GitLab 简单配置及项目新建

2.1 配置 Gitlab

这一步在官方的文档里面没有,但是如果没有配置的话,直接启动 GitLab,会出现不正确的 FQDN 错误,导致无法正常启动。因此必须做配置。

sudo gedit /etc/gitlab/gitlab.rb

把 external_url 改成部署机器的域名或者IP地址。

未分类

然后对 GitLab 进行重配置 (这一步也是启动 GitLab):

sudo gitlab-ctl reconfigure

查看启动状态:

sudo gitlab-ctl status

未分类

在浏览器的地址栏中输入服务器的公网 IP 即可登录 GitLab 的界面,第一次登录使用的用户名和密码为 root 和 5iveL!fe。

未分类

首次登录会强制用户修改密码。密码修改成功后,输入新密码进行登录。

2.2 Gitlab 项目新建

在 GitLab 的主页中新建一个 Project:

未分类

未分类

添加 ssh key 导入步骤2中生成的密钥文件内容(秘钥前面1.4节已生成):

未分类

ssh key 添加完成:

未分类

项目地址,该地址在进行 clone 操作时需要用到:

未分类

2.3 代码上传

克隆项目,在本地生成同名目录,并且目录中会有所有的项目文件 git clone [email protected]:gavin/test.git:

未分类

进入到项目目录,拷贝自己的项目文件到此目录上传:

cd test/ 
cp –rf  自己项目路径/*   .
git add .
git commit -m “add README” #将代码提交到本地仓库
git push -u origin master #将文件同步到GitLab服务器上

在网页中查看上传的文件已经同步到 GitLab 中:

未分类

三、Jenkins 安装与配置

3.1 Java 环境配置

Jenkins 基于 Java,Linux 下安装 Java 只要配置 Java 环境变量即可。 首先,解压java到相应目录,我一般习惯把安装的软件放到目录/usr/local下。

tar -zxvf jdk-6u45-linux-x64.tar.gz /usr/local

编辑环境变量,在 /etc/profile 文件中添加环境变量,Linux 的所有用户都可以使用。

vim /etc/profile

在文件最后添加内容如下:

export JAVA_HOME=/usr/local/jdk1.6.0_45
export PATH=$JAVA_HOME/bin:$PATHexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

这样,Java 就配置完成:

未分类

3.2 安装 Jenkins

简单来说需要下面四步:

  • wget -q -O – https://pkg.jenkins.io/debian/jenkins-ci.org.key | sudo apt–key add –
  • sudo sh -c ‘echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list’
  • sudo apt-get update
  • sudo apt-get install jenkins

未分类

这样之后就安装完成。可以查看进程信息。

ps -ef |grep jenkins

未分类

3.3 Jenins 配置

上面只是安装完成了 Jenkins,还需要进行一些配置才可以。 在这个系统端口中,8080已经在使用中了。所以在 /etc/default/jenkins.修改 Jenkins 默认端口设置:

gavin@gavin:~$ gedit /etc/default/jenkins

未分类

修改默认端口为 HTTP_PORT=8090,这时通过浏览器就可以访问 Jenkins 了。比如我的地址:http://192.168.0.122:8090/

未分类

可以看到提示,为了确保 Jenkins 的安全,将管理员的密码写入文件,需要复制到下面的文本框做验证。

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

然后把输出的内容复制到上面密码框处。

然后,到了选择插件的界面,通过附加功能扩展 Jenkins 可以支持许多不同的需求。

未分类

未分类

未分类

插件安装完成,就到了创建用户的界面,这里可以创建一个 Jenkins 用户。

未分类

到这里,基本配置就完成了。

未分类

未分类

如果在后续使用中,有插件需要安装,通过在已运行的 Jenkins 主页中,点击左侧的系统管理—>管理插件进入如下界面搜索安装:

未分类

四、Android 项目构建

4.1 SDK 环境变量的设置

在“系统管理”—>“系统设置”—>“全局属性”设置 SDK 的环境变量名称与本地 SDK 的路径。

未分类

未分类

4.2 新建 Android 项目

开始创建一个 AndroidDemo 项目进行演:

未分类

构建一个自由风格的软件项目,然后填写项目名称。

未分类

源码管理,这里可以根据自己的实际选择 Git 或者 SVN 服务器。先设置 Git 的源码路径:

然后设置免密凭证。如果是第一次需要通过“Add”添加。

未分类

添加凭证:

单击“add”按钮进行添加:

未分类

未分类

此处的私钥既是1.4章节所产生。

设置完成上面的步骤,直接按左下角保存,项目创建完成。

五、参数化项目构建

5.1 参数设置

建好的项目,相应修改构建参数等配置,直接通过“配置”进行修改:

未分类

选择实际需要用到的参数,比如发布的版本类型,Git 分支参数等。

未分类

1) 选择参数的设置:

未分类

设置打包的类型是 debug 或者 release。

未分类

打包的 App 针对的发布平台:

未分类

2) Git 分支选择:

想在构建的时候,自动获取 Git 仓库的分支并选择构建,可以设置如下:

未分类

未分类

3) gradle 脚本命令的配置:

未分类

选择“Invoke Gradle script”添加 gradle 命令脚本。

未分类

设置完这些参数保存,就可以退出到项目列表界面。

5.2 项目构建

进入项目开始构建,选择相应的参数。

未分类

构建成功的话,那么结果如下图:

未分类

构建完会在“Build History” 部分显示构建结果是成功还是失败并可以查看相应的构建日志,方便分析:

未分类

构建结果是红色代表构建失败,上图颜色表示构建成功。

构建状态:下图中分级符号概述了一个 Job 新近一次构建会产生的四种可能的状态。

  • Successful:完成构建且被认为是稳定的。
  • Unstable:完成构建,但被认为不稳定。
  • Failed:构建失败。
  • Disabled:构建已禁用。

未分类

在主界面则是通过构建稳定性评分等级进行表示。

未分类

构建稳定性:当一个 Job 中构建已完成并生成了一个未发布的目标构件,如果您准备评估此次构建的稳定性,Jenkins 会基于一些后处理器任务为构建发布一个稳健指数(从0-100 ),这些任务一般以插件的方式实现。它们可能包括单元测试(JUnit)、覆盖率(Cobertura)和静态代码分析(FindBugs)。分数越高,表明构建越稳定。下图中分级符号概述了稳定性的评分范围。任何构建作业的状态(总分100)低于80分就是不稳定的。

未分类

还有很多的参数配置,如触发器配置、邮箱配置,自动化发布等的参数很多,这里就不一一介绍,感兴趣的朋友可以上网或者留言交流。

Pick:一款 Linux 上的命令行模糊搜索工具

今天,我们要讲的是一款有趣的命令行工具,名叫 Pick。它允许用户通过 ncurses(3X) 界面来从一系列选项中进行选择,而且还支持模糊搜索的功能。当你想要选择某个名字中包含非英文字符的目录或文件时,这款工具就很有用了。你根本都无需学习如何输入非英文字符。借助 Pick,你可以很方便地进行搜索、选择,然后浏览该文件或进入该目录。你甚至无需输入任何字符来过滤文件/目录。这很适合那些有大量目录和文件的人来用。

安装 Pick

对 Arch Linux 及其衍生品来说,Pick 放在 AUR 中。因此 Arch 用户可以使用类似 Pacaur,Packer,以及 Yaourt 等 AUR 辅助工具来安装它。

pacaur -S pick

或者,

packer -S pick

或者,

yaourt -S pick

Debian,Ubuntu,Linux Mint 用户则可以通过运行下面命令来安装 Pick。

sudo apt-get install pick

其他的发行版则可以从这里下载最新的安装包,然后按照下面的步骤来安装。在写本指南时,其最新版为 1.9.0。

wget https://github.com/calleerlandsson/pick/releases/download/v1.9.0/pick-1.9.0.tar.gz
tar -zxvf pick-1.9.0.tar.gz
cd pick-1.9.0/

使用下面命令进行配置:

./configure

最后,构建并安装 Pick:

make
sudo make install

用法

通过将它与其他命令集成能够大幅简化你的工作。我这里会给出一些例子,让你理解它是怎么工作的。

让们先创建一堆目录。

mkdir -p abcd/efgh/ijkl/mnop/qrst/uvwx/yz/

现在,你想进入目录 /ijkl/。你有两种选择。可以使用 cd 命令:

cd abcd/efgh/ijkl/

或者,创建一个快捷方式 或者说别名指向这个目录,这样你可以迅速进入该目录。

但,使用 pick 命令则问题变得简单的多。看下面这个例子。

cd $(find . -type d | pick)

这个命令会列出当前工作目录下的所有目录及其子目录,你可以用上下箭头选择你想进入的目录,然后按下回车就行了。

像这样:

未分类

而且,它还会根据你输入的内容过滤目录和文件。比如,当我输入 “or” 时会显示如下结果。

未分类

这只是一个例子。你也可以将 pick 命令跟其他命令一起混用。

这是另一个例子。

find -type f | pick | xargs less

该命令让你选择当前目录中的某个文件并用 less 来查看它。

未分类

还想看其他例子?还有呢。下面命令让你选择当前目录下的文件或目录,并将之迁移到其他地方去,比如这里我们迁移到 /home/sk/ostechnix。

mv "$(find . -maxdepth 1 |pick)" /home/sk/ostechnix/

未分类

通过上下按钮选择要迁移的文件,然后按下回车就会把它迁移到 /home/sk/ostechnix/ 目录中的。

未分类

从上面的结果中可以看到,我把一个名叫 abcd 的目录移动到 ostechnix 目录中了。

使用方式是无限的。甚至 Vim 编辑器上还有一个叫做 pick.vim 的插件让你在 Vim 中选择更加方便。

要查看详细信息,请参阅它的 man 页。

man pick

我们的讲解至此就结束了。希望这款工具能给你们带来帮助。如果你觉得我们的指南有用的话,请将它分享到您的社交网络上,并向大家推荐我们。

Linux使用crontab运行Java程序定时任务小记

Linux中,crontab的功能是十分强大的,能够方便的调度程序的运行,甚至在很多时候能够替代程序中的定时任务。

它的命令格式和主要参数如下:

命令格式
crontab [-u user] [ -e | -l | -r ]

命令参数
-u user:用来设定某个用户的crontab服务;
file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,>crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。
-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。
-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。
-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。
-i:在删除用户的crontab文件时给确认提示。

图片化格式说明:

未分类

本次因为需要推送一批数据,简单起见用Java写了个jar程序,用命令行java -jar push.jar调用完事,但是数据是每天都要推送的,难道每天都要手工去执行一下命令行?这显然不切实际。

为了这么个小程序开发个定时任务也嫌麻烦,后来就想到了Linux系统的crontab,但是在使用过程中还是碰到了几个问题,在此记录一下。

第一步,编写start_pust.sh文件,内容简单如下:

#!/bin/bash
java -jar /home/liyd/push.jar

为了避免路径问题引起的错误,这里使用了绝对路径来保证执行正确。

第二步,编写crontab.txt文件,简单的一行指定执行时间:

34 1 * * * /home/liyd/start_push.sh

每天的1点34分调用push.jar。

最后指定crontab运行:

crontab crontab.txt

到这里设置就都完成了,按照我们的预想每天的1点34分就会执行pust.jar推送数据。

可是事情往往不会按我们预想的发展,我们发现程序根本就没有执行,这是为什么呢?因为在这之前我用命令行直接./start_push.sh都是可以的呀。

只能查查资料了,发现网上也有很多人碰到这个问题,总结起来两点:

一、路径问题

二、环境变量问题

这里我为了避免出错已经使用了绝对路径,那看来就是环境变量的问题了。

原来crontab并不会加载环境变量配置,需要我们在脚本中设置,Java程序没有JDK等环境变量当前不能运行了。

修改前面的start_push.sh脚本,加入profile文件的读取:

#!/bin/bash
. /etc/profile
. ~/.bash_profile
java -jar /home/liyd/push.jar

到这里,程序能够正常运行了,使用ps aux | grep java能够看到执行的进程,但是我的Java程序死活没有日志输出啊,查看我的日志配置:

  <appender name="DEFAULT-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
  <param name="file" value="${user.dir}/logs/common-default.log"/>
  <param name="append" value="true"/>
  <param name="encoding" value="UTF-8"/>
  <layout class="org.apache.log4j.PatternLayout">
  <param name="ConversionPattern" value="[%x][%r][%p][%t] %d{HH:mm:ss,SSS} method:%l %m%n"/>
  </layout>
  </appender>

本来应该是输出到当前项目的logs文件夹下的,这里初步估计应该是${user.dir}这个变量又找不到了吧。

再次修改start_pust.sh文件,加入user.dir参数:

#!/bin/bash
. /etc/profile
. ~/.bash_profile
java -Duser.dir="/home/liyd/" -jar /home/liyd/push.jar

到这里,终于一切正常!

python、virtualenv和virtualenvwrapper

简介

搭建不同版本的python环境,并且能够随意的创建、删除和管理,在不同的python环境下切换

安装环境

centos7.4、 VMware Workstation 14 Pro

安装步骤

1、查看现有的python环境

python -V

2、安装必要的工具

yum -y install zlib zlib-devel openssl-devel gcc

3、编译安装不同的python版本

1)安装python2.7.13

cd && wget https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz

tar -zxvf Python-2.7.13.tgz

cd Python-2.7.13

./configure --prefix=/usr/local/python2.7 && make -j 4 && make install

2)安装python3.6.3

cd && wget https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tgz

tar -zxvf Python-3.6.3.tgz

cd Python-3.6.3

./configure --prefix=/usr/local/python3.6 && make -j 4 && make install

4、安装配置virtualenv

easy_install pip 

(

如果没有 easy_install可下载安装 

wget https://bitbucket.org/pypa/setuptools/downloads/ez_setup.py && python ez_setup.py

)

pip install virtualenv

配置pip

cat << EOF  > /etc/pip.conf

[list]

format=columns

EOF

pip completion --bash >> ~/.bash_profile

5、安装配置virtualenvwrapper

pip install virtualenvwrapper

echo "export WORKON_HOME=~/test/venv" >>~/.bash_profile

source ~/.bash_profile

source virtualenvwrapper.sh

使用说明

1、创建python环境

mkvirtualenv -p /usr/local/python3.6/bin/python3.6 venv301

mkvirtualenv -p /usr/local/python2.7/bin/python2.7 venv201

2、查看python环境

lsvirtualenv

3、给所有python环境安装flask

allvirtualenv pip install flask

4、切换python环境

workon venv301

workon venv201

5、直接进入python环境的目录下

cdvirtualenv

6、退出python环境

deactivate

7、删除python环境

rmvirtualenv venv201

8、其它

钩子脚本,可自行查询相关内容

FLASK实现机器负载监控

  • 系统环境:centos7.4

  • 主机IP:192.168.134.149

Flask代码段:

未分类

moitor.html页面:

<!DOCTYPE html>

<html>

<head>

<meta charset=”utf-8″>

<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>

<title>运维管理系统</title>

<link rel=”shortcut icon” href=”favicon.ico”>

<link href=”{{url_for(‘static’,filename=’css/plugins/morris/morris-0.4.3.min.css’)}}” rel=”stylesheet”>

</head>

<body>

<script src=”{{ url_for(‘static’,filename=’js/jquery.min.js’) }}”></script>

<script src=”{{url_for(‘static’,filename=’js/plugins/morris/raphael-2.1.0.min.js’)}}”></script>

<script src=”{{url_for(‘static’,filename=’js/plugins/morris/morris.js’)}}”></script>

<script type=”text/javascript”>

$(function () {

function monitor() {

$(‘#myfirstchart’).html(“”);

var SCRIPT_ROOT = {{ request.script_root|tojson|safe }}

$.post(SCRIPT_ROOT + ‘/monitor’,function(data){

Morris.Line({

element: ‘myfirstchart’,

data: data ,

xkey: ‘timenow’,

ykeys: [‘a’,’b’,’c’],

labels: [‘1分钟’, ‘5分钟’,’15分钟’]

});

})

};

setInterval(monitor,3000)

})

</script>

<div id=”myfirstchart” style=”height: 250px;”></div>

</body>

</html>

效果图:

未分类

使用python3和flask构建RESTful API(接口测试服务)

引言

构建RESTful API貌似是开发的工作,和测试有和关系?

其实测试开发需要构建RESTful API的场景很多。比如测试Android应用,一般的接口测试只考虑了服务器端,至于客户端在网络异常或者服务端异常时如何反应,多数天朝的测试人员是没有考虑到的。客户端在对这些异常处理不够充分的时候,会出现崩溃等各种莫名其妙的问题。

为此一些走在前沿的测试人员会自己写一些RESTful API, 把服务端的域名劫持到自己的API,故意返回各种异常,看客户端的稳定性。

另外测试开发的测试工具需要和其他系统对接等场景也经常需要API。

术语

REST: REpresentational State Transfer

目标

  • GET – /api/Category – Retrieve all categories

  • POST – /api/Category – Add a new category

  • PUT – /api/Category – Update a category

  • DELETE – /api/Category – Delete a category

  • GET – /api/Comment – Retrieve all the stored comments

  • POST – /api/Comment – Add new comment

要求

  • python3.*
  • PostgreSQL

工程目录

project/
├── app.py
├── config.py
├── migrate.py
├── Model.py
├── requirements.txt
├── resources
│   └── Hello.py
│   └── Comment.py
│   └── Category.py
└── run.py

requirements.txt的内容如下:

flask
flask_restful
flask_script
flask_migrate
marshmallow
flask_sqlalchemy
flask_marshmallow
marshmallow-sqlalchemy
psycopg2
  • flask – Python的微框架

  • flask_restful – 这是Flask的扩展,可快速构建REST API。

  • flask_script – 提供了在Flask中编写外部脚本的支持。

  • flask_migrate – 使用Alembic的Flask应用进行SQLAlchemy数据库迁移。

  • marshmallow – ORM/ODM/框架无关的库,用于复杂数据类型(如对象)和Python数据类型转换。

  • flask_sqlalchemy – Flask扩展,增加了对SQLAlchemy的支持。

  • flask_marshmallow – 这是Flask和marshmallow的中间层。

  • marshmallow-sqlalchemy – 这是sqlalchemy和marshmallow的中间层。

  • psycopg – Python的PostgreSQL API。

安装依赖

# pip3 install -r requirements.txt

安装配置PostgreSQL

这里以 Ubuntu 16.04为例:

# sudo apt-get update && sudo apt-get upgrade
# apt-get install postgresql postgresql-contrib
# su - postgres
$ createdb api
$ createuser andrew --pwprompt #创建用户
$ psql -d api -c "ALTER USER andrew WITH PASSWORD 'api';"

参考资料:

https://linode.com/docs/databases/postgresql/how-to-install-postgresql-on-ubuntu-16-04/

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-14-04

配置

# -*- coding: utf-8 -*-
# Author:    xurongzhong#126.com wechat:pythontesting qq:37391319
# CreateDate: 2018-1-10

from flask import Blueprint
from flask_restful import Api
from resources.Hello import Hello
from resources.Category import CategoryResource
from resources.Comment import CommentResource


api_bp = Blueprint('api', __name__)
api = Api(api_bp)

# Routes
api.add_resource(Hello, '/Hello')
api.add_resource(CategoryResource, '/Category')
api.add_resource(CommentResource, '/Comment')

快速入门

app.py

from flask import Blueprint
from flask_restful import Api
from resources.Hello import Hello

api_bp = Blueprint('api', __name__)
api = Api(api_bp)

# Route
api.add_resource(Hello, '/Hello')

resource/Hello.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author:    xurongzhong#126.com wechat:pythontesting qq:37391319
# CreateDate: 2018-1-10

from flask_restful import Resource


class Hello(Resource):
    def get(self):
        return {"message": "Hello, World!"}

    def post(self):
        return {"message": "Hello, World!"}

run.py

from flask import Flask


def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_object(config_filename)

    from app import api_bp
    app.register_blueprint(api_bp, url_prefix='/api')

    return app


if __name__ == "__main__":
    app = create_app("config")
    app.run(debug=True)

启动服务

$ python3 run.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 136-695-873

用浏览器访问: http://127.0.0.1:5000/api/Hello

{
    "hello": "world"
}

接入数据库

from flask import Flask
from marshmallow import Schema, fields, pre_load, validate
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy


ma = Marshmallow()
db = SQLAlchemy()


class Comment(db.Model):
    __tablename__ = 'comments'
    id = db.Column(db.Integer, primary_key=True)
    comment = db.Column(db.String(250), nullable=False)
    creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id', ondelete='CASCADE'), nullable=False)
    category = db.relationship('Category', backref=db.backref('comments', lazy='dynamic' ))

    def __init__(self, comment, category_id):
        self.comment = comment
        self.category_id = category_id


class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), unique=True, nullable=False)

    def __init__(self, name):
        self.name = name


class CategorySchema(ma.Schema):
    id = fields.Integer()
    name = fields.String(required=True)


class CommentSchema(ma.Schema):
    id = fields.Integer(dump_only=True)
    category_id = fields.Integer(required=True)
    comment = fields.String(required=True, validate=validate.Length(1))
    creation_date = fields.DateTime()

migrate.py

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from Model import db
from run import create_app

app = create_app('config')

migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)


if __name__ == '__main__':
    manager.run()

数据迁移

$ python3 migrate.py db init
$ python3 migrate.py db migrate
$ python migrate.py db upgrade

修改Category.py 和Comment.py, 完整代码

测试

可以使用curl,比如:

curl http://127.0.0.1:5000/api/Category --data '{"name":"test5","id":5}' -H "Content-Type: application/json"

也可以在chrome中使用postman:

未分类

未分类

未分类

未分类

[Flask教程] 9.注册和登录功能实现(2)—— 注册与登录的错误提示

在注册和登录功能实现(1)中,我们已经获取到了页面POST过来的登录或者注册数据,接下来我们需要与数据库中的数据做验证,验证通过才能登录或者注册。我们平时在登录网站时,如果输入的用户名或者密码错误,有的网站是在登录框附近提示错误,也有的是跳转到一个页面提示出错,并经过几秒倒计时再返回原来的页面。
我们在后续做搜索功能的时候,用页面跳转来处理未找到结果的情形,这里就通过使用Flask的flash功能,直接在当前页面显示错误提示。简单来说,步骤就是在视图函数中flash一个字符串,在html模板中使用get_flashed_messages()去获取这个字符串,并显示在网页中。
首先,我们先新建一个exts.py,用于存放一些功能性的函数,在其中写一个去验证登录和注册信息的函数,如下:

from models import Users

def validate(username, password1, password2=None):
    user = Users.query.filter(Users.username == username).first()
    if password2:
        if user:
            return '用户名已经存在'
        else:
            if len(username) < 4:
                return '用户名长度至少4个字符'
            elif password1 != password2:
                return '两次密码不一致'
            elif len(password1) < 6:
                return '密码长度至少6个字符'
            else:
                return '注册成功'
    else:
        if user:
            if user.password == password1:
                return '登录成功'
            else:
                return '密码错误'
        else:
            return '用户名不存在'

要使用flash功能,还得设置一个名为SECRET_KEY的参数,用于加密数据,我们在config.py中写进去,随便取个值SECRET_KEY = “THIS-A-SECRET-KEY”。然后在HarpQA.py中,从flask中导入flash,从exts.py中导入validate,修改register视图函数,如下:

from flask import Flask, render_template, request, flash
from models import db
from exts import validate
import config

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)

...

@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        message = validate(username, password1, password2)
        flash(message)
        return render_template('register.html')

在视图函数中flash了message,接下来我们需要在html中显示它,我们再去修改base.html(这样对register.html和login.html都能起作用),在body区域尾部增加如下代码(直接从Flask官方文档复制修改的):

...
<nav><!-- 导航条内容 -->
...
</nav>
<div class="body-container">
    {% block body_part %}
    {% endblock %}
</div>
<div class="flash-message">
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul>
            {% for message in messages %}
                <li>{{ message }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
</div>
</body>

然后运行程序,访问注册页,随便输入几个试试,发现已经能用了:

未分类

只是不太美观,我们再用css调整一下,借助Boostrap中的警告框样式,最终结果如下:

未分类

登录也是同理,就不演示啦。

2017年1月17日补充内容:
当我们登录或注册出现问题,出现的警告框是如上图红色的,显然我们不希望登录或者注册成功的时候也是红色的,我们将提示成功的颜色设置为蓝色,然后传入一个参数给模板,告诉模板现在是成功还是失败的情况,然后在html中增加if,现在的html代码如下:

<div class="flash-message">
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul>
            {% for message in messages %}
                {% if status == 'OK' %}
                    <li><div class="alert alert-info" role="alert">{{ message }}</div></li>
                {% else %}
                    <li><div class="alert alert-danger" role="alert">{{ message }}</div></li>
                {% endif %}
            {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
</div>

那么status这个参数是怎么传递给模板呢,我们之前提到在render_templates函数中传入,现在我们使用@app.context_processor这个装饰器,它其实是上下文管理器,其装饰的函数返回的内容对所有html模板都起作用,用法如下:

@app.context_processor
def my_context_processor():
    status = session.get('status', '')
    return {'status': status}

将其放在HarpQA.py中,它返回一个字典,在任意html中使用{{ key }},就能得到这个字典key对应的value。那么session是什么?在现在这个case中,我们简单理解其为一个保存数据的容器,在登录和注册的视图函数中,验证完账号密码之后,将验证结果的信息存入session,例如注册函数修改如下:

@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        message = validate(username, password1, password2)
        flash(message)
        if message == '注册成功':
            session['status'] = 'OK'
            return redirect(url_for('login'))
        else:
            session['status'] = 'BAD'
            return render_template('register.html')

如果注册成功,就向session中写入status,值为’OK’,反之则为’BAD’。这样html模板就能根据status显示不同的颜色了。flask中还有个g对象也可用于保存和共享数据,但g对象是基于每一个请求的,不能跨请求使用。我们注册成功之后,要跳转到登录页,此时g就无法使用了,而session是基于这一次http连接的,不同请求都能使用。

未分类

实际上更简单的方法是,我们直接对传入的{{ message }}进行判断,如果带有’成功’字符串,就显示蓝色,否则就显示红色。上文主要是为了说明@app.context_processor这个装饰器,以及session和g对象的区别。

[Flask教程] 8.注册和登录功能实现(1)—— 页面设计和获取POST数据

在导航条中,右侧有登录和注册两个链接,储存用户数据的Users模型也有了,现在我们来实现登录和注册的功能。
先来看注册功能的实现,新建一个视图函数,如下:

@app.route('/register/')
def register():
    return render_template('register.html')

在导航条模板base.html中,为注册添加链接,使用url_for函数将其href属性值修改为register.html,如下:

<li><a href="{{ url_for('register') }}">注册</a></li>

接下来我们要去制作register.html了,这部分是基础的html/css知识,控件也是用的Bootstrap框架,最终注册页http://127.0.0.1:5000/register/的效果图如下(结尾贴出代码):

未分类

register.html中的表单控件,我们设置了其方法为POST,注册按钮的type为submit,这样点击提交按钮的时候,浏览器就会以POST方法去请求当前网址,但我们的视图函数默认只能接受GET方法,因此我们要为其添加POST方法,并使用flask中的request对象获取表单提交的数据,最终视图函数代码如下:

@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        print(username, password1, password2)
        return ' '

request.form.get方法需要传入表单控件的name属性的值,这样就能获取到对应的填入到表单中的值了,为了演示,我们在结尾将其打印了出来,结果显示无误,说明后端已经获取到了POST提交的数据。
登陆页面也是一样的原理,拿register.html简单修改成login.html,模仿register再增加一个login视图函数,然后访问http://127.0.0.1:5000/login/,效果如下:

未分类

结尾贴上register.html的内容:

{% extends 'base.html' %}

{% block static_files %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/register_login.css.css') }}"/>
{% endblock %}

{% block page_name %}注册{% endblock %}

{% block body_part %}
<h3>注册</h3>
<form class="form" action="" method="POST">
    <div class="input-group input-group-lg">
      <span class="input-group-addon" id="sizing-addon1">用户</span>
      <input type="text" class="form-control" placeholder="请输入用户名" aria-describedby="sizing-addon1" name="username">
    </div>
    <div class="input-group input-group-lg">
      <span class="input-group-addon" id="sizing-addon1">密码</span>
      <input type="password" class="form-control" placeholder="请输入密码" aria-describedby="sizing-addon1" name="password1">
    </div>
    <div class="input-group input-group-lg">
      <span class="input-group-addon" id="sizing-addon1">密码</span>
      <input type="password" class="form-control" placeholder="请再次确认密码" aria-describedby="sizing-addon1" name="password2">
    </div>
    <div class="register-button">
        <button type="submit" class="btn btn-primary btn-lg btn-block">注册</button>
    </div>
</form>
{% endblock %}

代码中引入的register_login.css是register.html和login.html通用的,其内容如下:

h3{
    text-align: center;
    padding-top: 10px;
}

.form{
    width: 400px;
    margin: 0 auto;
}

form.form > div{
    padding: 8px;
}

此外还对导航条模板做了简单的调整,第一是增加了一个block,给每个继承它的html一个放自定义css文件的地方,第二是在body中增加了一个div块,即register.html和login.html中间白色的内容区域,最终base.html代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="{{ url_for('static',filename='css/base.css') }}"/>
    <link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.ico') }}">
    {% block static_files %}{% endblock %}
    <title>{% block page_name %}{% endblock %}-HarpQA</title>
</head>
<body>
    <nav><!-- 导航条内容 -->
        ...省略...
        <li><a href="{{ url_for('login') }}">登录</a></li>
        <li><a href="{{ url_for('register') }}">注册</a></li>
        ...省略...
    </nav>
    <div class="body-container">
        {% block body_part %}
        {% endblock %}
    </div>
</body>
</html>

base.css 也添加了 <div class="body-container"> 的样式:

.body-container{
    width: 600px;
    background: white;
    margin: 0 auto;
    border-radius: 5px;
}

[Flask教程] 7.ORM与SQLAlchemy (3) – flask-migrate数据库迁移

在上一遍文章中,我们增加了两个模型Questions和Comments,并为Users增加了avatar_path这个字段,然后通过这段代码更新到数据库:

with app.test_request_context():
    db.drop_all()
    db.create_all()

因为当使用过db.create_all()之后,再次直接使用db.create_all(),对模型的修改并不会更新到数据库,我们要使用db.drop_all()先把数据库中所有的表先删除掉,然后再db.create_all()一次。听上去是不是很麻烦?更糟糕的是,原先数据库的的数据也就没有了。所以我们不用这种简单粗暴的方式去更新数据库结构,而是借助flask-migrate这个专门用于迁移数据库的工具,它可以在保留数据库原始数据的情况下,完成模型的更新。此外,我们还将结合flask-script一起使用,简单来说flask-script让我们可以使用命令行去完成数据库迁移的操作。

在项目主文件夹下新建一个manage.py,代码如下:

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from HarpQA import app, db
from models import Users, Questions, Comments

manager = Manager(app)

migrate = Migrate(app, db)

manager.add_command('db', MigrateCommand)


if __name__ == '__main__':
    manager.run()

首先导入相关的类,注意模型要全部导入过来,即使代码中并没有显式地使用它们。然后传入app或db来构建Manager和Migrate两个类的实例,最后将MigrateCommand的命令加入到manager中。

此时我们假设要更新模型的结构,在models.py的User模型结尾添加一行代码test = db.Column(db.Integer),然后点击PyCharm下方的Terminal,自动进入到了虚拟环境的命令行中,输入python manage.py db init来初始化,这一步主要是建立数据库迁移相关的文件和文件夹,只是在第一次需要使用。接着依次使用python manage.py db migrate和python manage.py db upgrade,待运行完成,查看users_infor表的结构,结果如下:

未分类

可以看到test字段已经添加到表中了。