参考文档:https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile 编写语法参考 :http://groovy-lang.org/semantics.htmlpipeline {
agent any // (1)
stages {
stage('Build') { // (2)
steps {
// (3)
}
}
stage('Test') { // (4)
steps {
// (5)
}
}
stage('Deploy') { // (6)
steps {
// (7)
}
}
}
}node { // (1)
stage('Build') { // (2)
// (3)
}
stage('Test') { // (4)
// (5)
}
stage('Deploy') { // (6)
// (7)
}
}
(1)开发人员代码提交
(2)开发人员点击jenkins构建
(3)开发人员使用运维工具查看服务情况父工程
|--- 子模块1 # java子模块
|------ docker文件夹 # 存放部署文件
|--------- Dockerfile
|--------- Jenkinsfile
|--------- k8s.yaml
|------ src
...
|--- 子模块2
|------ docker文件夹
|--------- Dockerfile
|--------- Jenkinsfile
|--------- k8s.yaml
|------ srcFROM openjdk:8-jre
MAINTAINER QiMing Mei <meiqiming@talkweb.com.cn>
ARG JAR_FILE
ENV JAVA_OPTS="-server -Xms1024m -Xmx1024m" JAR_FILE="${JAR_FILE}"
ADD target/${JAR_FILE} /usr/share/service/${JAR_FILE}
ENTRYPOINT java ${JAVA_OPTS} -jar /usr/share/service/${JAR_FILE} --server.port=${CONTAINER_PORT} \
--spring.cloud.nacos.discovery.ip=${DOMAIN_NAME} --spring.cloud.nacos.server-addr=${NACOS_URL}apiVersion: v1
kind: Namespace
metadata:
name: __NAME_SPACE__
labels:
name: __NAME_SPACE__
---
apiVersion: v1
kind: Service
metadata:
name: __DOMAIN_NAME__
namespace: __NAME_SPACE__
spec:
ports:
- name: app-port
port: __CONTAINER_PORT__
targetPort: __CONTAINER_PORT__
protocol: TCP
# 当开启管理端口时,外部服务需要访问时打开此注释
# - name: manage-port
# port: __MANAGE_PORT__
# targetPort: __MANAGE_PORT__
# protocol: TCP
selector:
app: __DOMAIN_NAME__
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: __DOMAIN_NAME__
namespace: __NAME_SPACE__
spec:
selector:
matchLabels:
app: __DOMAIN_NAME__
replicas: __REPLICAS_NUM__
template:
metadata:
labels:
app: __DOMAIN_NAME__
spec:
initContainers:
- name: init-agent-sidecar
image: ming19871211/skywalking-agent:__SKYWALKING_VESION__
command:
- 'sh'
- '-c'
- 'set -ex;cp -r /skywalking/agent/* /usr/skywalking/agent;'
volumeMounts:
- name: agent
mountPath: /usr/skywalking/agent
containers:
- name: __DOMAIN_NAME__
image: __DOCKER_IMAGE__
imagePullPolicy: IfNotPresent #本地存在就不到远程拉取镜像
env: #环境变量设置
- name: TZ
value: Asia/Shanghai
- name: NACOS_NAMESPACE
value: __NACOS_NAMESPACE__
- name: DEPLOY_ENV # 兼容性配置,后续版本建议删除
value: __NACOS_NAMESPACE__
- name: NACOS_GROUP
value: __NACOS_GROUP__
- name: JAVA_OPTS
value: "__JAVA_OPTS__"
- name: CONTAINER_PORT
value: "__CONTAINER_PORT__"
- name: MANAGE_PORT
value: "__MANAGE_PORT__"
- name: NACOS_URL
value: __NACOS_URL__
- name: DOMAIN_NAME
value: __DOMAIN_NAME__.__NAME_SPACE__
# - name: DOMAIN_NAME
# valueFrom:
# fieldRef:
# apiVersion: v1
# fieldPath: status.podIP
envFrom:
- secretRef:
name: __NACOS_AUTH__
resources: #资源限制
requests:
memory: "128Mi"
cpu: "100m" #最低需要 0.1个cpu
limits:
memory: "__LIMIT_MEMORY__Mi"
cpu: "1000m"
ports:
- containerPort: __CONTAINER_PORT__
name: app-port
protocol: TCP
- containerPort: __MANAGE_PORT__
name: manage-port
protocol: TCP
readinessProbe: #就绪探针
httpGet:
# path: __APP_MANAGE_PATH__/actuator/health/readiness
# port: __MANAGE_PORT__
tcpSocket:
port: __CONTAINER_PORT__
initialDelaySeconds: 60
periodSeconds: 15
timeoutSeconds: 5
livenessProbe: #健康检查
# httpGet:
# path: __APP_MANAGE_PATH__/actuator/health/liveness
# port: __MANAGE_PORT__
tcpSocket:
port: __CONTAINER_PORT__
initialDelaySeconds: 60
periodSeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: time-config
mountPath: /etc/localtime
readOnly: true
- name: agent
mountPath: /usr/skywalking/agent
# 增加挂载
# - name: [PVC_NAME_ALIAS] # pod挂载别称
# mountPath: [POD_MOUNT_PATH] # pods内挂载路径
# subPath: [PVC_MOUNT_SUBPATH] # pvc挂载盘中的子路径,注释这一项标识挂载根路径下
imagePullSecrets:
- name: __DOCKER_REGISTRY_SECRET__
nodeSelector:
isDev: "true"
volumes:
- name: time-config
hostPath:
path: /etc/localtime
- name: agent
emptyDir: {}
# 增加挂载
# - name: [PVC_NAME_ALIAS] # pod挂载别称
# persistentVolumeClaim:
# claimName: [PVC_NAME] # 对应指定的pvc名称def docker_image = "" //定义全局的镜像变量
pipeline {
/*
1.配置maven相关变量 credentials-[Secret file]类型: jenkins-maven-setting[maven setting.xml文件]
2.配置docker仓库相关变量 系统环境变量:DOCKER_REGISTRY_ADDR [docker仓库地址]
credentials-[Username with password]类型:jenkins-docker-registry-creds[docker仓库账号/docker仓库密码]
3.配置nacos相关变量 定义账号密码对应下面的NACOS_URL
credentials-[Username with password]类型:jenkins-nacos-creds[nacos登录账号/nacos登录密码]
4.配置k8s相关变量 credentials-[Secret file]类型: jenkins-k8s-config[k8s集群访问凭证文件]
5.若需要启动skywalking链路跟踪,需要配置环境变量IS_SKYWALKING=true, SKYWALKING_VESION=[skywalking的版本号]
合计:1个docker仓库地址环境变量,4个credentials为必须配置;2个skywalking环境变量根据需求配置;
`参考文档`:https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile
`编写语法参考 `:http://groovy-lang.org/semantics.html
*/
// 如果指定具体的节点执行,请 agent { label 'docker-slave' }
agent any
options {
//超时一小时
timeout(time: 1, unit: 'HOURS')
//不允许同时执行
disableConcurrentBuilds()
}
// #######################需要修改的区域 开始#################################
/*** 一般情况下只需要修改environment与parameters区域的 ${SERVER_NAME} ${MODULE_NAME} ${NAME_SPACE}
* ${NACOS_NAME_SPACE} 没有多层子项目的情况下SERVER_NAME与MODULE_NAME一般是相同的
* 教育云认证服务 eg: SERVER_NAME=MODULE_NAME=ssop-auth-service, NACOS_NAME_SPACE=NAME_SPACE=talkweb-school-dev
**/
environment {
// 服务的域名,一般与项目名称相同
DOMAIN_NAME='${SERVER_NAME}'
// docker容器内部端口,不建议修改
CONTAINER_PORT=80
// spring boot中设置了管理端口 eg: management.server.port:${MANAGE_PORT:18085} 必须设置 MANAGE_PORT=XXX, 建议直接打开下面注释
// MANAGE_PORT=90
// spring boot中没有设置管理端口,但是设置了server.servlet.context-path,并且在非根目录时需要增加环境变量APP_MANAGE_PATH其值与server.servlet.context-path相同
// APP_MANAGE_PATH="/"
// 服务的的实例数量
REPLICAS_NUM=1
// 表示限制内存大小,单位为M,只能为数字;
LIMIT_MEMORY=2048
// maven配置文件setting路径
MAVEN_SETTING=credentials('jenkins-maven-setting')
// docker仓库地址,一般与系统环境变量相同,若相同时手动修改
DOCKER_REGISTRY_ADDR="${env.DOCKER_REGISTRY_ADDR}"
// docker仓库账号密码--对应系统环境变量DOCKER_REGISTRY_ADDR
DOCKER_REGISTRY=credentials('jenkins-docker-registry-creds')
// 注册中心域名,默认是nacos 一般不需要修改
NACOS_URL='nacos-headless.talkweb:8848'
// nacos凭证-对应NACOS_URL
NACOS_CREDS=credentials('jenkins-nacos-creds')
// k8s版本号
K8S_VERSION='v1.19.16'
// K8s配置文件路径
K8S_CONFIG=credentials('jenkins-k8s-config')
// k8s中nacos账号密码存储的secret名称
K8S_NACOS_AUTH="nacos-auth"
// k8s中docker仓库信息存储的secret名称
K8S_DOCKER_REGISTRY="hub-local"
// 根据需求,xms、xmm已经自动添加 此处不用添加
//JAVA_OPTS="-Dspring.XXX=XXX "
}
parameters {
// 修改部署的域名空间
string(name:'name_space', defaultValue: "${NAME_SPACE}", description: '发布的命名空间')
// 修改为子项目名称 多层情况,合同产品eg: soeasy-clm-message/soeasy-clm-message-server
string(name:'module', defaultValue: "${MODULE_NAME}", description: '发布的子项目')
// nacos空间
choice(name: 'nacos_namespace', choices: ['${NACOS_NAME_SPACE}'], description: 'nacos命名空间')
// nacos组
choice(name: 'nacos_group', choices: ['DEFAULT_GROUP'], description: 'nacos组名称')
// 配置Tag的git参数,参数类型 设置为 分支或标签
// 配置git地址:在流水线 -> 定义[Pipeline script from SCM] -> SCM[git] -> Repository URL[git地址]
// -> Credentials[git账号] -> 指定分支(为空时代表any)[${Tag}] --> 脚本路径[Jenkinsfile文件的工程路径]
// -> 轻量级检出[去掉√ ]
}
// ########################需要修改的区域 结束################################
stages {
stage('Run maven') {
agent {
docker {
image 'maven:3.5-jdk-8-alpine'
args '-v $HOME/.m2:$HOME/.m2 '
reuseNode true
}
}
environment {
// 设置maven本地仓库地址
MAVEN_OPTS=" -Dmaven.repo.local=$HOME/.m2/repository "
}
steps {
sh "mvn -version && mvn clean package -pl ${params.module} -am -Dmaven.test.skip=true -s $MAVEN_SETTING"
}
}
stage('Build And Push Docker Image'){
steps {
script {
// 获取标签版本或者分支的commitId
if (!params.Tag || params.Tag.contains('origin/')) {
sh "git rev-parse --short HEAD > commit-id"
tag = readFile('commit-id').replace("\n", "").replace("\r", "")
} else {
tag = "$params.Tag"
}
// 如果符合条件的tag指向最新提交则只是显示tag的名字,否则会有相关的后缀来描述该tag之后有多少次提交以及最新的提交commit-id
//git_tag = sh(returnStdout: true,script: 'git describe --tags --always').trim()
// 获取jar的名称
target_jar_name = sh(returnStdout: true,script: "target_jar_name=\$(ls ${params.module}/target/*.jar) && echo \${target_jar_name##*/}").trim()
module_std = sh(returnStdout: true,script: "module_std=${target_jar_name.replace('.jar','')} && echo \${module_std%-[0-9].[0-9]*}").trim()
// 解决打包名称不按正规方式命名, 保证模块代号相同
project_prefix=env.DOMAIN_NAME.split("-")[0]
module_std = module_std.contains("$project_prefix") ? module_std : params.module.split("/")[-1]
module_std = module_std.contains("$project_prefix") ? module_std : "$project_prefix-$module_std"
// 赋值镜像名称
docker_image = "${env.DOCKER_REGISTRY_ADDR}/${params.name_space}/${module_std}:$tag"
// 编译镜像
sh "rm -rf ${params.module}/docker/target && mv ${params.module}/target ${params.module}/docker"
sh "docker build -t $docker_image --build-arg JAR_FILE=$target_jar_name ${params.module}/docker"
}
echo "完成DOCKER镜像打包......"
sh "docker login ${DOCKER_REGISTRY_ADDR} --username=${DOCKER_REGISTRY_USR} --password=${DOCKER_REGISTRY_PSW}"
sh "docker push $docker_image"
sh "docker rmi $docker_image"
}
}
stage('Deploy to k8s'){
agent {
docker {
image "ming19871211/kubectl:${K8S_VERSION}"
reuseNode true
}
}
environment {
// 定义k8s访问凭证的环境变量
KUBECONFIG='.kube/config'
}
steps{
sh "mkdir -p .kube && cp $K8S_CONFIG .kube/config"
script {
java_opts = env.JAVA_OPTS ? env.JAVA_OPTS : ""
java_opts="${java_opts} -server -Xms${LIMIT_MEMORY.toInteger() / 2 }m -Xmx${LIMIT_MEMORY.toInteger() / 2}m"
// 若有大并发随机数场景 java_opts增加 -Djava.security.egd=file:/dev/./urandom 参数
if (env.IS_SKYWALKING && env.IS_SKYWALKING == "true") {
java_opts="${java_opts} -javaagent:/usr/skywalking/agent/skywalking-agent.jar=collector.backend_service='${SKYWALKING_BACKEND_SERVICE}',agent.namespace=${params.name_space},agent.service_name=${DOMAIN_NAME}"
}
skywalking_vesion = env.SKYWALKING_VESION ? env.SKYWALKING_VESION : "7.0.0"
//判断k8s部署空间是否存在,若不存在,则直接创建
def k8s_namespaces = sh(script: "kubectl get namespaces ${params.name_space}", returnStatus: true)
if (k8s_namespaces != 0){
sh "kubectl create namespace ${params.name_space}"
}
//查看nacos认证密钥是否存在,若不存在则创建
def nacos_auth_name = sh(script: "kubectl -n ${params.name_space} get secret ${K8S_NACOS_AUTH} ", returnStatus: true)
if (nacos_auth_name != 0){
sh "kubectl -n ${params.name_space} create secret generic ${K8S_NACOS_AUTH} \
--from-literal=NACOS_USR=$NACOS_CREDS_USR --from-literal=NACOS_PWD=$NACOS_CREDS_PSW"
}
//查看docker仓库认证密钥是否存在,若不存在则创建
def docker_registry_name = sh(script: "kubectl -n ${params.name_space} get secret ${K8S_DOCKER_REGISTRY} ", returnStatus: true)
if (docker_registry_name != 0){
sh "kubectl -n ${params.name_space} create secret docker-registry ${K8S_DOCKER_REGISTRY} \
--docker-server=${DOCKER_REGISTRY_ADDR} \
--docker-username=${DOCKER_REGISTRY_USR} --docker-password=${DOCKER_REGISTRY_PSW} "
}
//管理端口
//manage_port = env.MANAGE_PORT ? env.MANAGE_PORT : "${CONTAINER_PORT.toInteger() + 10}"
manage_port = env.MANAGE_PORT ? env.MANAGE_PORT : env.CONTAINER_PORT
// 应用上下文件访问根路径
app_manage_path = ( env.APP_MANAGE_PATH && env.APP_MANAGE_PATH != "/" ) ? env.APP_MANAGE_PATH : ""
//部署服务
sh "sed -e 's#__DOCKER_IMAGE__#'$docker_image'#' \
-e 's#__DOMAIN_NAME__#'${env.DOMAIN_NAME}'#' \
-e 's#__NAME_SPACE__#'${params.name_space}'#' \
-e 's#__REPLICAS_NUM__#'${env.REPLICAS_NUM}'#' \
-e 's#__CONTAINER_PORT__#'${env.CONTAINER_PORT}'#' \
-e 's#__MANAGE_PORT__#'${manage_port}'#' \
-e 's#__APP_MANAGE_PATH__#'${app_manage_path}'#' \
-e 's#__DOCKER_REGISTRY_SECRET__#'${env.K8S_DOCKER_REGISTRY}'#' \
-e 's#__LIMIT_MEMORY__#'${env.LIMIT_MEMORY}'#' \
-e \"s#__JAVA_OPTS__#${java_opts}#\" \
-e 's#__NACOS_NAMESPACE__#'${params.nacos_namespace}'#' \
-e 's#__NACOS_GROUP__#'${params.nacos_group}'#' \
-e 's#__NACOS_URL__#'${env.NACOS_URL}'#' \
-e 's#__NACOS_AUTH__#'${env.K8S_NACOS_AUTH}'#' \
-e 's#__SKYWALKING_VESION__#'${skywalking_vesion}'#' \
${params.module}/docker/k8s.yaml | kubectl apply -f -"
//查看部署服务的状态
timeout(time: 30, unit: 'SECONDS') {
for (int i = 0; i < 3; i++) {
sleep 5
sh "kubectl -n ${params.name_space} get pods |grep ^${env.DOMAIN_NAME} "
if ( i == 2 ){
no_run_num= sh(returnStdout: true,script: "kubectl get pods -n ${params.name_space} |grep ^${env.DOMAIN_NAME} |grep -v Running |wc -l").trim()
echo "还未部署完成pods数目: $no_run_num"
}
}
}
}
}
}
}
post {
always {
echo '执行完成。'
sh 'rm -rf .kube '
}
success {
echo '恭喜你,发布成功了!'
}
unstable {
echo '发布不稳定哦...'
}
failure {
echo '发布失败啦,请查明原因哦!'
}
changed {
echo '与之前信息有所不同哦...'
}
}
}|--- docker文件夹 # 存放部署文件
|------ Dockerfile
|------ Jenkinsfile
|------ k8s.yaml
|------ default.conf
|------ entrypoint.sh
|------ init.js
|--- public
|--- src
|--- vue相关文件
...
|--- .gitlab-ci.yml/* 全局静态初始化环境配置 */
var ENV_API = {
AUTH2_URL: "${AUTH2_URL}",
SERVICE_API_URL: "${SERVICE_API_URL}"
};#!/bin/bash
#配置文件路径,默认是static/js/config.js
__config_file="/usr/share/nginx/html/static/js/config.js"
if [ -n "$VUE_CONFIG_FILE_PATH" ]; then
__config_file="/usr/share/nginx/html/${VUE_CONFIG_FILE_PATH}"
fi;
#
if [ -f "/init-env/env.conf" ]; then
grep -v "^#" /init-env/env.conf |grep -v ^$ |while read LINE
do
A=`echo $LINE |awk -F "=" '{print $1}'`
B=`echo $LINE |awk -F "$A=" '{print $2}'`
result=$(echo $A | grep "\.")
if [[ "$result" != "" ]]; then
echo "#$A=\"$B\"" >> /init-env/env1.conf
elif [[ -z "$B" ]]; then
echo "空行,不需转换舍弃";
else
B=${B//\"/\\\"}; #处理特殊字符
echo "$A=\"${B/\`/\\\`}\"" >> /init-env/env1.conf
fi
done
sed -i 's/\r//' /init-env/env1.conf
#sed -e 's#\&#\\&#g' env.conf > env1.conf
eval "$(cat /init-env/env1.conf)"
rm -f /init-env/env1.conf
else
echo "/init-env/env.conf文件不存在,注意需要引入外部环境变量哦!";
fi;
#备份配置文件,存在就不备份,不存在就备份
if [ ! -f "${__config_file}.env" ]; then
cp ${__config_file} ${__config_file}.env
fi
# 变量替换方式直接会将${}的变量替换掉
if [ -f "/init-env/env.conf" -o "$IS_ENV_REPLACE" = "true" ]; then
#替换${}中的环境变量
eval "cat <<EOF
$(<${__config_file}.env)
EOF
" > ${__config_file}
#删除环境变量文件
rm -rf /init-env/env.conf
#赋予可读权限
chmod -R +r /usr/share/nginx/html
fi
nginx -g 'daemon off;'# 隐藏nginx版本
server_tokens off;
# 在某些浏览器上禁用内容类型嗅探。
add_header X-Content-Type-Options nosniff;
# 此标头启用跨站点脚本 (XSS) 过滤器
add_header X-XSS-Protection "1; mode=block";
# X-Frame-Options 是为了防止clickJacking
# DENY:不允许在 frame 中展示#
# SAMEORIGIN:允许在相同域名下frame展示
# ALLOW-FROM https://example.com/ :指定来源的 frame 中展示
# add_header X-Frame-Options SAMEORIGIN;
# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 5;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";
# 设置压缩所需要的缓冲区大小
gzip_buffers 32 4k;
# 设置gzip压缩针对的HTTP协议版本
gzip_http_version 1.0;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
# 带前缀方式的将 ${SERVER_NAME}替换为对应前缀名称即可,若访问直接在根目录下去掉${SERVER_NAME},去掉多余的/
location /${SERVER_NAME} {
alias /usr/share/nginx/html;
try_files $uri $uri/ /${SERVER_NAME}/index.html;#根据官网这规则配置
expires 5m;
}
# 若访问直接在根目录下,注释下面三行
location = / {
return 301 $scheme://$host/${SERVER_NAME};
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}FROM nginx:stable
MAINTAINER Qiming Mei <meiqiming@talkweb.com.cn>
ADD default.conf /etc/nginx/conf.d/default.conf
COPY dist/ /usr/share/nginx/html/
COPY init.js /usr/share/nginx/html/static/js/config.js
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
#CMD ["/bin/bash","-c", "/entrypoint.sh" ]
ENTRYPOINT ["/entrypoint.sh"]apiVersion: v1
kind: Namespace
metadata:
name: __NAME_SPACE__
labels:
name: __NAME_SPACE__
---
apiVersion: v1
kind: Service
metadata:
name: __DOMAIN_NAME__
namespace: __NAME_SPACE__
spec:
ports:
- name: app-port
port: 80
targetPort: 80
protocol: TCP
selector:
app: __DOMAIN_NAME__
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: __DOMAIN_NAME__
namespace: __NAME_SPACE__
spec:
selector:
matchLabels:
app: __DOMAIN_NAME__
replicas: __REPLICAS_NUM__
template:
metadata:
labels:
app: __DOMAIN_NAME__
spec:
initContainers:
- name: init-env-sidecar
image: busybox:latest
command: [ "sh", "-c"]
args:
- set -ex;
CONFIG_FILE=${CONFIG_FILE:-"vue-comm.properties"};
SYS_GLOBAL_CONFIG=${SYS_GLOBAL_CONFIG:-"sys-global-config.properties"};
wget --post-data="username=${NACOS_USR}&password=${NACOS_PWD}" -S "${NACOS_URL}/nacos/v1/auth/users/login" -O login-token;
access_token=$(grep -Eo '"accessToken":"([^"]*)"' login-token |awk -F \":\" '{print $2}');
access_token=${access_token/\"/};
rm -f /init-env/env.conf;
rm -f /init-env/env-sys.conf;
wget "${NACOS_URL}/nacos/v1/cs/configs?dataId=${CONFIG_FILE}&group=${NACOS_GROUP}&tenant=${NACOS_NAMESPACE}&accessToken=$access_token" -O /init-env/env.conf;
wget "${NACOS_URL}/nacos/v1/cs/configs?dataId=${SYS_GLOBAL_CONFIG}&group=${NACOS_GROUP}&tenant=${NACOS_NAMESPACE}&accessToken=$access_token" -O /init-env/env-sys.conf || return 0;
if [ $? -eq 0 -a -f "/init-env/env-sys.conf" ]; then
echo -e "\n" >> /init-env/env.conf;
cat /init-env/env-sys.conf >> /init-env/env.conf;
fi
env: #环境变量设置
- name: NACOS_NAMESPACE
value: __NACOS_NAMESPACE__
- name: NACOS_GROUP
value: __NACOS_GROUP__
- name: SYS_GLOBAL_CONFIG
value: __SYS_GLOBAL_CONFIG__
- name: CONFIG_FILE
value: __CONFIG_FILE__
- name: NACOS_URL
value: __NACOS_URL__
envFrom:
- secretRef:
name: __NACOS_AUTH__
volumeMounts:
- name: init-env
mountPath: /init-env/
containers:
- name: __DOMAIN_NAME__
image: __DOCKER_IMAGE__
imagePullPolicy: IfNotPresent #本地存在就不到远程拉取镜像
env: #环境变量设置
- name: TZ
value: Asia/Shanghai
- name: DOMAIN_NAME
value: __DOMAIN_NAME__.__NAME_SPACE__
resources: #资源限制
requests:
memory: "128Mi"
cpu: "100m" #最低需要 0.1个cpu
limits:
memory: "__LIMIT_MEMORY__Mi"
cpu: "1000m"
ports:
- containerPort: 80
readinessProbe: #就绪探针
# httpGet:
# path: /index.html
# port: 80
tcpSocket:
port: 80
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
livenessProbe: #健康检查
# httpGet:
# path: /index.html
# port: 80
tcpSocket:
port: 80
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: time-config
mountPath: /etc/localtime
readOnly: true
- name: init-env
mountPath: /init-env/
imagePullSecrets:
- name: __DOCKER_REGISTRY_SECRET__
nodeSelector:
isDev: "true"
volumes:
- name: time-config
hostPath:
path: /etc/localtime
- name: init-env
emptyDir: {}def docker_image = "" //定义全局的镜像变量
pipeline {
/*
1.配置docker仓库相关变量 系统环境变量:DOCKER_REGISTRY_ADDR [docker仓库地址]
credentials-[Username with password]类型:jenkins-docker-registry-creds[docker仓库账号/docker仓库密码]
2.配置nacos相关变量 定义账号密码对应下面的NACOS_URL
credentials-[Username with password]类型:jenkins-nacos-creds[nacos登录账号/nacos登录密码]
3.配置k8s相关变量 credentials-[Secret file]类型: jenkins-k8s-config[k8s集群访问凭证文件]
合计:1个docker仓库地址环境变量,3个credentials为必须配置;
`参考文档`:https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile
`编写语法参考 `:http://groovy-lang.org/semantics.html
*/
// 如果指定具体的节点执行,请 agent { label 'docker-slave' }
agent any
options {
//超时一小时
timeout(time: 1, unit: 'HOURS')
//不允许同时执行
disableConcurrentBuilds()
}
// #######################需要修改的区域 开始#################################
/*** 一般情况下只需要修改environment与parameters区域的
* 修改以下值即可 ${SERVER_NAME} ${NAME_SPACE} ${NACOS_NAMESPACE}
* 一般情况建议把nacos空间与部署空间值设置相同,容易理解
* nacos配置文件非默认值,eg:vue-comm.properties的,修改为指定名称的属性即可
*/
environment {
// 服务的域名,一般与项目名称相同
DOMAIN_NAME='${SERVER_NAME}'
// 注册中心的实例数量
REPLICAS_NUM=1
// 表示限制内存大小,单位为M,只能为数字;
LIMIT_MEMORY=2048
// docker仓库地址,一般与系统环境变量相同,若相同时手动修改
DOCKER_REGISTRY_ADDR="${env.DOCKER_REGISTRY_ADDR}"
// docker仓库账号密码--对应系统环境变量DOCKER_REGISTRY_ADDR
DOCKER_REGISTRY=credentials('jenkins-docker-registry-creds')
// 注册中心域名,默认是nacos 一般不需要修改
NACOS_URL='nacos-headless.talkweb:8848'
// nacos凭证-对应NACOS_URL
NACOS_CREDS=credentials('jenkins-nacos-creds')
// nacos配置中心配置文件名称(dataId)
CONFIG_FILE="vue-comm.properties"
// nacos配置中心配置文件名称(dataId) 系统全局配置
SYS_GLOBAL_CONFIG="sys-global-config.properties"
// k8s版本号
K8S_VERSION='v1.19.16'
// K8s配置文件路径
K8S_CONFIG=credentials('jenkins-k8s-config')
// k8s中nacos账号密码存储的secret名称
K8S_NACOS_AUTH="nacos-auth"
// k8s中docker仓库信息存储的secret名称
K8S_DOCKER_REGISTRY="hub-local"
}
parameters {
// 修改部署的域名空间
string(name:'name_space', defaultValue: "${NAME_SPACE}", description: '发布的命名空间')
// 项目名称
string(name:'module', defaultValue: "${SERVER_NAME}", description: '发布的项目')
// nacos空间
choice(name: 'nacos_namespace', choices: ['${NACOS_NAMESPACE}'], description: 'nacos命名空间')
// nacos组
choice(name: 'nacos_group', choices: ['DEFAULT_GROUP'], description: 'nacos组名称')
// 配置Tag的git参数,参数类型 设置为 分支或标签
// 配置git地址:在流水线 -> 定义[Pipeline script from SCM] -> SCM[git] -> Repository URL[git地址]
// -> Credentials[git账号] -> 指定分支(为空时代表any)[${Tag}] --> 脚本路径[Jenkinsfile文件的工程路径]
// -> 轻量级检出[去掉√ ]
}
// ########################需要修改的区域 结束################################
stages {
stage('Run node') {
agent {
docker {
image 'node:12'
reuseNode true
}
}
steps {
sh "npm -v && node -v && npm config set registry https://registry.npm.taobao.org && npm install && npm run build "
}
}
stage('Build And Push Docker Image'){
steps {
script {
// 获取标签版本或者分支的commitId
if (!params.Tag || params.Tag.contains('origin/')) {
sh "git rev-parse --short HEAD > commit-id"
tag = readFile('commit-id').replace("\n", "").replace("\r", "")
} else {
tag = "$params.Tag"
}
// 如果符合条件的tag指向最新提交则只是显示tag的名字,否则会有相关的后缀来描述该tag之后有多少次提交以及最新的提交commit-id
//git_tag = sh(returnStdout: true,script: 'git describe --tags --always').trim()
// 赋值镜像名称
docker_image = "${env.DOCKER_REGISTRY_ADDR}/${params.name_space}/${params.module}:$tag"
// 编译镜像
sh "rm -rf docker/dist && mv dist docker/"
sh "docker build -t $docker_image docker"
}
echo "完成DOCKER镜像打包......"
sh "docker login ${DOCKER_REGISTRY_ADDR} --username=${DOCKER_REGISTRY_USR} --password=${DOCKER_REGISTRY_PSW}"
sh "docker push $docker_image"
sh "docker rmi $docker_image"
}
}
stage('Deploy to k8s'){
agent {
docker {
image "ming19871211/kubectl:${K8S_VERSION}"
reuseNode true
}
}
environment {
// 定义k8s访问凭证的环境变量
KUBECONFIG='.kube/config'
}
steps{
sh "mkdir -p .kube && cp $K8S_CONFIG .kube/config"
script {
//判断k8s部署空间是否存在,若不存在,则直接创建
def k8s_namespaces = sh(script: "kubectl get namespaces ${params.name_space}", returnStatus: true)
if (k8s_namespaces != 0){
sh "kubectl create namespace ${params.name_space}"
}
//查看nacos认证密钥是否存在,若不存在则创建
def nacos_auth_name = sh(script: "kubectl -n ${params.name_space} get secret ${K8S_NACOS_AUTH} ", returnStatus: true)
if (nacos_auth_name != 0){
sh "kubectl -n ${params.name_space} create secret generic ${K8S_NACOS_AUTH} \
--from-literal=NACOS_USR=$NACOS_CREDS_USR --from-literal=NACOS_PWD=$NACOS_CREDS_PSW"
}
//查看docker仓库认证密钥是否存在,若不存在则创建
def docker_registry_name = sh(script: "kubectl -n ${params.name_space} get secret ${K8S_DOCKER_REGISTRY} ", returnStatus: true)
if (docker_registry_name != 0){
sh "kubectl -n ${params.name_space} create secret docker-registry ${K8S_DOCKER_REGISTRY} \
--docker-server=${DOCKER_REGISTRY_ADDR} \
--docker-username=${DOCKER_REGISTRY_USR} --docker-password=${DOCKER_REGISTRY_PSW} "
}
//部署服务
sh "sed -e 's#__DOCKER_IMAGE__#'$docker_image'#' \
-e 's#__DOMAIN_NAME__#'${env.DOMAIN_NAME}'#' \
-e 's#__NAME_SPACE__#'${params.name_space}'#' \
-e 's#__REPLICAS_NUM__#'${env.REPLICAS_NUM}'#' \
-e 's#__DOCKER_REGISTRY_SECRET__#'${env.K8S_DOCKER_REGISTRY}'#' \
-e 's#__LIMIT_MEMORY__#'${env.LIMIT_MEMORY}'#' \
-e 's#__NACOS_NAMESPACE__#'${params.nacos_namespace}'#' \
-e 's#__NACOS_GROUP__#'${params.nacos_group}'#' \
-e 's#__CONFIG_FILE__#'${env.CONFIG_FILE}'#' \
-e 's#__SYS_GLOBAL_CONFIG__#'${env.SYS_GLOBAL_CONFIG}'#' \
-e 's#__NACOS_URL__#'${env.NACOS_URL}'#' \
-e 's#__NACOS_AUTH__#'${env.K8S_NACOS_AUTH}'#' \
docker/k8s.yaml | kubectl apply -f -"
//查看部署服务的状态
timeout(time: 30, unit: 'SECONDS') {
for (int i = 0; i < 3; i++) {
sleep 5
sh "kubectl -n ${params.name_space} get pods |grep ^${env.DOMAIN_NAME} "
if ( i == 2 ){
no_run_num= sh(returnStdout: true,script: "kubectl get pods -n ${params.name_space} |grep ^${env.DOMAIN_NAME} |grep -v Running |wc -l").trim()
echo "还未部署完成pods数目: $no_run_num"
}
}
}
}
}
}
}
post {
always {
echo '执行完成。'
sh 'rm -rf .kube '
}
success {
echo '恭喜你,发布成功了!'
}
unstable {
echo '发布不稳定哦...'
}
failure {
echo '发布失败啦,请查明原因哦!'
}
changed {
echo '与之前信息有所不同哦...'
}
}
}
注意:新建任务前,需要将Jenkinsfile文件注释的前4点,在jenkins系统中配置好。
Jenkinsfile-docker文件(不会使用上面的jenkinsfile与k8s.yaml文件)def docker_image = "" //定义全局的镜像变量
pipeline {
/*
1.配置maven相关变量 credentials-[Secret file]类型: jenkins-maven-setting[maven setting.xml文件]
2.配置docker仓库相关变量 系统环境变量:DOCKER_REGISTRY_ADDR [docker仓库地址]
credentials-[Username with password]类型:jenkins-docker-registry-creds[docker仓库账号/docker仓库密码]
3.配置nacos相关变量 系统环境变量:NACOS_URL[nacos地址]
credentials-[Username with password]类型:jenkins-nacos-creds[nacos登录账号/nacos登录密码
4.配置docker客户端访问的证书
credentials-[X.509 Client Certificate]类型:jenkins-docker-client-creds[docker客户端凭证]
5.若需要启动skywalking链路跟踪,需要配置环境变量IS_SKYWALKING=true, SKYWALKING_VESION=[skywalking的版本号]
合计:1个docker仓库地址环境变量,4个credentials为必须配置;2个skywalking环境变量根据需求配置;
`参考文档`:https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile
`编写语法参考 `:http://groovy-lang.org/semantics.html
*/
// 如果指定具体的节点执行,请 agent { label 'docker-slave' }
agent any
options {
//超时一小时
timeout(time: 1, unit: 'HOURS')
//不允许同时执行
disableConcurrentBuilds()
}
// #######################需要修改的区域 开始#################################
/*** 一般情况下只需要修改environment与parameters区域的 ${SERVER_NAME} ${MODULE_NAME} ${NAME_SPACE}
* ${NACOS_NAME_SPACE} 没有多层子项目的情况下SERVER_NAME与MODULE_NAME一般是相同的
**/
environment {
// 服务的域名,一般与项目名称相同
DOMAIN_NAME='${SERVER_NAME}'
// 表示限制内存大小,单位为M,只能为数字;
LIMIT_MEMORY=2048
// maven配置文件setting路径
MAVEN_SETTING=credentials('jenkins-maven-setting')
// docker仓库地址,一般与系统环境变量相同,若不相同时手动修改
DOCKER_REGISTRY_ADDR="${env.DOCKER_REGISTRY_ADDR}"
// docker仓库账号密码--对应系统环境变量DOCKER_REGISTRY_ADDR
DOCKER_REGISTRY=credentials('jenkins-docker-registry-creds')
// 注册中心域名,默认是nacos 一般与系统环境变量相同,若不相同时手动修改
NACOS_URL="${env.NACOS_URL}"
// nacos凭证-对应NACOS_URL
NACOS_CREDS=credentials('jenkins-nacos-creds')
// 根据需求,xms、xmm已经自动添加 此处不用添加
//JAVA_OPTS="-Dspring.XXX=XXX "
}
parameters {
// 修改部署的域名空间
string(name:'name_space', defaultValue: "${NAME_SPACE}", description: '发布的命名空间')
// 修改为子项目名称 多层情况,eg: soeasy-clm-message/soeasy-clm-message-server
string(name:'module', defaultValue: "${MODULE_NAME}", description: '发布的子项目')
// nacos空间
choice(name: 'nacos_namespace', choices: ['${NACOS_NAME_SPACE}'], description: 'nacos命名空间')
// nacos组
choice(name: 'nacos_group', choices: ['DEFAULT_GROUP'], description: 'nacos组名称')
/* 以下4项参数需要配置到jenkins工程的参数中 */
// string(name:'container_port', defaultValue: "", description: '容器端口')
// string(name:'docker_remotes', defaultValue: "", description: '远程docker的api地址列表,以“,”隔开')
// string(name:'is_tls', defaultValue: "true", description: 'docker的api访问是否开启tls认证')
// 服务部署扩展的环境变量,eg
// choice(name: 'init_env', choices: ['-e ACTIVE_PROFILES=dev -e SENTINEL_DASHBOARD=10.154.142.19:8718'], description: '服务部署扩展的环境变量')
/* 以下配置为jenkins工程必须设置 */
// 配置Tag的git参数,参数类型 设置为 分支或标签
// 配置git地址:在流水线 -> 定义[Pipeline script from SCM] -> SCM[git] -> Repository URL[git地址]
// -> Credentials[git账号] -> 指定分支(为空时代表any)[${Tag}] --> 脚本路径[Jenkinsfile文件的工程路径]
// -> 轻量级检出[去掉√ ]
}
// ########################需要修改的区域 结束################################
stages {
stage('Run maven') {
agent {
docker {
image 'maven:3.5-jdk-8-alpine'
args '-v $HOME/.m2:$HOME/.m2 '
reuseNode true
}
}
environment {
// 设置maven本地仓库地址
MAVEN_OPTS=" -Dmaven.repo.local=$HOME/.m2/repository "
}
steps {
sh "mvn -version && mvn clean package -pl ${params.module} -am -Dmaven.test.skip=true -s $MAVEN_SETTING"
}
}
stage('Build And Push Docker Image'){
steps {
script {
// 获取标签版本或者分支的commitId
if (!params.Tag || params.Tag.contains('origin/')) {
sh "git rev-parse --short HEAD > commit-id"
tag = readFile('commit-id').replace("\n", "").replace("\r", "")
} else {
tag = "$params.Tag"
}
// 如果符合条件的tag指向最新提交则只是显示tag的名字,否则会有相关的后缀来描述该tag之后有多少次提交以及最新的提交commit-id
//git_tag = sh(returnStdout: true,script: 'git describe --tags --always').trim()
// 获取jar的名称
target_jar_name = sh(returnStdout: true,script: "target_jar_name=\$(ls ${params.module}/target/*.jar) && echo \${target_jar_name##*/}").trim()
module_std = sh(returnStdout: true,script: "module_std=${target_jar_name.replace('.jar','')} && echo \${module_std%-[0-9].[0-9]*}").trim()
// 解决打包名称不按正规方式命名, 保证模块代号相同
project_prefix=env.DOMAIN_NAME.split("-")[0]
module_std = module_std.contains("$project_prefix") ? module_std : params.module.split("/")[-1]
module_std = module_std.contains("$project_prefix") ? module_std : "$project_prefix-$module_std"
// 赋值镜像名称
docker_image = "${env.DOCKER_REGISTRY_ADDR}/${params.name_space}/${module_std}:$tag"
// 编译镜像
sh "rm -rf ${params.module}/docker/target && mv ${params.module}/target ${params.module}/docker"
sh "docker build -t $docker_image --build-arg JAR_FILE=$target_jar_name ${params.module}/docker"
}
echo "完成DOCKER镜像打包......"
sh "docker login ${DOCKER_REGISTRY_ADDR} --username=${DOCKER_REGISTRY_USR} --password=${DOCKER_REGISTRY_PSW}"
sh "docker push $docker_image"
sh "docker rmi $docker_image"
}
}
stage('Deploy to docker'){
environment {
// docker客户端证书凭证,若不需要ssl访问则注释
DOCKER_CERT_PATH = credentials('jenkins-docker-client-creds')
}
steps{
script {
// 确定端口
container_port=80
container_port_map = ""
skywalking_volume_map = ""
docker_client_env=""
if (params.container_port) {
container_port="${params.container_port}"
container_port_map = " -p ${params.container_port}:${params.container_port} "
}
// 确定部署机器
def docker_remote_arr = "${params.docker_remotes}".split(",")
for (int i = 0; i < docker_remote_arr.size(); ++i) {
docker_remote = "${docker_remote_arr[i]}"
if (params.docker_remotes) {
docker_client_env="export DOCKER_TLS_VERIFY=1; export DOCKER_HOST=tcp://${docker_remote}; docker_remote=${docker_remote} ;"
if (params.is_tls && params.is_tls == "false") {
docker_client_env="export -n DOCKER_TLS_VERIFY; export DOCKER_HOST=tcp://${docker_remote}; docker_remote=${docker_remote} ;"
}
}
//部署服务
sh "$docker_client_env docker rm -f $DOMAIN_NAME"
// 确定java_opts参数
java_opts = env.JAVA_OPTS ? env.JAVA_OPTS : ""
java_opts="${java_opts} -server -Xms${LIMIT_MEMORY.toInteger() / 2 }m -Xmx${LIMIT_MEMORY.toInteger() / 2}m"
// 若有大并发随机数场景 java_opts增加 -Djava.security.egd=file:/dev/./urandom 参数
if (env.IS_SKYWALKING && env.IS_SKYWALKING == "true") {
skywalking_vesion = env.SKYWALKING_VESION ? env.SKYWALKING_VESION : "7.0.0"
java_opts = "${java_opts} -javaagent:/usr/skywalking/agent/skywalking-agent.jar=collector.backend_service='${SKYWALKING_BACKEND_SERVICE}',agent.namespace=${params.name_space},agent.service_name=${DOMAIN_NAME}"
skywalking_volume_name = "$DOMAIN_NAME-skywalking-vol"
skywalking_volume_map = "-v ${skywalking_volume_name}:/usr/skywalking"
//创建一个配置卷
sh "$docker_client_env docker volume rm -f $skywalking_volume_name"
sh "$docker_client_env docker volume create $skywalking_volume_name"
//创建一个初始化配置卷的容器容器
sh "$docker_client_env docker run --rm --name=${skywalking_volume_name} -d \
-v $skywalking_volume_name:/data ming19871211/skywalking-agent:${skywalking_vesion} mv /skywalking/agent /data/ "
}
env_params=""
if (params.init_env) {
env_params = "${params.init_env}"
}
sh "$docker_client_env docker run -d --name ${DOMAIN_NAME} --hostname ${DOMAIN_NAME} --restart=always \
-e TZ='Asia/Shanghai' -e CONTAINER_PORT=${container_port} -e DOMAIN_NAME=\${docker_remote%:*} \
-e NACOS_URL='${NACOS_URL}' -e NACOS_USR=${NACOS_CREDS_USR} -e NACOS_PWD=${NACOS_CREDS_PSW} \
-e NACOS_NAMESPACE='${params.nacos_namespace}' -e NACOS_GROUP='${params.nacos_group}' \
-e DEPLOY_ENV='${params.nacos_namespace}' -e JAVA_OPTS='${java_opts}' ${env_params} \
$container_port_map -v /etc/localtime:/etc/localtime:ro $skywalking_volume_map \
-m ${LIMIT_MEMORY}M $docker_image"
}
}
}
}
}
post {
always {
echo '执行完成。'
}
success {
echo '恭喜你,发布成功了!'
}
unstable {
echo '发布不稳定哦...'
}
failure {
echo '发布失败啦,请查明原因哦!'
}
changed {
echo '与之前信息有所不同哦...'
}
}
}

注意:新建任务前,需要将Jenkinsfile-docker文件注释的前4点,在jenkins系统中配置好。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
我似乎经常遇到一些设计问题,但我不知道是什么是真的很合适。一方面我经常听到我应该限制耦合和坚持单一职责,但当我这样做时,我常常发现它很困难到在需要时将信息获取到程序的一部分。为了例如,classSingerdefinitialize(name)@name=nameendattr:nameend那么Song应该是:classSongdefnew(singer)@singer=singerendend或classSongdefnew(singer_name)@singer_name=singer_nameendend后者耦合性小,按道理应该用。但如果我以后发现宋有什么需要了解更多歌手,我的
我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http