iamtomas / note Goto Github PK
View Code? Open in Web Editor NEWThis project forked from jwenjian/ghiblog
GitHub Issues Blog, powered by GitHub Issues and GitHub Actions
Home Page: https://timeline.jwj.life
This project forked from jwenjian/ghiblog
GitHub Issues Blog, powered by GitHub Issues and GitHub Actions
Home Page: https://timeline.jwj.life
为确保统一排序集,可提前配置 collaction
属性如 utf8mb4_general_ci
注意:记住是提前配置,如果已经存在的数据库,则需要删掉重新执行
rake db:create
定义:用于处理后台任务且依赖于 Redis 的工具
快速使用:
# 1. Gemfile
gem 'redis-rails'
gem 'request_store' # 替换 Thread.current
gem 'request_store-sidekiq' # 确保在处理每个作业后清除存储,以确保与 Rack 中的安全性和一致性
gem 'redis-namespace' # 方便多个应用使用一个 redis
gem 'sidekiq', '< 6'
gem 'sidekiq-cron' # sidekiq 调度插件
# 2. config/redis.yml redis 配置,可以在 config/applicaiton.rb 设置 Rails.cache_store 为 redis
# 3. config/initializers/sidekiq.rb 绑定 redis 配置
# 4. config/sidekiq.yml 配置 sidekiq 信息
# 可选
# config/sidekiq_cron.yml 可通过 cron 方式调度作业
# config/initializers/sidekiq_cron.rb 加载到 Rails 应用
源码分析:
启动 Sidekiq 后首先解析配置(比如 queues、concurrency 等),然后是接连启动心跳检查、任务拉取器 、worker 管理器 及 worker(如下图)
好文分享:
本质上是一个hmap类型的指针,键值对通过哈希表来储存
扩容过程可能存在大量的rehash操作,从而导致性能受到影响。
那么可以在创建Map时指定较大的容量或者手动调用runtime.GC()来触发垃圾回收操作,以释放未使用的内存。
map会检测并发写并调用runtime.throw() 函数抛出异常,可以在业务层加锁(sync.Mutex或sync.RWMutext)或sync.map
与加锁的区别在于它无需加解锁,而是使用了更高级的算法。当多个 goroutine 同时访问 sync.Map 时,它会自动分配不同的段来存储数据,并且每个段都有自己的读写锁,以避免竞争条件。另外还拥有动态分片(并发读写的压力增加会动态调整分片的数量)以及零值操作(读取或删除不存在的key非常高效)等提高性能的手段
sync.Map的Load()方法用于获取指定键对应的值,其过程首先获取内部的读锁,确保不被其他goroutine,如果有其他的goroutine正在写入,则Load方法会被堵塞,直到写入完成
sync.Map Store()方法用于向映射中存储一个键值对,为了保持缓存层和底层 map 数据的一致性,sync.Map 使用了一种特殊的机制,具体流程如下:
sync.Map 使用缓存层和底层 map 之间的转换机制来避免锁的使用,从而提高了并发性能。然而,由于缓存层和底层 map 之间存在一定的延迟和不一致性,因此在一些特殊的场景下,可能会出现一些数据同步的问题,需要特别注意
了解 GraphQL 使用场景及基本原理
“What's GraphQL?”
是一个用于 API 的查询语言,在一些特定场景下,提供了更高效、更强大、更灵活的 REST 替代方案
抱着疑问,让我们看看 GraphQL与 REST API 都有哪些区别?
REST API
GraphQL
“Why use GraphQL?”
团队维度
技术维度
“客户端将请求参数发送到服务端,这一整个过程都做了什么?”
简单来说,分以下几个步骤
那么服务端如何识别并响应客户端 Schema 这段字符串,分为三个阶段:解析 -> 校验 -> 执行
GraphQL 定义了一系列的 特征标识符 及 AST语法树规范,以便逐字符扫描( charCodeAt )从而识别客户端 Schema,最终解析的产出物为 document,比如:
{
"kind":"Document",
"definitions":[
{
"kind":"OperationDefinition",
"operation":"query",
"name":{
"kind":"Name",
"value":"DisplayMember",
"loc":{
"start":13,
"end":26
}
},
"selectionSet":{
"kind":"SelectionSet",
"selections":[
{
"kind":"Field",
"alias":null,
"name":{
"kind":"Name",
"value":"fetchByGender",
"loc":{
"start":37,
"end":50
}
},
"arguments":[
{
"kind":"Argument",
"name":{
"kind":"Name",
"value":"gender",
"loc":{
"start":51,
"end":57
}
},
"value":{
"kind":"StringValue",
"value":"M",
"loc":{
"start":59,
"end":62
}
},
"loc":{
"start":51,
"end":62
}
}
],
...
如果客户端 Schema 不符合定义的规范,就会直接抛出语法异常 Syntax Error
这种结构化的报错信息在定位问题上非常方便
判断客户端 Schema 是否按照服务端 Schema 定义的方式获取数据,比如方法名是否有误,必填项是否有值等
举个例子,调用一个不存在的接口:
{
"errors":[
{
"message":"Cannot query field {方法名} on type "Query". Did you mean {方法名}?",
"locations":[
{
"line":3,
"column":9
}
]
}
]
}
根据解析阶段的产出物 document与服务端 Schema ,定位到服务端 Schema 下具体接口的 resolve 函数并执行返回结果
GraphQL 虽然弥补了 REST API 部分不足,但是也存在几个值得思考的问题
query Authors {
author(id: "abc") {
posts {
author {
posts {
# ..
}
}
}
}
}
官方提供了不少解决方案,比如:
# Schema-level:
class MySchema < GraphQL::Schema
# ...
max_depth 10
end
# Query-level, which overrides the schema-level setting:
MySchema.execute(query_string, max_depth: 10)
# Constant complexity:
field :top_score, Integer, null: false, complexity: 10
# Dynamic complexity:
field :top_scorers, [PlayerType], null: false do
argument :limit, Integer, limit: false, default_value: 5
complexity ->(ctx, args, child_complexity) {
if ctx[:current_user].staff?
# no limit for staff users
0
else
# `child_complexity` is the value for selections
# which were made on the items of this list.
#
# We don't know how many items will be fetched because
# we haven't run the query yet, but we can estimate by
# using the `limit` argument which we defined above.
args[:limit] * child_complexity
end
}
end
query {
author(id: "abc") { # complexity: 1
posts(first: 5) { # complexity: 5
title # complexity: 5
}
}
}
前面几种方案都是服务端层级的限制,但过程浪费了不少资源,是否可以提前在客户端拦截,从而避免资源浪费呢?
GraphQL 提供了客户端层级的解决方案:节流
代码实现如下:
#1.定义数据
module Types
class HttpLogType < Types::BaseObject
field :id, ID, null: false
field :url, String
# ..
field :request_log, RequestLogType, null: false
end
end
# 2.接口支持
module Queries::HttpLog
class HttpLog < Queries::BaseQuery
description 'Find an HttpLog by ID'
argument :id, ID, required: true
argument :request_id, Integer, required: true
type Types::HttpLogType, null: true
def resolve(id:, request_id:)
::HttpLog.has_request_log(request_id).find_by(id: id)
end
end
end
module Mutations
class ResendHttpLog < BaseMutation
argument :id, Integer, required: true
type GraphQL::Types::JSON
def resolve(id:)
# ..
end
end
end
# 3.结果返回
query {
httpLog(id: 753, requestId: 101) {
id
url
}
}
# =>
{
"data": {
"httpLog": {
"id": "753",
"url": "http://cmb-gitaly.dev.gitee.work/cmbchina/0624/gitee/0624/PRchushihua/hooks/new"
}
}
}
mutation {
resendHttpLog(input: { id: 1 })
}
# =>
{
"data": {
"resendHttpLog": {
"message": "重发请求成功",
"client_mutation_id": null
}
}
}
正如官方文档说的 :
GraphQL and REST can actually co-exist in your stack. For example, you can abstract REST APIs behind a GraphQL server. This can be done by masking your REST endpoint into a GraphQL endpoint using root resolvers.
GraphQL 和 REST API 是可以结合使用的,这也是官方所推荐的方案
任何技术都有利弊,关键是结合场景权衡收益,找到适合自己的技术选型
https://www.modb.pro/u/462310
https://juejin.cn/post/6844903679640731655#heading-6
http://www.myriptide.com/introduce-graphql/
https://yuanlin.dev/posts/how-i-use-graphql-in-go-with-gqlgen
https://www.howtographql.com/advanced/4-security/
https://ruby-china.org/topics/41141
cd opt/bitnami/kafka/bin // 进入bin目录
unset JMX_PORT; // 避免端口号冲突
kafka-topics.sh --create --bootstrap-server localhost:9092 --topic <topic_name> --partitions 1 // 创建topic
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group <group_name> --describe // 查看某个消费者组的消费情况
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group <group_name> --topic <topic_name>:<partition_num1>, <partition_num2> --reset-offsets --to-offset <offset_num> -execute // 设置topic的offsets,需要先停止消费端消费,否则会报错
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list // group列表
架构图:
定义:基于 zookeeper 的分布式消息流平台,另外支持多分区、多副本,同时也是一款开源的基于发布订阅模式的消息引擎系统
术语:
Postgresql 执行 insert、delete、update、select 都是通过 postgres.c 里面的 exec_simple_query 方法,其基本流程:
start_xact_command();
parsetree_list = pg_parse_query(query_string);
querytree_list = pg_analyze_and_rewrite(parsetree, query_string, NULL, 0, NULL);
plantree_list = pg_plan_queries(querytree_list,CURSOR_OPT_PARALLEL_OK, NULL);
/*
* Run the portal to completion, and then drop it (and the receiver).
*/
(void) PortalRun(portal,
FETCH_ALL,
true, /* always top level */
true,
receiver,
receiver,
completionTag);
TODO 继续深入
poll.go
package poller
import (
"context"
"fmt"
"log"
"sync"
"time"
)
type Poller struct {
runtineGroup *goruntineGroup // 并发控制
workNum int // 记录同时在运行的最大goroutine数
sync.Mutex
ready chan struct{} // 某个goroutine已经准备好了
metric *metric // 统计当前在运行中的goroutine数量
}
func NewPoller(workerNum int) *Poller {
return &Poller{
runtineGroup: newRoutineGroup(),
workNum: workerNum,
ready: make(chan struct{}, 1),
metric: newMetric(),
}
}
// 调度器
func (p *Poller) schedule() {
p.Lock()
defer p.Unlock()
if int(p.metric.BusyWorkers()) >= p.workNum {
return
}
select {
case p.ready <- struct{}{}: // 只要满足当前goroutine数量小于最大goroutine数量 那么就通知poll去调度goroutine执行任务
default:
}
}
func (p *Poller) Poll(ctx context.Context) error {
for {
fmt.Println("=========")
p.schedule()
select {
case <-p.ready:
case <-ctx.Done():
return nil
}
LOOP:
for {
select {
case <-ctx.Done():
break LOOP
default:
task, err := p.fetch(ctx)
if err != nil {
log.Println("fetch task error:", err.Error())
break
}
fmt.Println(task)
p.metric.IncBusyWorker() // 当前正在运行的goroutine+1
p.runtineGroup.Run(func() {
if err := p.execute(ctx, task); err != nil {
log.Println("execute task error:", err.Error())
}
})
break LOOP
}
}
}
}
func (p *Poller) fetch(ctx context.Context) (string, error) {
time.Sleep(1000 * time.Millisecond)
return "task", nil
}
func (p *Poller) execute(ctx context.Context, task string) error {
defer func() {
p.metric.DecBusyWorker() // 执行完成之后 goroutine数量-1
p.schedule() // 重新调度下一个goroutine去执行任务 这一步是必须的
}()
return nil
}
metric.go
package poller
import "sync/atomic"
type metric struct {
busyWorkers uint64
}
func newMetric() *metric {
return &metric{}
}
func (m *metric) IncBusyWorker() uint64 {
return atomic.AddUint64(&m.busyWorkers, 1)
}
func (m *metric) DecBusyWorker() uint64 {
return atomic.AddUint64(&m.busyWorkers, ^uint64(0))
}
func (m *metric) BusyWorkers() uint64 {
return atomic.LoadUint64(&m.busyWorkers)
}
goroutine_group.go
package poller
import "sync"
type goruntineGroup struct {
waitGroup sync.WaitGroup
}
func newRoutineGroup() *goruntineGroup {
return new(goruntineGroup)
}
func (g *goruntineGroup) Run(fn func()) {
g.waitGroup.Add(1)
go func () {
defer g.waitGroup.Done()
fn()
}()
}
func (g *goruntineGroup) Wait() {
g.waitGroup.Wait()
}
测试
package main
import (
"context"
"fmt"
"ta/poller"
"go.uber.org/goleak"
"testing"
)
func TestMain(m *testing.M) {
fmt.Println("start")
goleak.VerifyTestMain(m)
}
func TestPoller(t *testing.T) {
producer := poller.NewPoller(5)
producer.Poll(context.Background())
}
参考资料:Go实现后台任务调度系统
问题:状态发生变化时,状态列会更新但不会保存
举个例子:调用 #close
将状态设置为 closed
,但不会自动保存
class Book < ActiveRecord::Base
# ...
aasm_event :close do
transitions :to => :closed, :from => [:opened]
end
# ...
end
解决方案:调用 close!
将设置状态并自动保存 AR 对象
目的:K8s如何对Pod进行编排
1、运维人员向 kube-apiserver 发出指令(我想干什么,我期望事情是什么状态)
2、API 响应命令,通过一系列认证授权,把 Pod 数据存储到 etcd,创建 Deployment 资源并初始化。(期望状态)
3、Controller 通过 list-watch 机制,监测发现新的 Deployment,将该资源加入到内部工作队列,发现该资源没有关联的 Pod 和 Replicaset,启用 Deployment Controller 创建 Replicaset 资源,再启用 Replicaset Controller 创建 Pod。
4、所有 Controller 被创建完成后,将 Deployment,Replicaset,Pod 资源更新存储到 etcd。
5、Scheduler 通过 list-watch 机制,监测发现新的 Pod,经过主机过滤、主机打分规则,将 Pod 绑定(binding)到合适的主机。
6、将绑定结果存储到etcd。
7、Kubelet 每隔 20s(可以自定义)向 Api Server 通过 NodeName 获取自身 Node 上所要运行的 Pod 清单,通过与自己的内部缓存进行比较,新增加 pod。
8、Kubelet 创建 Pod。
9、Kube-proxy 为新创建的 Pod 注册动态 DNS 到 CoreOS,给 Pod 的 Service 添加 iptables/ipvs 规则,用于服务发现和负载均衡。
10、Controller 通过 Control loop(控制循环)将当前 Pod 状态与用户所期望的状态做对比,如果当前状态与用户期望状态不同,则 Controller会将 Pod 修改为用户期望状态,实在不行会将此 Pod 删掉,然后重新创建 Pod。
参考资料:K8S架构原理及其工作流程
使用 Arel.sql()
避免 unsafe_raw SQL
警告
定义:其实就是允许合并指定分支的部分提交
好处:cherry-pick 命令提供参数也有不少好用的,比如 -e (编辑提交信息)及 -n (合并后无提交记录)等
参考:https://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html
在没有 ZooKeeper 的情况下运行 Kafka 有多便利?
是否有不需要停机的平滑升级方案?
2.8 版本之后的 3.0 版本会是一个特殊的搭桥版本(bridge release),在这个版本中,Quorum Controller 会和老版本的基于 ZooKeeper 的 Controller 共存,而在之后的版本才会去掉旧的 Controller 模块。这意味着如果想要从 2.8 版本以下升级到 3.0 以后的某一个版本,比如说 3.1,则需要借由 3.0 版本实现两次“跳跃”,建议持续采用灰度的方式来进行业务的升级验证
参考:
步骤:EDITOR="mate --wait" bin/rails credentials:edit
(如果存在 master.key 或者 credentials.yml.enc,需要同时删除再执行)
Dotenv.overload('.env')
从官网下载(如果因网络问题拉不下来,可以下载国内的脚本)后,把镜像修改成国内
负责多环境管理(包括 node 、ruby、go等)
brew install asdf
不要使用系统自带的 git
brew install git
另外 M1 下通过 brew install bash-completion
方式无法实现 git 补全,而是采用下边方式
curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash -o ~/.git-completion.bash
mkdir ~/.zsh
cp git-completion.bash ~/.zsh/.git-completion.bash
# ~/.zshrc
zstyle ':completion:*:*:git:*' script ~/.zsh/.git-completion.bash
fpath=(~/.zsh $fpath)
autoload -Uz compinit && compinit
terminal 显示 git 分支则可以采用以下方式
# ~/.zshrc
function parse_git_branch() {
git branch 2> /dev/null | sed -n -e 's/^\* \(.*\)/[\1]/p'
}
setopt PROMPT_SUBST
export PROMPT='%F{grey}%n%f %F{cyan}%~%f %F{green}$(parse_git_branch)%f %F{normal}$%f '
brew install mysql
navicat premium 16.0.9 无需注册码
brew install docker
网上查询下,下载规定版本的 docker desktop,可以通过镜像启动 k8s
brew install helm
brew install redis
官网下载
在 m1 芯片下通过 brew 安装始终启动不了,也确保了 java 和 zookeeper 环境存在,最后通过 docker 将 kafka 和 zookeeper 容器跑起来
---
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.0.1
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
broker:
image: confluentinc/cp-kafka:7.0.1
container_name: broker
ports:
# To learn about configuring Kafka for access across networks see
# https://www.confluent.io/blog/kafka-client-cannot-connect-to-broker-on-aws-on-docker-etc/
- "9092:9092"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://broker:29092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
执行 bundle exec db:create
报错 "grpc_c.bundle fails to load on Apple M1 (wrong architecture)"
bundle config set force_ruby_platform true.
bundle
执行bundle
报错 "ld: library not found for -lzstd while bundle install for mysql2 gem Ruby on macOS Big Sur"
gem install mysql2 -v '0.5.3' -- --with-opt-dir=$(brew --prefix openssl) --with-ldflags=-L/opt/homebrew/Cellar/zstd/xxx/lib
注意:xxx代表zstd版本
待更新
参考:https://cwhu.medium.com/kubernetes-basic-concept-tutorial-e033e3504ec0
举个例子
<div class="list" v-show=false>
...
</div>
我们都知道 v-show
底层其实就是改变了 display
属性,如果执行 document.querySelector('.list').setAttribute()
,它会将 display:none
覆盖
将了解到
什么是 Rake :一般 Ruby Web Application 包含 Framework(如 Rails、Sinatra 等)、Rack 层及 WebServer 层(如 puma、thin 等)三个部分,而 Rack 在这另外两者之间充当桥梁提供接口
Rack 实现原理 :一个可以响应 call 方法的 Ruby 对象,它仅接受来自外界的一个参数,也就是环境,然后返回一个只包含三个值的数组,按照顺序分别是状态码、HTTP Headers 以及响应请求的正文
如何使用 Rack :可以先从 Rack 是如何启动 WebServer 这个角度展开
首先在任意目录下创建如下所示的 config.ru
文件
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['get rack\'d']] }
因为 Proc 对象也能够响应 #call 方法,所以上述的 Proc 对象也可以看做是一个 Rack 应用
接着可以使用 rackup
命令在命令行中启动一个 WebServer 进程
参考:
基本**:两两比较相邻记录的关键字,如果是反序则交换,直到没有反序为止
特性:
代码实现:
class Solution {
public int[] sortArray(int[] nums) {
int len = nums.length;
//标志位
boolean flag = true;
//注意看 for 循环条件
for (int i = 0; i < len && flag; ++i) {
//如果没发生交换,则依旧为false,下次就会跳出循环
flag = false;
for (int j = 0; j < len - i - 1; ++j) {
if (nums[j] > nums[j+1]) {
swap(nums,j,j+1);
//发生交换,则变为true,下次继续判断
flag = true;
}
}
}
return nums;
}
public void swap(int[] nums,int i,int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
def solution(nums)
return [] unless nums.class == Array
flag = true
len = nums.size
i = 0
j = 0
while (i < len) && flag
flag = false
while j < len - i - 1
if nums[j] > nums[j+1]
nums[j],nums[j+1] = nums[j+1],nums[j]
flag = true
end
j += 1
end
i += 1
j = 0
end
nums
end
为了方便 TDD 驱动而搭建的测试环境,它不仅可以执行完 rspec 后清理数据库,还能在执行 rspec 时自动加载 seed
rails_hepler.rb
加入 require 'support/factory_bot'
)spec/rails_helper.rb
下加上这几句# ...
require 'rake'
# ...
RSpec.configure do |config|
# ...
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
Rails.application.load_tasks
Rake::Task['db:seed'].invoke # loading seeds
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
Docker 安装 PostgreSQL 更方便, 但 Gemfile 要求在本地安装,如何在本地不安装 PostgreSQL 下执行
gem install pg
?
libpq
是用 c 语言写的 postgreSQL 的程序接口 。在 Mac OS 环境,可以通过 brew install libpq
安装
然后指定 gem
的构建选项,命令如下:
gem install pg -- --with-opt-dir="/usr/local/opt/libpq"
如果使用bundle
,也需要指定一个配置目录,命令如下:
bundle config --local build.pg --with-opt-dir="/usr/local/opt/libpq"
bundle install
最后启动 Rails 程序就可以连接 Docker 里面的 PostgreSQL 了,减少本地安装依赖的开销
在如今人人都卷的时代,当然是要花最少的时间干最多的事。检验学习成果当然是实践出真知,但在此之前先看看工作流与基本概念
传统的前端部署往往要经历这几个阶段:
如何将这些机械重复的步骤实现自动化?
简单来说就是持续集成/持续部署,它是GitLab内置的工具,能通过持续方法进行软件开发:
持续集成就是将小的代码块推送到Git仓库中托管的应用程序代码库中,并且每次推送时,都要运行一系列脚本来构建、测试和验证代码更改,然后再将其合并到主分支中,而持续交付和部署相当于更进一步的CI,可以在每次推送到仓库默认分支的同时将应用程序部署到生产环境,这些方法使得可以在开发周期的早期发现BUG和错误,从而确保部署到生产环境的所有代码都符合为应用程序建立的代码标准
持续部署的工具有挺多,比如Jenkins、Travis CI及GitLab CI/CD,这次先介绍GitLab CI/CD
GitLab CI 是为 GitLab 提供持续集成服务的一整套系统,使用 GitLab CI 需要在仓库根目录创建一个gitlab-ci.yml
的文件,它用来指定持续集成需要运行的环境,以及要执行的脚本。还需要设置一个 GitLab Runner ,当有代码变更的时候,它会自动开始 pipeline ,并在gitlab上显示持续集成的结果
在8.0以后默认集成且启用 GitLab CI
Runner 在我看来可以理解为一个个 worker、process ,它能配合CI使用,具体的流程:
简单介绍完概念后可以快速开始实践
配置 gitlab-ci.yml
告诉CI要对你的项目做什么,仓库一旦收到任何推送,GitLab将立即查找.gitlab-ci.yml文件,并根据文件的内容在Runner上启动作业
# 定义在每个job之前运行的命令
before_script:
- echo '在每个job之前运行的命令'
# 定义在每个job之后运行的命令
after_script:
- echo '在每个job之后运行的命令'
# 定义全局变量
variables:
PROJECT: gitlab-rails-demo
IMAGE_NAME: xxx/gitlab-rails-demo
IMAGE_TAG: $CI_PIPELINE_ID
# Cache 在使用时制定一系列的文件或者文件目录,使得其在不同的 job 之间被缓存下来
# https://zhuanlan.zhihu.com/p/106971627
cache:
untracked: true
key: $PROJECTt-$CI_COMMIT_REF_NAME
paths:
- public/
stages:
- build # 构建
- test # 测试
- deploy # 部署
build_job:
stage: build
tags:
- gitlab-rails-demo
only:
- dev
- master
except: # 排除某些分支
- new-dev
script:
- echo '这里是构建docker镜像阶段'
- docker login xxx --username=$DOCKER_REGISTRY_USERNAME --password=$DOCKER_REGISTRY_PASSWORD
- docker build -t $IMAGE_NAME:$IMAGE_TAG --network webapp . # 构建docker镜像
- docker push $IMAGE_NAME:$IMAGE_TAG # 推送镜像
test_job:
stage: test
tags:
- gitlab-rails-demo
only:
- dev
- master
script:
- ls -a
- echo '这里是测试阶段'
allow_failure: true # 设置 allow_failure 的 job 失败后不会中断 CI 的执行
deploy_job:
stage: deploy
tags:
- gitlab-rails-demo
only:
- dev
- master
script:
- echo '这里是部署阶段'
- docker-compose up -d # docker-compose
after_script:
- echo y | docker image prune --filter="dangling=true" # 清理dangling镜像
- docker images
GitLab仓库下Settings-CI/CD
按指导注册
# 使用安装了最少的软件的镜像
FROM ruby:2.7.4-alpine3.14
# 安装依赖
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk update && apk add --no-cache libpq vim imagemagick sqlite-libs tzdata
RUN apk add --no-cache --virtual .temp-build-libs \
alpine-sdk gcc musl-dev build-base libffi-dev make sqlite-dev nodejs yarn
# 设置环境变量
# 来指代容器中的 rails 程序放的目录
ENV RAILS_ROOT /var/www/gitlab-rails-demo
ENV NODE_ENV production
ENV RAILS_ENV production
ENV HOST 0.0.0.0
ENV PORT 3000
ENV PIDFILE $RAILS_ROOT/tmp/pids/server.pid
ENV RAILS_MASTER_KEY 404aa1c143755a2541eba84121c52671
# 设置容器里的工作目录
WORKDIR $RAILS_ROOT
# 创建 rails 程序目录和程序运行所需要的 pids 的目录
RUN mkdir -p $RAILS_ROOT/tmp/pids
# 拷贝 docker-entrypoint.sh
COPY ./docker-entrypoint.sh .
RUN sed -i 's/\r$//g' $RAILS_ROOT/docker-entrypoint.sh
# 拷贝 Gemfile 及 lock到容器的工作目录中
# 当Gemfile 没有改变时,省略下面的 bundle install
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
# 安装 Rails 环境
RUN bundle install
# 拷贝js依赖
COPY package.json yarn.lock /app/
# 将 Dockerfile 目录下所有内容复制到容器工作目录
COPY . .
# 编译静态文件
RUN bundle exec rails webpacker:verify_install
RUN SECRET_KEY_BASE=nein bundle exec rails assets:precompile
# 删除依赖
RUN apk del .temp-build-libs
# 可执行权限
RUN chmod +x $RAILS_ROOT/docker-entrypoint.sh
# 暴露服务端口
EXPOSE 3000
# 运行 docker-entrypoint.sh
ENTRYPOINT ["./docker-entrypoint.sh"]
#!/bin/sh
# 启动服务
bundle exec puma -C config/puma.rb
exec "$@"
version: '3.9'
services:
gitlab-rails-demo:
container_name: gitlab-rails-demo
image: xxx/gitlab-rails-demo:${IMAGE_TAG:-latest}
networks:
- webapp
restart: always
expose:
- 3000
volumes:
- '/data/docker/gitlab-rails-demo/log:/var/www/gitlab-rails-demo/log'
networks:
webapp:
name: webapp
driver: bridge
upstream gitlab-rails-demo-puma {
server gitlab-rails-demo:3000;
}
## Redirects all HTTP traffic to the HTTPS host
server {
listen 80;
server_name rails.explorexd.com;
access_log /var/log/nginx/gitlab-rails-demo.log;
error_log /var/log/nginx/gitlab-rails-demo.log;
location / {
client_max_body_size 256m;
gzip on;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://gitlab-rails-demo-puma;
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.