我的三十岁

发布在 life

目录

  1. 1. 失落的2017
  2. 2. 万蛊蚀心
    1. 2018.02
    2. 2018.03
    3. 2018.04
    4. 2018.05
  3. 3. 你好死神
    1. 2018.06.05
    2. 2018.06.06
    3. 2018.06.07
    4. 2018.06.08
    5. 2018.06.09
    6. 2018.06.11
  4. 4. 昨日重现
    1. 2018.06.25
    2. 2018.06.27
    3. 2018.07.02
    4. 2018.07.03
    5. 2018.07.04
    6. 2018.07.05
    7. 2018.07.06
    8. 2018.07.07
    9. 2018.07.10
    10. 2018.07.11
  5. 5. 活着
  6. - 附录 -
  7. - 干货 -
    1. 关于体检
    2. 关于肿瘤标志物
    3. 关于看病
    4. 关于提升基本医疗素质
    5. 关于基础教育的重要性

孔老夫子说:“吾十有五,而志于学。三十而立,四十而不惑,五十而知天命,六十而耳顺,七十而从心所欲,不逾矩” 。然而我的而立之年,却是感受了两次“死亡”……

1. 失落的2017

2017,疾病先后夺去了我父亲和祖父的生命,整个家庭乃至整个家族都被巨大的阴霾所笼罩。我不清楚我和我的家人究竟是如何熬过这一年的,但在2018新年钟声敲响的那一刻,所有人都满怀希望,祈祷新的生活,美好的生活。

2. 万蛊蚀心

2018.02

祈祷好像失败了,命运从2月的某一天起跟我开了一个惊天大玩笑!

在一次下班的路上,我突然觉得后背很痒,适逢广州冬季,气候干燥,衣服穿得也多,下班赶地铁,走得快难免出点汗,干燥的皮肤在汗液的刺激下引起一些反应再正常不过,于是下意识的去挠。这一挠,更痒了,痒了再挠挠了更痒!“大弦嘈嘈如急雨,小弦切切如私语。嘈嘈切切错杂弹,大珠小珠落玉盘”。就是这种感受。

逐渐的,痒变成刺痛,从尾骨开始,到颈部,像一排排针扎过去又像无数的蚂蚁在啃咬,后背又痒又疼,头皮发麻。我以为是不是过敏了,起什么东西了,可是后背并没有明显的丘疹。我不敢再去碰触后背,衣服也不能,咬牙切齿坚持了数分钟,症状竟开始慢慢缓解了。由于我本身就是过敏体质,偶发的症状并没有在意。不过在随后的几天里,每天都会在下班赶地铁的路途中复发,时轻时重。那感觉,真的不好形容,暂且叫做万蛊蚀心吧。

2018.03

慢慢的,我好像找到了规律,只要突然遇热,比如运动、太阳晒就会立马引发症状;而缓解的方式就是立即降温,空调、冷水都行。起初以为受冬天干燥气候影响,但3月,广州逐渐变潮,我的病情却没有好转。一方面我开始在网上查找各种资料;另一方面,去!医!院!

网络上根据病征,普标指向到一种叫“胆碱能荨麻疹”的疾病。

胆碱能荨麻疹:乙酰胆碱是一种神经递质,在神经之间传递信息。当交感神经兴奋、副交感神经要分泌汗水时,乙酰胆碱将会起到作用。它是我们身体自身产生的物质,当身体对这种物质产生过敏引起的荨麻疹就叫做胆碱能荨麻疹。

这里敲黑板,重点提醒一下:不清楚神经递质、交感副交感的去看高中生物;乙酰胆碱是自身分泌的物质,胆碱能荨麻疹不是你身体内、血液内有毒,也不是吃坏什么东西,也不是哪虚。

知乎上有很多这种疾病的患者,都是好发于年轻人,没有征兆的出现,又随不同时间自愈。交流起来都是一把辛酸泪。【见附录1】

当然,网络不能治病,我还是要去了ZY医院皮肤科。三次!

2018.04

不知是不是因为胆碱能荨麻疹的症状不明显,相同症状疾病比较多,亦或是我查错了资料。总之,皮肤科的L主任给的诊断是普通荨麻疹,受热产生。相信医生嘛。

不管是什么荨麻疹,治疗的手段都差不多。各种抗组织胺药物上一遍!

这里再敲一下黑板:抗组织胺药物如扑尔敏、开瑞坦等,不是激素!不是激素!不是激素!

于是乎,氯雷他定、西替利嗪、依巴斯汀、咪唑斯汀的左勾拳、右勾拳、组合拳挨个上,但是都没用。吃药没用那就换其他方法呗。知乎网友最多提到的一个是多运动【见附录1】。真是以毒攻毒啊,受热刺激产生症状,然后迫使身体适应。刚好我这段时间在健身,尝试过确实有效。只不过每次万蛊蚀心只能换来一晚上安宁。

2018.05

在5月的一次锻炼中,课程还未过半,就觉得身体不适,心跳加速,浑身发烫,疲惫不堪。而这种症状以前从未出现。课程被迫中止。整个3月和4月都在折腾荨麻疹的事,再加上这段时间公司大小事务一起压过来,每天都觉得很忙碌,所以状态不佳也被当做累了。

然而从那天开始,只要我运动量一大,就会立马出现浑身发烫和疲惫的症状。仔细观察,居然发现罪魁祸首是我的皮肤不出汗了!虽然说人的皮肤是调节体温的器官,但主要依赖于毛孔和汗液,汗液附着在皮肤上,由于水的比热容比较大,才能加速体内温度散发。一旦不出汗,皮肤反而成了温室大棚的保温膜,体温散不出去,肯定会出现发烧、中暑的症状。

不过比较神奇的是,之前刺痒的症状缓解了很多。这个问题等不了,赶紧又去ZY医院皮肤科找了L主任。L主任表示没有见过这种情况,先天性的无汗症到是遇到过,一般是受基因影响。让我看看中医。

我向来对中医持中立态度,真的是很难遇到好的中医,但事已至此,还是要去看一下。于是转挂中医科。中医科的老阿姨一番望闻问切后,说这是“暑热”,在电脑上点开药方目录,“XXXX散”,“XXXX汤“,看起来跟武侠小说似的。我跟阿姨说我的病史以及不出汗的症状,阿姨一脸诧异,不敢相信,说皮肤要是不出汗你这会早烧死了,不可能。我还反复说了几遍事实如此,不信您摸。然后就被请出去开药了。

喝了一周的中药,还要拿中药洗澡,然后没什么然后。

第三次造访L主任,任然没有什么结论,让我看看神经内科,同时还把我的名字记到小本子上,要回去查阅资料,让我过一些时间再过来问问,L主任看上去也有一些年岁,真是有心了啊。

继续转挂神经内科,比较走运的是医生说他之前有见过这类病例,又问了我的工作,是否压力过大,考虑是植物神经功能紊乱。

植物神经又称自主神经,由交感和副交感神经系统组成。主要支配平滑肌,内脏和腺体,受大脑皮质和下丘脑支配,由于不受意志控制,所以叫自主神经。(不清楚的继续复习高中生物)

植物神经控制汗腺,如果这里出了问题,会引起不出汗的症状。治疗轻微的植物神经功能紊乱的方法比较简单和漫长,吃点谷维素、甲钴胺(维生素B12)。不过依旧没效,甚至挨了3次甲钴胺注射。于是医生建议住院挂点滴治疗。万万没想到这才是噩梦的开始。

3. 你好死神

2018.06.05

提前跟公司请好了几天假,准备前往ZY医院接受住院治疗,住院也是迫不得已,由于国家限制滥用静脉滴注,好多药物在门诊不能开,只得住院。想来也有好处,既能靠医保报销还能系统全面的检查。结果第一天就碰钉子,住院要排队,明早打电话跟住院部确认。

2018.06.06

一大早给神经科住院部打电话,护士小姐姐说有两个床位,我问能过去不,小姐姐说可以。害怕耽误时间,还专门打了车,等我屁颠屁颠到住院部时,告诉我床位满了!护士站问清情况后又解释又道歉说有重病人优先安排了,我也不好说什么,环顾一下病房,都是年纪大的患者,中风偏瘫之类,你一大小伙子难道还要跟大爷大妈抢床位不成?

2018.06.07

没打电话就直接跑去医院了,依旧没床位,问了护士说能不能临时加床,就只打个点滴,坐着都行,护士说不可以,要跟住院医生沟通。跑去问了住院医生,都很忙,一脸不耐烦推出来让去找门诊医生重新安排。然后又去找门诊医生,答复为门诊不管住院,再回去找住院部商量。得,踢一圈皮球后深感就医不容易。当然,我向来尊重医疗工作者,尤其在国内这种环境下,工作压力大,医患关系复杂。

既然没位置,不如换医院吧,只是为了打点滴,哪还不都一样,于是办理了退住院押金手续,又被护士站和住院医生踢了一圈皮球。想到医院作为传统行业,同一科室护士和医生竟然会和公司里一样,因为信息不同步,相互推脱,不禁再次感叹一番。

不耽误时间,立马打车去了附近的GW医院,这里相对人少,但也是人山人海啊。不过刚好神经科有床位,赶在中午12点前办理了住院手续。主治医师带了不少人对我进行了询问,对我这种不出汗的病征也表示好奇。

当天,例行住院检查,抽5管血,胸CT。因为不出汗也有可能是因为脑部神经问题引起,所以还做了脑和颈椎MRI。乱七八糟折腾完,收拾回家睡觉。

不得不提的是GW医院的神经科住院部和肿瘤科住院部在一起。而我的2017年,有半个月在肿瘤科住院部和父亲度过了最难以接受的时光。所以看到“肿瘤科”三个字,心中不寒而栗。这里的病人一半因为神经问题缺失活动能力,另一半是癌症病人哭天喊地。最沉重的是,好多患者的儿女不在身边,病人都由顾来的“保姆”照顾。我看到瘦的只剩下骨头的老太太,身上插着各样的管子,怕她乱动,双手被带着手套绑在床边。照顾她的也是位老阿姨,翻身擦洗,喂饭清便。每一项都是连推带搡,嘴里蹦出来的全是脏话。这场景是在难以接受,所以这也是我选择回家休息的最大原因。

2018.06.08

6月8号,高考的第二天,当天广州下暴雨,整个城市几乎被雨水淹没。本来计划早上8点前回到病房等待查房,却被大雨拦在门外,直到8:30才赶到医院。挂水,脑肌酐修复神经之类的药物。大概10点多,正当我还在笔记本上处理公司事务的时候,主治医师带着两三个医生过来了。而且,脸色不太好。

由于我早几年做过鼻窦炎手术,医生先是仔细询问了当初手术的经历,又提到说最近有没有觉得头晕不舒服。我开始察觉不太对劲,医生才说昨天的CT发现“脑子”里长了个东西。当时就感觉一身冷汗(哟吼吼吼,虽然我不出汗)。医生让我别紧张,没确诊,等输完液去办公室看看。我这那还有心思,提着输液瓶子就过去了。

A医生说东西长在额窦里。CT显示在眼睛后方有一块占位,黑黑的,圆圆的,还挺大,边界很清晰,同时很明显的看到大脑额叶被压迫。我忘了那时我问了些什么,怎么形容那时的心情?慌得一批吧!然而更慌的还在后面,抽血查出肿瘤标志物CEA为59(参考0-4.7),CA19-9为58(参考0-29)。

癌胚抗原CEA是一种光谱肿瘤标志物,它不能作为诊断某种恶性肿瘤的特异性指标,但在恶性肿瘤的鉴别诊断、病情监测、疗效评价等方面,有重要临床价值。一般吸烟、炎症,良性肿瘤可以引起轻度升高。参考值一般小于5ng/ml(不同检测设备参考值不同)

糖链抗原CA19-9同样也是一种肿瘤标志物,同样不能作为诊断某种恶性肿瘤的特异性指标,但对胰腺癌、肠癌或其他消化道癌症的病情检测,疗效评价有一定的特异性和灵敏性。参考值一般小于40ng/ml(不同检测设备参考值不同)

也就是说,我的CEA超标准12倍,CA19-9超标2倍。对于肿瘤标志物,我多少也有些了解。这个东西由于存在很高的假阳性和假阴性,不能说明什么问题,但似乎又能说明什么问题。两个放在一起,还很高,几乎告诉你消化道里肯定有东西。

这里很慌,但还是敲下黑板:假阳性是指正常人或者良性疾病也会引起肿瘤标志物升高,但一般不会很高。假阴性是指即便得了癌症,也不一定会引起肿瘤标志物升高。

我父亲当初患直肠癌的时候,这两个数值并没有异常,直到最后腹腔扩散时,跟我现在的数值差不多。当然这个数值在不同的人身上没有对比意义。

出于医生的谨慎和警惕,科室的几个医生建议我先去ZY医院拿回早几年鼻炎手术的记录和活检报告。同时安排进一步的检查。主任思维比较敏捷,鼻咽癌,胃肠癌,脑肿瘤,副肿瘤综合征什么的说了一大堆。反正都吓傻了。

医生让我尽快联系家人,我火急火燎的给老婆打了电话让她过来,这时才发现她比我镇定的多。人不能慌,一慌真的就乱了。

中午1点冒着大雨返回ZY医院,医院2点上班,度秒如年的过了一个小时,复印好出院报告即刻回到GW医院。可惜的是当初的记录并没有有价值的信息。一连串问题、一连串检查劈头盖脸的扔过来,我脑子很乱,想很多东西,但又不知道想什么。机械式的完成了增强MRI,抽血查其他肿瘤标志,然后准备明天的全腹CT。

外面下着暴雨,积水到了膝盖,公交暂停发车,交通全面瘫痪,摩的三轮要价25起步,行人基本提着鞋趟过马路。医生建议我留宿医院,但我心事重重还是想回家。于是就这样和老婆顶着大雨“游”回去了。

全腹CT需要做肠道准备,也就是要清肠。两包和爽配2升水,喝到最后真的想吐,脑子麻木了,心快死了,能做的就是一口一口往下咽。最神奇的是晚上我居然还能睡着。

2018.06.09

6月9号,星期六,医院没有什么人。很早到了医院,再喝一升甘露醇,用于充盈肠胃加强对比,到了CT操作室,换机器了,上128排CT,小姐姐说既然没人,再喝几杯水,免得做出来效果不好。还能怎样,喝呗!真的,喝水比喝啤酒难受!上机器前,我说话的声音都是颤抖的,我问小姐姐做完了能不能先告诉我结果,小姐姐说当然不能,她又不会看片。

做完CT回到病房已经9点多了,老婆一直安慰我说没事,然后B医生进来病房又做进一步咨询。说着说着聊到那个占位,B医生说东西长在脑子里,哎不是,昨天A医生说在额窦里啊。这个性质差别很大,额窦里出啥事也就来个内镜,脑袋里怕不是要开颅啊。而且脑子里会是什么?良性就是脑膜瘤,恶性来个胶质瘤?我用仅有的医学常识猜测。

心情惴惴不安,因为是周末,到了10点多,医生都走得差不多了,只有几个值班医生还在坚守岗位。病房里待不住,我打算再过去问一下关于不明占位的事情。值班医生姓聂,并不是我的主治医生,但他很耐心的翻看了我的所有检查记录,刚巧看到全腹CT结果出来了。我还没有做好心理准备,结果就呈现在屏幕上:嘛事儿没有啊!

随后聂医生根据几项检查结果给我分析了一遍:

  • 首先全腹CT正常,至少没有太大的问题
  • 脑MRI显示的占位确实在额窦,不在脑子里,边界清晰,不太像恶性肿瘤,额叶轻微弧形受压,但不会影响不出汗,因为额叶控制情绪和一部分行动,丘脑才会影响汗腺。当然人体很复杂, 也不一定
  • 过敏和炎症比较严重,这些可能会引起肿瘤标志物上升

聂医生人很好,让我悬着的心放了下来。然后他还给了我后续治疗和检查建议:

  • 去找当初鼻子的主治医生或去耳鼻喉专业医院咨询不明占位情况,毕竟不同科室医生的理解不同
  • 推荐了几家皮肤科专科医院咨询不出汗的问题
  • 去肿瘤内科进一步排查,或者先做肠胃镜排查消化道肿瘤,毕竟我有家族病史
  • 过一个月再复查肿瘤指标
  • 还是不放心只能做PET-CT全面排查

2018.06.11

星期天就被赶出医院,耗着也没用,出院前聂医生跑过来,说对我的病情很感兴趣,互加了微信,又再次表达了他的意见。随后我赶紧挂了ZY耳鼻喉科老李的号。老李是我鼻子的主治医生,他是我最信任的医生没有之一了。医术好,态度更好,稳重还幽默。尤其对待小朋友以及从外地特意来看病的人。

6月11,星期一,也是很着急的跑去ZY医院,这折腾一圈又给折腾回来了。见了老李像见了菩萨一样,寒暄了几句说好久没来了,谁还想天天和医生见啊。老李看了看片说你这个看上去像是额窦向后发育,黑色占位是额窦形成的空腔,并没有填充什么物质,没什么大问题,让我别紧张。我心想我都这么大岁数了,还发育个啥,好好发育也行,偏偏向后发育。那脑子被压着没事么?老李说压一点没事。不放心又安排内镜复查,未见异常。

半条命算是捡回来了。回想这几天,真的像死过一次似的。就好像走在路上猛地被人打了一拳,还没等回过神,那人就跑了。这几个月前前后后因为工作、因为身体消耗了太多太多精力,真的需要放松一下。

是不是对生活不太满意,很久没有笑过又不知为何,既然不快乐又不喜欢这里,不如一路向西去大理……

4. 昨日重现

大理的生活节奏真的很慢,慢的人快把所有的事都忘了。我身上还背着“炸弹”呢!

2018.06.25

当节奏又回归正轨,我才想起来要去复查一下CEA了。真是后怕,竟拖了半个月。要是真有啥事,这可就是救命的半个月。谁给我的勇气。

担心GW医院做的检查不准,于是挂了比较大的ZS医院。心想查个指标,普通号就行了。时间约在下周一7月2日。

人啊,真不能闲着,闲着就爱作,爱作就容易出事。每天晚上我开始百度CEA和CA19-9了。

  • CEA高多少有问题
  • CEA和CA19-9同时升高

一搜不要紧啊,各种信息带来的冲击好像黑暗中的细语在你耳边呢喃:你有事儿,你有事儿,你绝对有事儿

即便你是做IT的,能分辨网络上的真假信息;即便你有科学素养;即便你知道魏则西事件告诉你百度不治病还可能害人;但在“事实”陈述面前,你看到的是CEA超过10的都“有问题”;CEA和CA19-9同时超过标准的都“有问题”。我的理智很快就被击溃,随后又再次陷入恐慌。

2018.06.27

这一周过的很艰难,除了工作能让我暂时摆脱癫狂,在家的时间都是从百度看到天涯,从天涯看到丁香园。治病询问帖、专业分析贴、抗癌经验分享帖,看了就停不下来,越看越认为自己肯定有事。

期间最有感触的是《魁拔》制作人武寒青,于2015年春节因为体检查出CEA20多,复查40多,最终确诊为结肠癌3B期,和我父亲同样的分期。寒青姐最终在2017年5月11日没能摆脱病魔的魔爪。但在两年多的抗癌时期里,她在她的微信公众号“577自留地”分享了所有经历,鼓舞读者共同与病魔抗争。她的文字给与我不少力量,我后悔没有早点关注到她,没有让我父亲感受到那种力量。但同时,她的经历也告诉我有时候,病来的就是悄无声息。你以为没事,但很可能已经病至膏肓。

全腹CT正常并不代表没有问题,高分辨率CT能在肠胃充盈的情况下配合增强剂看出比较明显的器官组织病变,隆起、增厚等,但对于一些轻微溃疡,早期病变,乃至性质区分,都没有办法。还需肠胃镜检查,通过直观的观察配合组织活检才能真正确定结果。这项检查一天没做,就一天不能确诊。但这个时候,我开始害怕去做检查了。好像自己真的会被查出问题来。

2018.07.02

不容易熬过去一周,ZS医院不愧是大医院,普通号都排了一个小时。我带着资料走进病室,医生是年纪不大的姐姐,我说要复查CEA和CA19-9,之前查出来CEA 59,CA19-9 58。医生接下来的三句话差点就要了我的命:

  • 这么高!
  • 两项都高的话几乎很少见不是肿瘤引起的
  • 肠胃镜做了没有

如果说话能杀人,那让医生说出上面的几句话可能真的能行。

我哆嗦着问医生真的就很少么?医生说真的很少。老婆赶紧搭话说会不会是检查的时候出错,人工造成的。医生说这个倒是有可能,先复查吧。顺便查了CA724和CA125,同样是肿瘤标志物,作为消化道肿瘤联查能提升特异性。

晚上想了很多。母亲去年一整年心情都不好,呆在广州也没有朋友陪伴,刚好闺蜜姐妹几个人约到一起去旅游,这才刚出去没几天,要是真出事了,怎么承受得了。而老婆那边我都不敢想。脑子烧久了就会累,这几天,能入睡的方式就是这样胡思乱想。

2018.07.03

医院说结果要7月4号才能拿,但现在是2018年,报告都可以在微信查了。7月3号肯定能出结果,然后才说7月4号去拿。于是3号下午,我回到了10年前,查高考分数的感觉。不过更紧张一点,这查出来的可是命啊!

下午3点,忙完工作趁着喝水时间,点开微信。深深吸一口气,点“查询检查报告”,那一刻是闭着眼睛。有结果!但是……怎么有两条,CA724和CA125。卧槽李奶奶啊,你这还是分开出结果的?你咋知道我要等CEA和CA19-9的,上天派来逗我的吧?再吸气,点进去。CA724正常!吸气,点进去,CA125正常!这样至少心里舒服一点了。

一直忙到下班,想起来再看一眼。这时候变得异常紧张,描述不了,心理活动大概是:

  • 一定要正常啊
  • 不正常比上次降低也行啊
  • 算了,死就死了

给老子开!CA19-9 38.5(参考0-35),CEA 40.5(参考0-5)。还是高,但是“降低”了不少!

晚上回到家,突然想起来,第一次在GW医院检查时,CEA为59(参考0-5),CA19-9为58(参考0-29)。昨天在ZS医院检查是CEA 40.5(参考0-5),CA19-9 38.5(参考0-35)。这参考咋还不一样的?于是又一通搜索发现:

  • 肿瘤标志物有多重检测方法,如化学发光,电化学发光,酶免疫等等,不同的方法检测出来的值不同,范围可能也不同,以检查单的参考标准为准
  • 不同厂家的设备,检测出的结果不同,常见的厂家包括罗氏、雅培、贝克曼、西门子,都是生物医疗的巨头啊【见附录2】
  • 相同厂家的不同设备,检测出的结果也不同【见附录2】
  • 同一个设备,还可能因为做过其他检测项目受到影响,如贝克曼的ACCESS会受到叶酸也就是VB12的影响,导致测量CEA会偏高【见附录3、4】

也就是说我在两家医院的检查没有什么可对比性,也许两家医院用的东西就不一样呢。

我给聂医生发了微信,他说有下降至少不是坏事,该检查的还是继续检查。于是挂了ZY医院消化内科的专家号T教授。

2018.07.04

我居然在网上查到了ZS医院关于生物检测设备的采购招标公告,CA19-9用的是雅培,CEA用的西门子。我去,不同的项目用的设备都不一样啊!

我电话打到了ZS医院检验科,几经转接,免疫室的小哥哥接了电话。我问他们CEA和CA19-9用的分别是什么厂家的什么设备检测的。我能感受到小哥哥的一脸懵,这种问题应该没人会问吧。我又问CA19-9是不是雅培i2000。小哥哥好像清醒了,问我做什么什么检查,什么套餐。原来不同的检查套餐,用的设备都不同。这我哪知道是什么套餐,于是对话结束。

这件事让我明白,对于肿瘤标志物的跟踪检查,一定要在同一家医院,开同一个检查单,才可能有对比性。同时也感叹巨头垄断,行业没标准是多么可怕的事情。

2018.07.05

周四8:30前,先跑去GW医院抽了管血,复查一下指标。这样距离第一次检查差不多一个月,又是同一家医院。至少能有对比。然后就回到了ZY医院。消化内科的T教授是副院长,很慈祥的大叔。排队看病的人把候诊区围着水泄不通,还老有人迫不及待的跑到诊室去,然后被撵出来。10个号等了一个半小时。

我大概交代了一下病史,从不出汗那段开始。我说我该不会那什么了吧。T教授说哪就那什么了啊!好多原因都会引起CEA和CA19-9升高,不用担心,CT没什么大问题,问我还要不要查肠胃镜。我说肯定要啊,不查哪能放心。T教授还再三嘱咐不用多想。

尽管他是副院长,是教授,短短几句话在我看来更像是安慰,医学讲究证据,我一理科生,也讲究证据,我没有找到一些病例告诉我正常人CEA和CA19-9高到这个程度。几个医生也没有相关的证据。肠胃镜约到了下周三。我的心又要被折磨一周。

早上抽的血居然这么快就有结果了,10:30,我在微信里看到有新的检查报告。这一次没有之前那么紧张。点开一看,CEA为55(参考0-5),CA19-9为49(参考0-29)。果然,两家医院的结果差别很大。虽然也是下降了,但幅度很小,尤其是CEA小到可以认为是单次实验误差了。

中午,给我初高中同学发了信息,她是肿瘤科医生。简单说了两次肿瘤指标的事和其他相关检查。王同学很冷静的回复,考虑我有家族史,同时指标高,还是要等肠胃镜出结果。

2018.07.06

我的心病越来越重了,这几天居然开始看“常见胃肿瘤的CT影像分析”,“常见肠道肿瘤的影像学案例”。以至于现在能看懂全腹CT的皮毛。

天涯的抗癌帖子让我代入太深,我都打算要开个公众号为以后记录“抗癌”生涯做好准备。

最要命的是最近肠胃真的不好了,没有食欲,腹胀,嗳气,便不成形,腹部坠胀。再加上百度中各种肠癌胃癌早期症状,就是告诉你要出事。

想起个故事,把一个人四肢绑起来,放在黑暗环境中,用尺子在其手腕划一下,用水滴声造成割腕流血的假象。其实没有受伤,但潜意识会摧垮人的意志。

这一周,过的比起额窦占位事件煎熬的多。不断的往返各大医院,身上局部地方甚至开始出汗了!

2018.07.07

周六在家里实在待不住,跑去看了场《我不是药神》,最后程勇被押送监狱的路上,看到人群中出现“死去”的黄毛和吕受益,直戳心脏,哭成傻叉。

晚上继续乱查乱搜,我突然想到一开始是因为不出汗去医院的,而现在,不出汗现象略有改善,而指标也轻微降低,会不会肿瘤标志物跟不出汗有关联。百度上关于“胆碱能荨麻疹 不出汗 cea升高”的搜索结果只有两条有价值,这两条还是同一个人发的。底下“医生”的回复是不会。

腹胀的厉害,在马桶上蹲了20分钟思考人生。自从走出大学校门,生活完全不同了。工作中,生活中遇到的问题一个比一个尖锐犀利,难道说“中年”男人的油腻都是这么千锤百炼被榨出来的?

历史经验告诉我们,无数的创意灵感都是在马桶上产生的。作为一个IT男,工作中遇到的问题千奇百怪,而解决的途径无非是搜索,简单的问题靠百度,疑难杂症要靠Google,并且要用英文搜索。之前在查一系列病例信息时,也用过Google,但中文结果与百度并没有太大的差别,英文结果几乎全是专业性的论文,找到有用信息无疑是大海捞针。

比如关键字“CEA”,Google出来的结果都是学术研究之类。再比如,不论用Google还是百度,搜索“CEA升高20”,出来的都是国内的医疗网站寻医问药的帖子。而用Google搜索“CEA increase 20”,并没有太多的询问帖。所以说锅不全是百度的锅,但确实是因为国人讳疾忌医的根性,复杂的医患关系,不均衡的医疗资源,养活了一大批不专业医疗网站,这不是问题,问题是这些网站上充斥着太多中医骗子,药贩子,保健品贩子,非专业医生。整个圈子乌烟瘴气,难分是非。

所谓的灵感不过是在Google输入了“cholinergic urticaria cea”,当然,胆碱能荨麻疹是专业名词肯定不是我翻译的。Google有两个搜索按钮,一个就是“搜索”,另一个叫I’m Feeling Lucky “好运气”,就是直接展示和搜索关键字最匹配的内容。一般情况下,很少会用到“好运气”,但直到今天,我才知道Google的PM多么伟大!

当我不小心点到“好运气”,也是我第一次点到“好运气”时,出现在我眼前的是圣光!一篇叫做《Elevation of serum carcinoembryonic antigen in a case of cholinergic urticaria with failed detection of hypohidrosis by the conventional starch–iodine test》【见附录5】的论文,一例胆碱能荨麻疹患者CEA升高,同时没有检测到什么?不管没检测到什么,这篇paper真的就像黑暗中的曙光!全文讲述了一位27岁日本军人,因为在训练中中暑,无意间检测出CEA升高到19.1ng/ml,最终发现是胆碱能荨麻疹伴随AIGA(无汗/少汗症),同时提出观点,CEA可以作为AIGA的特异性标志物,以观测病情。

这跟我的病情非常相似,但仍有两个问题,AIGA会引起CA19-9同时升高么?文献中的个例CEA是19.1,那升高到40是否在普遍范围内呢?思路不能断,立马返回Google换回普通搜索,令人惊喜的是,相关的文献非常多。

《Serum carcinoembryonic antigen specifically increases among various serum markers of adenocarcinoma in hypohidrosis or conditions related to hypohidrosis》【见附录6】内容最为详细和完整,首先讲述什么是AIGA:

无汗/低汗症是各种程度出汗功能障碍的表现。其中,获得性特发性全身性无汗症(AIGA)表现为不充分的减少或出汗,除了胆碱能性荨麻疹外没有明显的神经和皮肤症状。多数好发于青年。

然后讲述5例AIGA患者,其中4人伴有胆碱能荨麻疹,他们的CEA均呈强阳性+++,平均为对照组的11.8倍;CA19-9呈弱阳性+,平均为对照组的1.93倍,而其他肿瘤标志物并无异常。

这里需要指出的是,论文配图有误,Antibody array的CA19-9 和CA125写反了!

1

随后讲述10名AIGA患者作为实验组,10名患有其他良性皮肤疾病的患者作为参照组,进行CEA和CA19-9的比对:

2

发现实验组对照参照组,CEA明显升高,具有统计学意义。

最后的结论就是良性皮肤类疾病会引起CEA和CA19-9的升高,而AIGA会引起CEA表达为强阳性,更具有特异性。所以CEA可以作为AIGA的特异性指标。但是前提是AIGA患者一定要做好肿瘤排查!以确保肿瘤标志物的升高不是由于肿瘤引起!

这篇论文给了我生的希望,仅存的疑点是,文献中的CEA最高为25.4ng/ml,使用富士的LAS-3000,化学发光法做的检测。论文又是日本写的。上面也说到不同设备检测结果差别很大,但我的CEA 40还没有落到我认为的那个“范围”。

继续看paper,《Serum carcinoembryonic antigen (CEA) as a clinical marker in acquired idiopathic generalized anhidrosis: a close correlation between serum CEA level and disease activity.》【见附录7】,同样的AIGA与CEA关系,但在这里,实验组的CEA范围是5.8-43.2 ng/ml。虽然我知道没有对比意义,但是最起码在心理上,我期望的那个范围出现了!

我一直苦苦寻找的,所谓的证据,能让我信服的,放心的,就是这些东西。我立即转发内容给聂医生,他当然表现出很浓厚的兴趣,结合我最近出汗有所改善,指标轻微下降,从医学角度分析了可能的原因。但是!肠胃镜依然要做,正如《Serum carcinoembryonic antigen specifically increases among various serum markers of adenocarcinoma in hypohidrosis or conditions related to hypohidrosis》【见附录6】所提到的,AIGA患者,依然要做好基本的肿瘤排查!毕竟我有家族病史,而且医学也只相信证据。只不过现在,至少我基本回到正常心态了。

2018.07.10

后面的几天日子过得相对轻松和正常,更让我反思心态对人的影响。很快就到了做肠胃镜检查的时间,周二晚上,需要喝掉3包和爽加3升水,这比做全腹CT时多了一升。

一大碗下肚,咸中带甜,这是第二次喝这种东西了,上一次心如死灰,没能细细品味,这次可以好好感受所谓的“菠萝味”。是我吃过的最难吃的“菠萝”!1升喝完就开始想吐,嘴里,鼻子里都充满令人恶心的味道,即使是现在,我敲下这些文字的时候,还能感受到那种感觉!第二碗喝了足足一个多小时,每次只能快速喝三口就要深呼吸,以免压不住那股恶心感。第三碗真的喝不下去了,每一个饱嗝都感觉胃要掉出来。就这样拖到了晚上10:30,第三碗也只喝掉了一半。

我想起了我爸,想到所有要接受肠胃手术的病人,术前肠胃镜检查喝一顿,术前CT喝一顿,手术喝一顿,术后每次复查喝一顿。就是这样喝水,都是巨大的痛苦,然而这些在治疗期间,不过是九牛一毛罢了。

水最终没喝完,实在是,太TMD难喝了!

2018.07.11

上考场的感觉,紧张又有期望,考完了就解放了,考砸了就考砸了呗,也是解放了!

做检查的居然是T教授,他看着我还微笑点头,这就记住我了是么?真有事啊?

躺床上摆好POSE,一针丙泊酚下去,我数了大概5秒,隐隐约约记得还比了个剪刀手……

醒来时我记得我说了什么,记不清了。有印象的是说了一句做完了啊?听到老婆说了句去缴费就只剩我一个人了。后来我老婆告诉我,醒来后说的第一句是好舒服啊,还想再做一次。怕真是打麻药打傻了!

晕,像喝醉了酒,但还是能动,我看报告都扔我身上了。迷迷糊糊拿起来,还是嘛事没有啊。老婆过来了,我问她是不是拿了假报告骗我,毕竟我家里又不是没干过这事。老婆说这不是有你名字么,还能造假。T教授也出来了,说没啥事,回去复查指标吧。我还晕乎乎嚷着:

“我知道没事,我在网上查了,跟我不出汗有关”

“网上东西别乱看,都是骗人的”

“英文的,论文”

“哦,那我不清楚,不出汗跟CEA有关,没事,没事啊”

5. 活着

活过来了!这几个月,这些事,一幕幕,跟演了一出电影似的。

肠胃没有不舒服了,就是肠易激综合征。说白了就是自己给自己吓得。

肠易激综合征,也叫肠胃功能紊乱。由于人的胃、肠道多是平滑肌,受迷走神经控制,而迷走神经容易受情绪影响。

由于找到了充分的证据,又做了全面的检查,头顶的达摩克利斯之剑也可以放下。“死过”才知道活着的意义。感谢老婆大人,感谢聂医生,老李和T教授,感谢王同学,感谢所有默默帮助我给我力量的人。

生活回到之前的样子,买车,生娃,该干啥干啥。公司来了项目经理,工作也不需要我项目、技术、管理一把抓了。人生就像一场游戏,没有人能活着通关,既然活着,就珍惜身边的人!感谢命运给我三十岁的生日礼物,活着,真好!

- 附录 -

  1. 《胆碱能性荨麻疹,痛苦,求经验分享?》
  2. 不同仪器检测的CA199结果相差很大,为什么?
  3. 【求助】什么情况出现CEA偏高,拜求
  4. 《求助》免疫荧光发,叶酸VB12对CEA测定的影响
  5. Elevation of serum carcinoembryonic antigen in a case of cholinergic urticaria with failed detection of hypohidrosis by the conventional starch–iodine test
  6. Serum carcinoembryonic antigen specifically increases among various serum markers of adenocarcinoma in hypohidrosis or conditions related to hypohidrosis.
  7. Serum carcinoembryonic antigen (CEA) as a clinical marker in acquired idiopathic generalized anhidrosis: a close correlation between serum CEA level and disease activity.
  8. 哪些癌症是可以趁早发现的?关于「癌症筛查」
  9. 普通人查癌症记住 3 条,简单便宜不被忽悠!
  10. 令人苦笑不得的CEA–谁有责任去普及检验项目的临床意义

- 干货 -

关于体检

2018我国城市地区男女患癌比例前十位:

3

消化道、呼吸道癌症占我国城市癌症的绝大部分比例,主要与空气污染、不良的饮食、生活习惯关系密切。癌症并不可怕,像胃、肠癌在I期和II期都有很高的“治愈”率,分别为90%和70%。其他癌症在早期发现也均有很高的“治愈”率,但是往往因为生活中的不操心,不在意,怕浪费钱,怕难受,耽误了最佳救命时机。

参考丁香医生的几篇科普【附录8】,结合自己的经验,总结一下体检如何”防癌”:

  • 胃肠癌:越来越年轻化
    • 大便潜血:工作后,每年体检查大便常规加潜血,由于某些肠癌早期出血量不大,或者周期性出血,所以一般要连续查三次,这个很少有人能在体检中做到,所以每年都查一下。大便潜血方便便宜,但特异性也不好,只用于辅助筛查。
    • 肛门指检:也是建议工作后每年都查一下,对于直肠息肉及癌症有一定诊断价值,但对结肠癌无意义,便宜方便,怕羞怕疼?不做也行,看下面胃肠镜。
    • 无痛胃肠镜:本来建议50岁以上开始查,但胃肠癌在30岁以下的发病率已经去到20%,折中,40岁以上都去查。每5年查一次无痛胃肠镜。由于是侵入式检查,有经济能力还是推荐做无痛,一套下来2000左右(广州价),真的汉子可以不打麻药,省3、400。但以下几类人群要提前做(复查间隔谨遵医嘱,以下仅是参考):
      • 家族有胃肠癌病史的,可提前到30乃至20岁,检查无问题可5-10年再查
      • 肠胃长期不好的,无论什么岁数,都应检查一次,检查无问题可3-5年再查
      • 检查出有溃疡、息肉的,应2-3年再查
    • 小肠镜,胶囊肠镜:小肠癌发病几率非常低,一般体检不会筛查。但很对体检中心近几年主推胶囊肠镜,这是一种同胶囊大小的机器人,通过磁力控制,可检查整个消化道,包括小肠。价格贵,视野相对较差,一般适用于胃肠镜不耐受的人群,土豪可无视。正常人群常规检查没有太大必要。
  • 肺癌:越来越与吸烟无关
    • 低剂量螺旋CT:有吸烟史的人,40岁以上,每两年体检查一次低剂量CT,50岁以上每一年查一次,由于城市污染,肺癌与吸烟的关系越来越淡,所以不吸烟的人,也可以尽早安排低剂量胸CT,检查没问题2-3年再查即可
    • X光胸透:基本没用,别吃辐射了
  • 肝癌:乙肝病人/携带者注意
    • 肝功能:体检基本必查肝功,肝功异常去专科看医生
    • 乙肝病毒DNA定量:DNA定量有助于了解病情发展,乙肝、乙肝携带病人每年必查
    • 腹部超声:腹部超声便宜无风险,检查肝脏同时,可以检查其他腹腔内脏,有经济能力或肝功异常可每年都查,其他30岁以上可1-3年查一次
    • 肿瘤标志物AFP(甲胎蛋白):体检一般都有,乙肝、乙肝携带病人每年可查,其他情况可不查。甲胎蛋白是肿瘤标志物中对肝癌特异性较好的一个,相对来说有一定意义。对于AFP升高也无需紧张,去专科听医生建议,切勿网上乱搜
  • 乳腺癌:男性也会得
    • 自查:无论男女,自查都是必须的,检查是否有肿块,是否有橘皮状皮肤
    • 超声:女性工作后每次体检都需要做一次超声,无风险价格便宜
    • 钼靶:钼靶是一种X射线检查,有轻微辐射,但比超声更精确,常规体检建议还是以超声为主,如有问题需要进一步检查再用钼靶
  • 宫颈癌:
    • HPV疫苗:现在大陆也可以打HPV疫苗了,还是黄花大闺女的有经济能力的,先打个九价。成年女子也可以打二价和四价,性价比不高
    • 刮片:HPV并不能杜绝所有宫颈癌,所以每年体检的宫颈刮片少不了。一般女性体检都会有

以上仅是高发肿瘤疾病在体检中的筛查建议,并不能预防所有的肿瘤,还是建议大家身体不适早去医院。另外,生活就是一场游戏,没人能活着通关,与其杞人忧天,不如健康生活。当然,有医院和体检中心推出PET-CT筛查早期肿瘤,这个自费8000-10000,辐射相对CT大,也不能确保所有肿瘤均可发现和确诊。土豪无视,正常人也查不起。

关于肿瘤标志物

肿瘤标志物是个坑爹的玩意!肿瘤标志物不适合用于在体检中的肿瘤筛查!!!【见附录9】

首先,目前没有什么标志物可以确诊肿瘤,抽一管血就能查癌症,这一直是所有医疗工作者正在努力的方向。目前仅有的几个如CEA、AFP、CA19-9、CA125等,距离理想的标志物还差的很远,非常远!

其次,一些肿瘤标志物的检测试剂盒的说明书中【见附录10】,如罗氏的CEA检测试剂,明确指出CEA不适用于正常人的肿瘤筛查,仅用于跟踪癌症病人病情发展。

由于目前的肿瘤标志物,有着较高的假阴性和假阳性,且影响因素尚不明确,过度依赖标志物,造成的后果难以估量。这一方面由于大部分人怕检查贵、检查麻烦、检查难受,对于抽血这种方便的形式接受程度高,另一方面是由于某些医院和体检中心抓住老百姓心里,大肆宣传标志物效果。

对于假阳性,是要钱的影响。轻微的升高很容易引起人的不安,而很多情况下,正常人本身就会造成标志物升高,更不要说各种各样未知的疾病和因素。查吧,花钱,我这一套下来奔两万,也是运气好,找到了证据,否则再继续查骨扫描,查PET-CT,直接上三万。有医保还能折腾,没医保的人折腾一次,没病也垮了。要是PET-CT都查不出原因,而医生又没给出所谓的证据,头上始终悬着一颗炸弹,心理素质差点的没事也得整出事。

对于假阴性,是要命的影响。有些人身体本不舒服,但体检发现标志物正常,很容易认为影响不大,而很多癌症早期,并不会引起标志物升高,即使到了中晚期,也有一部分癌症不会影响到肿瘤标志物。错过了最佳诊断时间,丢掉的可是一条人命。

所以做好“关于体检”小结所有的检查,不要再去浪费钱在肿瘤标志物上,而且肿瘤标志物检查并不便宜,单项在60-150之间。

关于看病

注意,网络不能看病,不能看病,不能看病!至少目前还不行!

随着定点医疗的推行,很多人像我一样,定了点就懒得再去其他医院,看病都是就近原则。但这一次的经历敲响警钟,小问题图方便毋庸置疑。但大病,诸如分娩、手术之类,有能力还是要去专科医院跑跑。毕竟接触到病例多,专业性上强太多。少走很多冤枉路,一定程度上就是省钱,当然,这个仁者见仁智者见智,同时还要结合自己的实际情况。

关于提升基本医疗素质

目前我国医疗行业还存在资源分配不均,工作压力大,医患关系复杂等诸多问题,和教育行业的因材施教一样,医生很难做到针对特殊的、罕见的但又不致命疾病的患者进行全方面的跟踪治疗。如何配合医生,更好的解决自身或家人的疾病问题?那一定是要具有基本的医学素养。

常用药、常见疾病、常见医学术语都需要掌握。不要在拿到扑尔敏后问医生吃这些都是激素,会不会对身体不好;也不要因为咳嗽去呼吸科,因为要拍CT抽血,而告诉医生开点药就好,就是上火了。如果确实因为经济问题或者没有医保定点,直接告诉医生,正规医院有良心的医生自然会给你一个经济方案。

广州作为沿海城市,有太多高新技术产业公司,公司整体员工教育素质都不低,但同时广东是乙肝大省,每10个人就有一个乙肝携带。即便如此,很多“高素质”公司依旧在不同程度上歧视乙肝或乙肝携带患者。

在发达城市,大多数医院都开展了无痛分娩,而却有不少高学历且有经济能力的男性,主张自己的妻子做传统的正常分娩。其观念是麻药打多了不好!

例子太多数不胜数,提升医疗素质不要去看国内乱七八糟的医疗问答网站,那里都是推销各种灵芝、人参的骗子,买保健品的药贩子。有病去医院,没事多看看“丁香医生”的公众号,日常用药也可以看“丁香医生”的APP,能在很大程度上提高个人医疗素养。

关于基础教育的重要性

学好数理化,走遍天下都不怕!真理。读书不要读死书,死读书。一点英文、计算机、数理化生,这些都是我这次解决问题的法宝,也是解决人生、工作中其他问题的神兵利器!

成绩可以不好,东西可得学到!

评论和共享

目录

  1. 一、K8S集群搭建
    1. 1.1 VPC组网
    2. 1.2 NAT网关与EIP打通网络
    3. 1.3 使用Kubeasz部署K8S集群
  2. 二、部署Gitlab实战
    1. 2.1 K8S Dashboard
    2. 2.2 PV与PVC
    3. 2.3 K8S部署Gitlab
    4. 2.4 使用Ingress-Nginx和阿里云SLB暴露服务
      1. 2.4.1 部署Ingress-Nginx
      2. 2.4.2 给gitlab配置ingress
      3. 2.4.3 设置阿里云SLB

前言:

考虑到公司持续集成与docker容器技术实施已有一段时间,取得了不错的效果,但对于设备运维、系统隔离、设备利用率和扩展性还有待提升,综合目前比较成熟的微服务技术,打算把现有业务迁移到K8S集群。

由于公司所有业务均部署在阿里云上,最开始就调研了阿里云自己提供的Kubernetes集群,但后来还是放弃了,主要考虑几方面:

  • 阿里云K8S集群尚不成熟,使用的版本也相对较老,不能及时更新版本
  • 阿里云K8S集群目前只支持多主多从结构,同时限定Master节点只能是3个,不能增减,这对于小型业务或者巨型业务均不适用
  • 自建原生K8S集群更有利于拓展和理解整体结构

接下来会详细介绍在阿里云搭建原生Kubernetes集群的过程。

一、K8S集群搭建

下面的实战操作基于阿里云的VPC网络,在4台ECS上搭建K8S单主多从集群,部署Gitlab,Gitlab的数据存储在阿里云NAS上,服务通过SLB暴露至外网

  • 阿里云VPC * 1
    • EIP * 2
    • NAT网关 * 1
    • 共享流量包 * 1
  • 阿里云ECS(无外网IP) * 4
  • 阿里云SLB * 4
  • 阿里云NAS * 1

1.1 VPC组网

对于VPC,新建交换机,目标网段用192.168.0.0/24,4台ECS的内网IP分别设置为192.168.0.1 ~ 192.168.0.4

1

1.2 NAT网关与EIP打通网络

由于VPC网络内,所有的ECS没有配置外网IP,所以这里要配置NAT网关和弹性IP来打通外网和VPC的通讯。

  • 开通一个NAT网关,并加入到VPC内

  • 开通两个EIP,一个用于DNAT(VPC访问外网),另一个用于SNAT(外网访问EIP)

  • 绑定EIP到NAT网关

    2

  • 配置DNAT(外网访问VPC)

    3

    • 我们有4台ECS,每台机器的22端口分别映射到EIP的不同端口上,如23301~23304,该端口用于SSH访问ECS
    • 同时映射192.168.0.1的6443端口到EIP上,如映射至23443端口,该端口用于访问K8S集群的API,见第二章内容
  • 配置SNAT(VPC访问外网)

    4

配置完成后,便可以使用绑定DNAT的EIP的映射端口通过SSH访问ECS

1.3 使用Kubeasz部署K8S集群

搭建K8S集群相对比较简单,使用kubeaszAllinOne部署即可

  • 修改hosts文件,根据实际环境配置master、node、etc的ip
  • 这里将192.168.0.1设置为master,使用单主多从的方式
  • 配置完成后重启所有ECS

二、部署Gitlab实战

2.1 K8S Dashboard

部署好集群后,我们可以使用DNAT的EIP,通过映射端口23443访问K8S API和Dashboard

https://EIP:Port/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy

  • 进入后会要求输入API的账号密码,与1.3章节hosts文件里配置的账号密码一致

  • 通过账号密码验证后可看到K8S Dashboard登录界面

    5

  • 令牌可在Master节点通过以下命令获取

    kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')

2.2 PV与PVC

K8S中的PV和PVC的概念这里不再多提,引用官方的一段解释:

A PersistentVolume (PV) is a piece of storage in the cluster that has been provisioned by an administrator. It is a resource in the cluster just like a node is a cluster resource. PVs are volume plugins like Volumes, but have a lifecycle independent of any individual pod that uses the PV. This API object captures the details of the implementation of the storage, be that NFS, iSCSI, or a cloud-provider-specific storage system.

A PersistentVolumeClaim (PVC) is a request for storage by a user. It is similar to a pod. Pods consume node resources and PVCs consume PV resources. Pods can request specific levels of resources (CPU and Memory). Claims can request specific size and access modes (e.g., can be mounted once read/write or many times read-only).

Gitlab for Docker中,我们看到Volumes 有三个,如下表所示

Local location Container location Usage
/srv/gitlab/data /var/opt/gitlab For storing application data
/srv/gitlab/logs /var/log/gitlab For storing logs
/srv/gitlab/config /etc/gitlab For storing the GitLab configuration files

所以我们也需要给Gitlab for K8S分配3个PV和PVC,这里我们用到了阿里云NAS

  • 给NAS添加挂载点,选择VPC网络和VPC的交换机

    6

  • 查看挂载地址

    7

  • SSH登录Master节点,挂载NAS,并创建文件夹(注意PV的path必须已存在才可以成功建立,所以需要先在NAS中创建文件夹)

    1
    2
    3
    4
    5
    mkdir /nas
    sudo mount -t nfs -o vers=4.0 xxx.xxx.nas.aliyuncs.com:/ /nas
    mkdir -p /gitlab/data
    mkdir -p /gitlab/logs
    mkdir -p /gitlab/config
  • 编写PV和PVC的YAML,根据实际需求替换server节点的NAS挂载地址配置以及storage大小配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    apiVersion: v1
    kind: Namespace
    metadata:
    name: gitlab
    labels:
    name: gitlab
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: gitlab-data
    labels:
    release: gitlab-data
    namespace: gitlab
    spec:
    capacity:
    storage: 500Gi
    accessModes:
    - ReadWriteMany
    persistentVolumeReclaimPolicy: Retain
    nfs:
    path: /gitlab/data
    server: xxx.xxx.nas.aliyuncs.com
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: gitlab-config
    labels:
    release: gitlab-config
    namespace: gitlab
    spec:
    capacity:
    storage: 1Gi
    accessModes:
    - ReadWriteMany
    persistentVolumeReclaimPolicy: Retain
    nfs:
    path: /gitlab/config
    server: xxx.xxx.nas.aliyuncs.com
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: gitlab-log
    labels:
    release: gitlab-log
    namespace: gitlab
    spec:
    capacity:
    storage: 1Gi
    accessModes:
    - ReadWriteMany
    persistentVolumeReclaimPolicy: Retain
    nfs:
    path: /gitlab/log
    server: xxx.xxx.nas.aliyuncs.com
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: gitlab-data-claim
    namespace: gitlab
    spec:
    accessModes:
    - ReadWriteMany
    resources:
    requests:
    storage: 500Gi
    selector:
    matchLabels:
    release: gitlab-data
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: gitlab-config-claim
    namespace: gitlab
    spec:
    accessModes:
    - ReadWriteMany
    resources:
    requests:
    storage: 1Gi
    selector:
    matchLabels:
    release: gitlab-config
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: gitlab-log-claim
    namespace: gitlab
    spec:
    accessModes:
    - ReadWriteMany
    resources:
    requests:
    storage: 1Gi
    selector:
    matchLabels:
    release: gitlab-log

2.3 K8S部署Gitlab

接下来补全Gitlab的Deployment和Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitlab
namespace: gitlab
spec:
selector:
matchLabels:
app: gitlab
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: gitlab
spec:
containers:
- image: gitlab/gitlab-ce:latest
name: gitlab
ports:
- containerPort: 80
name: gitlab-http
- containerPort: 443
name: gitlab-https
- containerPort: 22
name: gitlab-ssh
volumeMounts:
- name: gitlab-config
mountPath: /etc/gitlab
- name: gitlab-log
mountPath: /var/log/gitlab
- name: gitlab-data
mountPath: /var/opt/gitlab
volumes:
- name: gitlab-data
persistentVolumeClaim:
claimName: gitlab-data-claim
- name: gitlab-config
persistentVolumeClaim:
claimName: gitlab-config-claim
- name: gitlab-log
persistentVolumeClaim:
claimName: gitlab-log-claim
---
kind: Service
apiVersion: v1
metadata:
name: gitlab-service
labels:
app: gitlab-service
namespace: gitlab
spec:
selector:
app: gitlab
ports:
- protocol: TCP
name: gitlab-https
port: 443
targetPort: 443
- protocol: TCP
name: gitlab-http
port: 80
targetPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: gitlab-ssh-service
labels:
app: gitlab-ssh-service
namespace: gitlab
spec:
type: NodePort
selector:
app: gitlab
ports:
- protocol: TCP
name: gitlab-ssh
port: 22
targetPort: 22
nodePort: 30000
  • 注意在Deployment中,开放了Gitlab Pod的80、443和22端口,用于Gitlab的HTTP、HTTPS和SSH的访问

  • 创建了2个Service,第一个只将80和443端口开放到Cluster IP上,第二个Service通过NodePort将22端口映射到NodeIp的30000端口上

  • 我们将2.2章节PV与PVC中的相关代码和上面的代码合并,并命名成gitlab.yaml,上传到Master节点,执行命令

    1
    kubectl apply -f gitlab.yaml
  • 接下来进入Gitlab的Pod,修改gitlab的域名,并启用https访问

    1
    2
    3
    4
    5
    6
    7
    8
    kubectl get pod --namespace=gitlab
    # 获得gitlab pod名称后
    kubectl exec -it gitlab-xxxx-xxxx --namespace=gitlab /bin/bash
    # 进入pod后
    vi /etc/gitlab/gitlab.rb
    # 修改external_url 'https://xxx.xxx.com',保存后退出
    gitlab-ctl reconfigure
    exit

到这里,配置与部署基本完成了,但我们还不能从外网访问Gitlab,不过至少可以在集群内验证配置是否正确。

  • 在Master节点查看Service

    1
    kubectl get svc --namespace=gitlab

    8

    可以看到443和80端口已经开发给Cluster IP,同时22端口映射到了30000的NodePort上

  • 通过curl命令查看访问结果

    1
    curl https://10.68.88.97 --insecure

    这时返回一串包含redirect的字符,如下

    <html><body>You are being <a href="https://10.68.88.97/users/sign_in">redirected</a>.</body></html>

    表示服务已部署成功

  • 如果有telnet客户端,还可以验证30000端口,在任何一个节点上执行任意一条命令

    1
    2
    3
    4
    telnet 192.168.0.1:30000
    telnet 192.168.0.2:30000
    telnet 192.168.0.3:30000
    telnet 192.168.0.4:30000

2.4 使用Ingress-Nginx和阿里云SLB暴露服务

K8S暴露服务的方法有3种:

  • ClusterIP:集群内可访问,但外部不可访问
  • NodePort:通过NodeIP:NodePort方式可以在集群内访问,结合EIP或者云服务VPC负载均衡也可在集群外访问,但开放NodePort一方面不安全,另一方面随着应用的增多不方便管理
  • LoadBalancer:某些云服务提供商会直接提供LoadBalancer模式,将服务对接到负载均衡,其原理是基于kubernetes的controller做二次开发,并集成到K8S集群,使得集群可以与云服务SDK交互

由于我们的集群搭建在阿里云上,所以第一时间想到的是LoadBalancer方案,但很遗憾,没办法使用,原因如下:

回归到NodePort的方式,目前已有的解决方案是基于Ingress的几款工具,如Ingress-Nginx、Traefik-Ingress,他们的对比如下(注意,目前的版本是IngressNginx 0.13.0、Traefik 1.6)

  • IngressNginx和Traefik都是通过hostname方式反向代理已解决端口暴露问题
  • IngressNginx依赖于Nginx,功能更多;Traefik不依赖Nginx,所以更轻量
  • IngressNginx支持4层和7层LB,但4层也不好用,Traefik只支持7层代理
  • 目前网上关于IngressNginx的文章都是beta 0.9.X版本的信息,而IngressNginx在Github的地址也变化了,直接由Kubernetes维护,所以网上的文章基本没参考性,还需看官方文档,但是官方文档极其混乱和不完善!!! 后面会有填坑指南。

最终我们还是选择了Ingress-Nginx,结合阿里云SLB,最终的拓扑图如下所示:

9

其原理是:

  • 通过Service的ClusterIP负载Pod
  • 通过Ingress-Nginx监听Ingress配置,动态生成Nginx,并将Nginx暴露到23456的NodePort
  • 通过阿里云SLB监听所有节点的23456端口

接下来看详细步骤。

2.4.1 部署Ingress-Nginx

主要参考https://kubernetes.github.io/ingress-nginx/deploy/,并做一些小调整

  • 替换gcr.io的镜像为阿里云镜像
  • 暴露服务端口到NodePort 23456
  • 整合成一个ingress-nginx.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
app: default-http-backend
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: default-http-backend
template:
metadata:
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissible as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: registry.cn-shenzhen.aliyuncs.com/heygears/defaultbackend:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: ingress-nginx
labels:
app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: default-http-backend
---
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: registry.cn-shenzhen.aliyuncs.com/heygears/nginx-ingress-controller:0.13.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
---
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx-service
namespace: ingress-nginx
spec:
selector:
app: ingress-nginx
ports:
- protocol: TCP
port: 80
# 从默认20000~40000之间选一个可用端口,让ingress-controller暴露给外部的访问
nodePort: 23456
type: NodePort

上传到Master节点后执行命令:

1
kubectl apply -f ingress-nginx.yaml

2.4.2 给gitlab配置ingress

修改2.3章节的gitlab.yaml,添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: gitlab-ingress
namespace: gitlab
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # 强制http重定向到https
nginx.ingress.kubernetes.io/ssl-passthrough: "true" # 将请求时的ssl传递到此,如果后台监听80端口,则无需此配置
nginx.ingress.kubernetes.io/proxy-body-size: "0" # 设置client_max_body_size为0
spec:
rules:
- host: git.xxx.com # hostname
http:
paths:
- path: /
backend:
serviceName: gitlab-service
servicePort: 443 # 监听443端口

重新执行

1
kubectl apply -f gitlab.yaml

这里就有几个坑了:

  • 网上很多关于Ingress-Nginx的文章比较老旧,与新版annotations的配置不同,还是要以官方文档为准

  • annotations的配置与nginx实际配置的名称不同,不要错填,还是要以文档为准。比如上面例子中,nginx.ingress.kubernetes.io/proxy-body-size 实际上在nginx里是client_max_body_size,不要错填成nginx.ingress.kubernetes.io/client_max_body_size

  • 官方文档也有不清楚的地方,比如部分配置没有示例说明,还有的示例错误。比如上面例子中,官方给出的例子是

    10

    而事实上,value必须用双引号,否则配置将无效

2.4.3 设置阿里云SLB

阿里云SLB的设置比较简单

  • 后台服务器添加所有的K8S节点

    11

  • 配置监听,HTTP:80 -> 23456 ,HTTPS:443 -> 23456 并配置SSL证书, TCP:22 -> 30000,需要注意的是HTTP和HTTPS监听需要勾选SLB监听协议,以配合2.4.2章节中的force-ssl-redirect重定向,HTTPS中的SSL将配合ssl-passthrough传递到后台

    12

    13

  • 把SLB的公网IP添加到域名解析

至此,所有的配置完成。

评论和共享

RabbitMQ 基础介绍

发布在 architecture

目录

  1. 一、什么是RabbitMQ
  2. 二、 RabbitMQ简介
    1. 2.1 术语
    2. 2.2 基本特性
  3. 三、 官方Demo与思考
    1. 3.1 Hello World
      1. 3.1.1 操作
      2. 3.1.2 思考
    2. 3.2 工作队列
      1. 3.2.1 操作
      2. 3.2.2 思考
    3. 3.3 发布/订阅
      1. 3.3.1 操作
      2. 3.3.2 思考
    4. 3.4 路由
      1. 3.4.1 操作
      2. 3.4.2 思考
    5. 3.5 主题交换机
      1. 3.5.1 操作
      2. 3.5.2 思考
    6. 3.6 远程过程调用RPC
      1. 3.6.1 操作
      2. 3.6.2 思考
  4. 四、 思考总结
  5. 五、 场景模拟

一、什么是RabbitMQ

RabbitMQ是一个消息代理。

RabbitMQ基于AMQP协议用Erlang编写。

什么是AMQP?

  • AMQP(高级消息队列协议)是一个网络协议。它支持符合要求的客户端应用(application)和消息中间件代理(messaging middleware broker)之间进行通信。

简单来说: MQ是邮递过程,寄信人去邮局把信件放入邮箱,邮递员就会把信件投递到收件人

二、 RabbitMQ简介

2.1 术语

术语 类比
生产者Producing 寄信人,信件的来源
消息Message 信件
交换机Exchange 邮局,信件分发的场所
队列Queue 邮箱,存储邮件
绑定Binding 邮局信件和邮箱之间的关系,邮局信件按收件省市划分后归纳到不同邮箱
消费者Consuming 收件人,信件的归宿

1

2.2 基本特性

  • 消息只存储在Quene中

    • 只有邮箱,也就是Queue具有存储功能,Exchange不能存储消息
  • RoutingKey

    • RoutingKey就是消息的收件地址
    • RoutingKey可以直接写成XXX格式,也可以写成XXX.XXX.XXX格式,也可以为空
  • 交换机有4种类型

    • Direct 直连交换机
    • Fanout 扇形交换机
  • Topic 主题交换机
  • Headers 头交换机

三、 官方Demo与思考

3.1 Hello World

2

3.1.1 操作

生产者:

1
2
3
4
5
6
7
8
- 连接MQ
- 申明Queue
MQ.Queue.Name = "hello"
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = "hello"
Body = "hello wolrd"

消费者:

1
2
3
4
5
6
7
- 连接MQ
- 申明Queue
MQ.Queue.Name = "hello"
- 消费信息
MQ.Consume
Queue.Name= "hello"
Auto ack = true //注意自动ack

3.1.2 思考

  • 为何没有给Exchange命名?Exchange是什么类型?
  • 为何在生产者和消费者都申明了Queue?
  • RoutingKey和Queue.Name 有什么关系?

3.2 工作队列

3

3.2.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Queue
MQ.Queue.Name = "task_queue"
MQ.Queue.Durable = true //注意Queue持久化
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = "task_queue"
Body = "hello wolrd"
delivery_mode = persistent or 2 //注意消息持久化

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
- 连接MQ
- 申明Queue
MQ.Queue.Name = "task_queue"
MQ.Queue.Durable = true
- QoS
MQ.Qos
prefetch_count = 1 //注意QoS
- 消费信息
MQ.Consume
Queue.Name= "task_queue"
Auto ack = false //注意手动ack
- Sleep模拟处理事务
- 手动ack

3.2.2 思考

  • 多个消费者在Direct交换机,相同Queue下,如何接收消息?
  • 如何让Queue和消息持久化?
  • 可以直接给3.1中的hello队列赋予持久优设置吗?
  • 持久化有什么用?
  • 默认交换机是持久化的吗?
  • 手动ack和自动ack的区别?
  • 忘记ack怎么办?
  • QoS(Quality of Service)如何实现?

3.3 发布/订阅

4

3.3.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "logs"
MQ.Exchange.Type = "fanout"
MQ.Exchange.Durable = true
- 发送信息
MQ.Publish
Exchange = "logs"
RoutingKey = "" //注意RoutingKey
Body = "hello wolrd"

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "logs"
MQ.Exchange.Type = "fanout"
MQ.Exchange.Durable = true
- 申明临时Queue
MQ.Queue.exclusive= true //注意临时Queue
- 绑定
Binding //注意绑定
Exchange = "logs"
Queue.Name = MQ.Queue.Name
RoutingKey = ""
- 消费信息
MQ.Consume
Queue.Name = MQ.Queue.Name
Auto ack = false
- Sleep模拟处理事务
- 手动ack

3.3.2 思考

  • 扇形交换机的RoutingKey是如何配置的?
  • 扇形交换机的主要用途?
  • 什么是临时队列?
  • 临时队列与持久化可以同时设置吗?
  • 扇形交换机可以在生产者设置持久化的队列吗?

3.4 路由

5

6

3.4.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "direct_logs"
MQ.Exchange.Type = "direct" //注意Exchange类型
MQ.Exchange.Durable = true
- 发送信息
MQ.Publish
Exchange = "direct_logs"
RoutingKey = "info" or "error" or "warn" //注意RoutingKey
Body = "hello wolrd"

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "direct_logs"
MQ.Exchange.Type = "direct"
MQ.Exchange.Durable = true
- 申明临时Queue
MQ.Queue.exclusive= true
- 绑定
Binding
Exchange = "direct_logs"
Queue.Name = MQ.Queue.Name
RoutingKey = "info" or "error" or "warn"
- 消费信息
MQ.Consume
Queue.Name = MQ.Queue.Name
Auto ack = false
- Sleep模拟处理事务
- 手动ack

3.4.2 思考

  • 直连交换机多重绑定和扇形交换机有什么区别?
  • 直连交换机RoutingKey绑定有什么限制?

3.5 主题交换机

7

3.5.1 操作

生产者:

1
2
3
4
5
6
7
8
9
10
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "topic_logs"
MQ.Exchange.Type = "topic"
MQ.Exchange.Durable = true
- 发送信息
MQ.Publish
Exchange = "topic_logs"
RoutingKey = "this.abc" or "that.abc.xyz" or "abc" //注意RoutingKey写法
Body = "hello wolrd"

多个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Exchange
MQ.Exchange.Name = "topic_logs"
MQ.Exchange.Type = "topic"
MQ.Exchange.Durable = true
- 申明临时Queue
MQ.Queue.exclusive= true
- 绑定
Binding
Exchange = "topic_logs"
Queue.Name = MQ.Queue.Name
RoutingKey = "*.abc" or "#.abc" or "*.abc.*" //注意binding写法
- 消费信息
MQ.Consume
Queue.Name = MQ.Queue.Name
Auto ack = false
- Sleep模拟处理事务
- 手动ack

3.5.2 思考

  • *# 的区别?
  • 绑定键为 * 的队列会取到一个路由键为空的消息吗?
  • a.*.#a.#的区别在哪儿?

3.6 远程过程调用RPC

8

3.6.1 操作

调用端(生产者):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 调用本机某方法
调用另一个服务的接口
- 连接MQ
- 申明临时Queue
MQ.Queue.exclusive= true //注意临时Queue,用于接收回传信息
- 消费信息
MQ.Consume
Queue.Name= MQ.Queue.Name
Auto ack = true
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = "rpc_queue" //注意发送信息的队列,是服务端的队列,不是上面的临时队列
CorrelationId: corrId, //唯一id,用以与回调结果匹配
ReplyTo: q.Name, //回传地址,用以服务端将结果返回
Body = "123456" //传输id
- 接收结果
匹配CorrelationId 和 Server.CorrelationId
获得信息

服务端(消费者):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 连接MQ
- 申明Queue
MQ.Queue.Name= "rpc_queue" //注意此队列和调用者的临时队列不同
- QoS
MQ.Qos
prefetch_count = 1 //注意QoS
- 消费信息
MQ.Consume
Queue.Name= MQ.Queue.Name
Auto ack = false
- 调用接口
查询id相关信息
- 发送信息
MQ.Publish
Exchange = ""
RoutingKey = Client.ReplyTo
CorrelationId = Client.CorrelationId
Body = id相关信息

3.6.2 思考

  • 上面例子中使用了临时队列和非持久化队列,会出现什么问题?
  • 如果服务器发生故障,并且抛出异常,应该被转发到客户端吗?
  • 当没有服务器运行时,客户端如何作出反映?

四、 思考总结

  • Q:为何没有给Exchange命名?Exchange是什么类型?

    • 没有命名的交换机是默认(匿名)交换机
    • 默认交换机是Direct类型
  • Q:为何在生产者和消费者都申明了Queue?

    • 声明交换机和队列只能生效一次
    • 由于生产者和消费者无法确定启动顺序,所以两处申明可以防止招不到交换机或队列造成消息丢失
  • Q:RoutingKey和Queue.Name 有什么关系?

    • 默认或者匿名交换机的消息将会根据指定的routing_key分发到指定的队列

  • Q:多个消费者在Direct交换机,相同Queue下,如何接收消息?

    • 轮询分发
  • Q:如何让Queue和消息持久化?

    • 对于交换机和队列,设置其Durable属性
    • 对于消息,设置其发送模式为Persistent(部分语言用编码2表示)
  • Q:可以直接给3.1中的hello队列赋予持久优设置吗?

    • 不可以,队列只可申明一次,改动属性会报异常
  • Q:持久化有什么用?

    • 持久化会使MQ服务退出后,以申明的交换机、队列不仍存在,队列内的数据仍存在
    • 但持久化不能保证所有的数据都不会丢失
  • Q:默认交换机是持久化的吗?

  • Q:手动ack和自动ack的区别?

    • 自动ack会在消费者收到消息时就自动发送确认
    • 手动ack需要消费者自己手动发送确认
    • 消息没有超时的概念
    • 当消息被RabbitMQ发送给消费者之后,马上就会在内存中移除
    • 自动ack场景下,当消费者执行一个费时任务时,MQ崩溃,会导致消息丢失
    • 手动ack场景下,当消费者执行一个费时任务时,MQ崩溃,消息会被重新推送
  • Q:忘记ack怎么办?

    • 忘记ack会让MQ不断重复发送信息,导致MQ内存增加
    • 可以在MQ中查询messages_unacknowledged字段,手动处理
  • Q:QoS(Quality of Service)如何实现?

    • 设置MQ的QoS相关配置
    • 设置prefetch_count = 1

  • Q:扇形交换机的RoutingKey是如何配置的?

    • 填写为空
  • Q:扇形交换机的主要用途?

    • 发送广播
  • Q:什么是临时队列?

    • 临时队列随机生成队列名称
    • 当与消费者断开连接的时候,这个队列应当被立即删除
  • Q:临时队列与持久化可以同时设置吗?

    • 不可以,消费者断开后随机队列就删除,所以一旦MQ退出,消费者就与MQ断开连接,随机队列就会删除,不能设置持久化
  • Q:扇形交换机可以在生产者设置持久化的队列吗?

    • 可以,以保证所有消息不丢失。

    • 但是要注意,一般扇形交换机每个消费者一般独占一条队列,如果多个消费者共用一条队列,会跟直连交换机一样进行轮询分发

    • 如果在生产者设置10个持久化队列,但有20个消费者,每个消费者独占一条队列,那么只有其中的10个可以获取全部信息,其他10个消费者无法使用

    • 如果在生产者设置10个持久化队列,但有20个消费者,每2个消费者共用一条队列,那么20个消费者将轮询消费信息,不能收到完整的全部的信息

    • 所以扇形交换机一般不按照上面的方式使用

  • Q:直连交换机多重绑定和扇形交换机有什么区别?

    • 扇形交换机不能通过RoutingKey过滤消息
  • Q:直连交换机RoutingKey多重绑定有什么限制?

    • 不能用通配符方式模糊过滤消息

  • Q: *# 的区别?

    • (星号) 用来表示一个单词
    • (井号) 用来表示任意数量(零个或多个)单词
  • Q:绑定键为 * 的队列会取到一个路由键为空的消息吗?

    • 不能,星号表示至少一个单词
  • Q:a.*.#a.#的区别在哪儿?

    • 前者不可以匹配 a
    • 后者可以匹配 a

  • Q:上面例子中使用了临时队列和非持久化队列,会出现什么问题?
    • 没启动服务端时,客户机消息丢失
    • 客户端发送给服务端后,客户端断开连接,会导致回传信息丢失
  • Q:如果服务器发生故障,并且抛出异常,应该被转发到客户端吗?
    • 一般来说不需要
  • Q:当没有服务器运行时,客户端如何作出反映?
    • 对于不重要的信息,采用临时队列,丢失消息即可
    • 对于重要信息,采用持久化队列,等待服务器处理,并根据实际情况定时处理(清除或消息补偿)

五、 场景模拟

  • 账号注册后一系列短信邮件通知
    • 使用扇形交换机,短信和邮件服务作为消费者申明临时队列
    • 使用Auto ack
  • 秒杀下单
    • 使用直连交换机,秒杀服务作为生产者,订单中心作为消费者,都需要申明持久化队列
    • 使用手动ack
  • 下单后支付并返回结果
    • 使用直连交换机,下单服务作为生产者,支付服务作为消费者,都需要申明持久化队列
    • 使用手动ack
    • 消息安全性要求很高,需要消息补偿机制
  • 用户下单(订单服务接收下单请求,异步调用仓库服务查询库存是否足够)
    • 使用RPC
    • 仓库服务申明异步调用队列 A
    • 订单服务申明回传队列 B
    • 订单服务将待查询的产品id和B的地址发送到A
    • 仓库服务从A中消费信息,处理订单服务请求,并把结果发送到B
    • 订单服务从B中消费信息,接收仓库服务的查询结果

评论和共享

目录

  1. 前言
  2. 一、安装Docker
    1. 1.1 关闭selinux
    2. 1.2 删除CentOS自带Docker
    3. 1.3 安装Docker CE
    4. 1.4 安装Docker Compose
  3. 二、安装NextCloud
    1. 2.1 使用docker-compose安装NextCloud
    2. 2.2 设置开机启动
  4. 三、Nginx反向代理
    1. 3.1 域名解析
    2. 3.2 申请CA证书
    3. 3.3 安装Nginx
    4. 3.4 配置Nginx
    5. 3.5 开放防火墙端口
    6. 3.6 端口转发
  5. 四、DDNS
    1. 4.1 创建阿里云AccessKey
    2. 4.2 安装
    3. 4.3 配置

前言

本文涉及的操作系统、软件平台和其他环境如下:

  • 云盘服务器
    • CentOS 7
    • WD红盘/阵列盒(可选)
    • 所在网络需要有公网IP
    • Docker CE
    • Docker Compose
    • Nginx with SSL
  • 阿里云
    • DNS
    • CA证书

一、安装Docker

1.1 关闭selinux

如果没有专业的运维,建议关闭selinux,以免后续配置引起冲突

修改” /etc/selinux/config “文件,设置SELINUX=disabled ,保存并重启服务器

1.2 删除CentOS自带Docker

CentOS 7自带了旧版本Docker,所以先删除,如果服务器已经安装Docker请谨慎执行!!!

1
sudo yum -y remove docker docker-common container-selinux

1.3 安装Docker CE

Docker分CE和EE两个版本,这里我们用开源免费的CE版即可

1
2
3
4
5
6
7
8
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce
# 启动docker
sudo systemctl start docker
# 查看版本确认是否安装成功
docker --version

1.4 安装Docker Compose

Docker Compose是用来管理和配置多个Docker的工具,后面我们会用到它来部署NextCloud

1
2
3
4
sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose
sudo chmod +x /usr/bin/docker-compose
# 查看版本确认是否安装成功
docker-compose --version

二、安装NextCloud

2.1 使用docker-compose安装NextCloud

这里使用的安装方式来自https://hub.docker.com/r/wonderfall/nextcloud/

有兴趣可以了解所有的配置和相关逻辑,下面仅列出使用方法:

  • 将存储盘或者本地磁盘的某个分区挂载到目录” /data “下
  • 在” /etc/nextcloud “目录下创建文件” docker-compose.yml “

    1
    2
    cd /etc/nextcloud
    vi docker-compose.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    version: '2'
    services:
    nextcloud:
    image: wonderfall/nextcloud
    links:
    - nextcloud-db
    - redis
    environment:
    - UID=1000
    - GID=1000
    - UPLOAD_MAX_SIZE=30G #单个文件上传限制
    - APC_SHM_SIZE=128M
    - OPCACHE_MEM_SIZE=128
    - CRON_PERIOD=15m
    - TZ=Asia/Shanghai #修改时区
    - ADMIN_USER=admin
    - ADMIN_PASSWORD=admin
    - DOMAIN=xxxx #需要设置的域名
    - DB_TYPE=mysql
    - DB_NAME=nextcloud
    - DB_USER=nextcloud
    - DB_PASSWORD=xxxx #数据库nextcloud用户密码
    - DB_HOST=nextcloud-db
    volumes:
    - /data/docker/nextcloud/data:/data # /data/docker/nextcloud/XX 是挂载卷的位置
    - /data/docker/nextcloud/config:/config
    - /data/docker/nextcloud/apps:/apps2
    - /data/docker/nextcloud/themes:/nextcloud/themes
    expose:
    - 8888
    nextcloud-db:
    image: mariadb:10
    volumes:
    - /data/docker/nextcloud/db:/var/lib/mysql
    environment:
    - MYSQL_ROOT_PASSWORD=xxxx #数据库root用户密码
    - MYSQL_DATABASE=nextcloud
    - MYSQL_USER=nextcloud
    - MYSQL_PASSWORD=xxxx #数据库nextcloud用户密码
    redis:
    image: redis:alpine
    container_name: redis
    volumes:
    - /data/docker/nextcloud/redis:/data

    上面的配置中使用了mysql作为数据库,redis作为缓存,加速同步效率,将容器的持久数据挂载到/data目录,没有使用默认启用的全文检索工具solr,没有将端口映射至宿主机。至于如何通过宿主机访问nextcloud,请参考第三章。

  • 执行docker-compose命令部署docker容器

    1
    2
    cd /etc/nextcloud
    docker-compose up -d
  • 查看是否部署成功

    1
    docker ps -a

    1

    如果三个容器的STATUS都是UP,证明容器启动成功

  • 查看nextcloud容器ip

    1
    docker inspect root_nextcloud_1

    2

    如上图所示,查询并记录nextcloud容器的ip,供第三章使用

2.2 设置开机启动

  • 编辑 /etc/rc.local

    1
    vi /etc/rc.local
    1
    2
    3
    service docker start
    cd /etc/nextcloud
    docker-compose start
  • 赋予可执行权限

    1
    chmod +x /etc/rc.d/rc.local

三、Nginx反向代理

3.1 域名解析

将第二章docker-compose.yml 配置中的域名解析到服务器公网IP

3

3.2 申请CA证书

有条件可以购买收费CA证书,这里使用了阿里云的免费证书,现在(2017.12)阿里云刻意“隐藏”了免费证书的位置,按以下操作可以找到:

  • 保护类型选择“1个域名”
  • 选择品牌先选择赛门铁克Symantec
  • 这时候才能看到证书类型出现“免费型DV SSL”

4

购买证书后需要进行验证,通过后才能下载使用

5

下载证书请选择for Nginx,解压将其中两个文件(key和pem)拷贝至服务器” /etc/cert “目录

6

3.3 安装Nginx

如果已经安装Nginx可以跳过该步骤,否则可以执行下面的命令安装nginx

1
2
3
4
5
6
7
8
# 设置rpm源
sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
# 安装nginx
sudo yum install -y nginx
# 开机自启动nginx
systemctl enable nginx
# 启动nginx
systemctl start nginx

3.4 配置Nginx

1
vi /etc/nginx/nginx.conf

修改配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
proxy_buffering off; #关闭代理缓存
server {
listen 23456 ssl ; #监听端口为23456,并启用ssl
server_name pan.xxx.com; #域名
ssl_certificate /etc/cert/xxxxx.pem; #pem文件路径
ssl_certificate_key /etc/cert/xxxxx.key; #key文件路径
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://172.18.0.4:8888; #填写nextcloud容器的ip,端口为8888
client_max_body_size 30000m; #允许用户上传文件的大小修改成30G
}
}
}

重启nginx

1
systemctl restart nginx

3.5 开放防火墙端口

执行命令:

1
2
firewall-cmd --zone=public --add-port=23456/tcp --permanent
firewall-cmd --reload

打开23456端口以免访问受限。

3.6 端口转发

最后,我们还需要把路由器的23456端口转发至NextCloud服务器(如192.168.2.254),路由器不同,配置方式也不同,下图是H3路由器配置方式:

10

到这里,我们就可以通过 https://pan.XXX.com:23456 访问NextCloud了,需要注意我们只配置了Nginx监听SSL,也就是输入URL时,不要忘记是https

需要提醒的是,如果服务器网络是动态IP,还需做DDNS,否则IP更换后,将不能通过域名访问NextCloud

四、DDNS

由于我们想用公司自己的二级域名,又有服务器,还有阿里云的SDK,所以我们没有使用花生壳等第三方解决方案,这里使用DDNS的方式来自https://github.com/rfancn/aliyun-ddns-client

4.1 创建阿里云AccessKey

  • 进入阿里云访问控制页面,新建domain用户,并自动生成AccessKey,确认后记录access_key和access_id,注意保密

    7

  • 分配域名管理权限给domain用户

    8

4.2 安装

  • 安装python的requests包

    1
    2
    3
    yum -y install epel-release
    yum install python-pip
    pip install requests
  • 下载源码到 /usr/local

    1
    2
    cd /usr/local
    wget https://github.com/rfancn/aliyun-ddns-client/archive/master.zip
  • 解压源码

    1
    2
    yum install -y unzip
    unzip master.zip

4.3 配置

  • 重命名ddns.conf.example

    1
    2
    cd /usr/local/aliyun-ddns-client-master
    mv ddns.conf.example ddns.conf
  • 编辑ddns.service

    1
    vi ddns.service

    修改WorkingDirectory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [Unit]
    Description=Aliyun DDNS Client.
    Wants=network-online.target
    After=network.target network-online.target
    [Service]
    Type=simple
    WorkingDirectory=/usr/local/aliyun-ddns-client-master #修改工作目录
    ExecStart=/usr/bin/python ddns.py
  • 复制服务文件

    1
    2
    cp ddns.timer /usr/lib/systemd/system
    cp ddns.service /usr/lib/systemd/system
  • 修改ddns.conf

    1
    vi ddns.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [DEFAULT]
    # 填写阿里云domain用户的access_id
    access_id=XXXX
    # 填写阿里云domain用户的access_key
    access_key=XXXXX
    # Optional: not used now
    interval=600
    # Optional: turn on debug mode or not
    debug=true
    [DomainRecord1]
    # 填写一级域名,如google.com
    domain=xxxx.com
    # 填写子域名,如pan,注意不要写成pan.xxxx.com
    sub_domain=pan
    # Required: resolve type, now it only supports 'A'
    type=A
  • 启动服务并验证配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /usr/local/aliyun-ddns-client-master
    python ddns.py
    # 如果显示 2017-12-26 15:28:15 [INFO] Successfully updated DomainRecord[pan.xxxx.com]则环境正常
    systemctl daemon-reload
    systemctl start ddns.timer
    # 开机自启动
    systemctl enable ddns.timer
    # 查看服务状态
    systemctl status ddns.timer -l

    如果如下图所示,则服务正常:

    9

最后的最后,我们可以在内网或者公网通过 https://pan.xxx.com:23456 访问NextCloud,初始管理员账号密码为admin,登陆后就可以配置和使用了。

评论和共享

Scrum实施总结(一)

发布在 agile

目录

  1. 一、团队简介
    1. 1.1 团队组成
    2. 1.2 使用工具
  2. 二、 实践总结:sprint 1 - sprint 4
    1. 2.1 Sprint 1
      1. 2.1.1 迭代周期统计
      2. 2.1.2 优点
      3. 2.1.3 问题
    2. 2.2 Sprint 2
      1. 2.2.1 迭代周期统计
      2. 2.2.2 优点
      3. 2.2.3 问题
    3. 2.3 Spring 3
      1. 2.3.1 迭代周期统计
      2. 2.3.2 优点
      3. 2.3.3 成员评价和总结
    4. 2.4 Sprint 4
      1. 2.4.1 迭代周期统计
      2. 2.4.2 优点
      3. 2.4.3 问题

一、团队简介

1.1 团队组成

Dev Team:

  • 后台开发 * 1
  • 前端开发 * 1
  • 客户端开发 * 1
  • 测试 * 1

PO:

  • PO * 1
  • UI设计 * 1

Scrum Master:

  • scrum master * 1

1.2 使用工具

  • Leangoo
  • Jenkins
  • Selenium
  • TestStack.White

二、 实践总结:sprint 1 - sprint 4

2.1 Sprint 1

整个项目团队是新组建的,没有实施敏捷的经验

2.1.1 迭代周期统计

Sprint长度:2 week

统计:

WEB 总计
功能点 42 42
工作量 61 61
bug 未做探索性测试
完成功能点 39 39

结果:

功能没有完成,迭代失败

2.1.2 优点

  • 团队对于敏捷的兴趣比较高,工作积极性高

2.1.3 问题

  • 问题暴露不及时:由于团队刚刚组建,成员彼此不熟悉,遇到问题不习惯当面沟通

    在Sprint3开始后(一个月),该情况明显好转,遇到问题时,开发人员逐渐习惯整个团队来讨论解决问题

  • 没有做探索性测试:sprint1中,只针对AC做自动化测试,忽略了探索性测试

    Sprint2开始后,补充探索性测试

  • 发布时间长:没有为开发、测试、生产环境做独立配置,容易出错

    Sprint2开始后,上jenkins持续集成,Sprint3开始后,整体正常

  • 开发团队不习惯任务领取

    由于Sprint1没有持续集成,发布麻烦,所以开发不愿意逐个领任务。要求Sprint2开始后,开发人员要按优先级逐个领取任务并签名

  • 演示太慢:花费2个小时

    Sprint2开始后,由测试人员来演示,由于测试人员对AC和流程比较熟悉,演示速度能控制到1小时左右

  • 不会看燃尽图:PO和开发不会看燃尽图,不会评估开发进度

    Sprint2教会团队和PO通过Leangoo查看燃尽图

  • 立会内容表达不准确:开发团队每日立会讲述的内容不准确

    制定了一个参考模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    功能
    今天在XX配合下做完了XX相关的X个功能的开发/测试,工作量共计X,是否达成目标
    今天还在做XX相关的X个功能,工作量共计X
    明天会做完XX相关的X个功能,工作量为X
    存在什么问题,没打成目标的原因
    任务
    今天做完了哪些任务,正在做哪些任务
    明天要做完的任务,任务预期是什么时间完成
    存在什么问题

2.2 Sprint 2

2.2.1 迭代周期统计

Sprint长度:2 week

统计:

WEB 总计
功能点 39 39
工作量 64 64
bug 4 4
完成功能点 20 20

结果:

功能没有完成,迭代失败

2.2.2 优点

  • 团队沟通和配合更默契
  • 上了Jenkins,发布更快捷

2.2.3 问题

  • 团队对全量回归测试认识不足:验收会前2个小时,团队修复2个bug,之后没有及时做全量回归测试,导致验收时19个功能没通过验收

    在Sprint3开始后,将全量回归测试加入jenkins持续集成中

  • 开发人员不按需求擅自增改功能

    要求开发人员遇到有争议的需求,必须和PO商量解决,不能擅自增改AC

  • 大任务拆解:对于网站页面UI这种比较大的任务,不方便评估工作量,也不方便及时测试

    工作量过大的任务,根据模块、页面拆解成子任务

  • 测试环境预览:PO和UI设计也想看到测试环境,好尽早判断开发是否复合需求

    给PO权限,让PO和设计人员也参与到测试中

  • 优先级调整和需求替换:PO刚开始不太能把控需求的优先级,Sprint开始前,如何确认和调整?PO不清楚如果有需求变化,需要改变或增加功能,需要怎么做?

    • 在下一个Sprint开始前,PO先大概罗列近期要做的功能,开发先写出AC并评估出工作量,PO根据工作量再考虑下一个Sprint要做的功能,从而重排PB优先级。在下一个Spring计划会上,确定该周期要做的功能。
    • 一般不建议在迭代周期内增改需求,如果迭代开始后,一定有需求需要修改或增加,一般有两个方式:等量替换和请客吃饭。从现在的周期内拿出等量或工作量略大于需要修改增加的功能,保证迭代能按期完成。或PO请团队吃饭,让团队加班完成。但不论哪一种,都不能经常使用。

2.3 Spring 3

2.3.1 迭代周期统计

Sprint长度:2 week

统计:

WEB CLIENT 总计
功能点 35 36 71
工作量 67 70 137
bug 13 2 15
完成功能点 35 36 71

结果:

迭代成功

2.3.2 优点

  • 迭代周期中替换了部分需求,没有影响到迭代周期
  • 自动化测试也集成到jenkins中,整套流程正规化

2.3.3 成员评价和总结

  • 开发评价Sprint1是忙,很多技术债要偿还,要适应新的开发模式;Sprint2是茫,有点不知所措,团队和协作不流畅;Sprint3团队已经逐渐熟悉这种模式
  • PO已经找到和开发团队沟通的方法,PO遇到疑问时,如果不是很重要的,会在每天下午5点后空闲时间和开发沟通,不打断开发人员正常的节奏

2.4 Sprint 4

2.4.1 迭代周期统计

Sprint长度:1 week

统计:

WEB CLIENT 总计
功能点 12 27 39
工作量 35 17 52
bug 0 9 9
完成功能点 13 27 40

结果:

迭代成功

2.4.2 优点

  • 从Spring4开始,尝试将迭代周期长度变成1周,以适应产品发布的实际情况,过度良好
  • PO和设计人员也“参与”到测试中

2.4.3 问题

  • Scrum的节奏比较快,团队压力比较大,容易疲劳

    在Spring4后,整个团队休息一周,调整节奏、偿还技术债

评论和共享

目录

  1. 一、使用插件
    1. 1.1 安装插件
    2. 1.2 启用插件
  2. 二、配置权限
    1. 2.1 角色管理
    2. 2.2 用户管理
    3. 2.3 分配角色
  3. 三、权限问题

jenkins默认的权限管理不支持用户分组或者按项目划分权限,所以如果团队有这种需求,需要安装插件。下面将介绍使用插件来实现用户角色的管理。

一、使用插件

1.1 安装插件

安装Role-based Authorization Strategy插件

1

1.2 启用插件

进入“系统管理”的“Configure Global Security”界面,配置如下:

2

  • 启动安全
  • 使用Jenkins专有用户数据库
  • 取消勾选“允许用户注册”,一般由管理员分配
  • 使用“Role-Based Strategy”策略

【注意】:使用Role-Based Strategy策略后,先不要注销管理员账号,否则会由于后续角色权限没配置而无法登陆,如果遇到这种问题,参考第三章

二、配置权限

正确安装了插件后,就能在“系统管理”中看到“Manage and Assign Roles”

3

2.1 角色管理

  • 进入“Manage Roles”

    4

  • 根据需要编辑Global roles

    5

    • 这里创建两个全局角色
    • admin有所有权限
    • project用于分配给项目组,这里只开放只读权限
  • 根据需要编辑Project roles

    6

    • 这里通过编写表达式让角色拥有对应项目的权限
    • 如果要匹配前缀是“Dent”的项目,表达式为“Dent.*”,和一般通配符表达式不同的是,星号前面还有一个点,不要忘记了

需要注意Project roles和Global roles配置的project不同,在2.3章节中会进一步解释。

2.2 用户管理

创建用户的步骤非常简单,如下所示:

  • 进入“系统管理”的“管理用户”

    7

  • 左边栏“新建用户”,按内容填写

    8

2.3 分配角色

编辑好角色和用户后,现在把它们关联起来,让权限生效:

  • 同样进入“系统管理”的“Manage and Assign Roles”,点击“Assign Roles”

    9

  • 参考下面的配置

    10

    • 在Global roles中,添加所有的用户,然后分配对应的全局角色
    • 在Item roles中,也要添加所有的用户(管理员用户可以不用分配),分配对应的项目角色

    这里解释一下,所有的用户都分配了两个角色,Global roles和Item roles,很容易不理解或者容易犯错的是只给一般用户分配Item roles,也就是项目角色,这样分配后,用户登陆会提示没有Overall的Read权限,也就是说用户虽然有某个项目的权限,他可以通过某个项目的URL去访问,但没有总体预览权限,没法进入首页。所以必须给用户配置全局角色,以获得Overall权限。

三、权限问题

在配置权限时,如果因为配置不当,导致管理员账号不能登陆jenkins,可以按下面的方式操作:

  • 编辑config.xml

    • 你可以在宿主机的映射位置如“/var/lib/docker/volumes/jenkins/_data/”找到该文件
    • 或者在docker容器内的“/var/jenkins_home”找到
  • 修改useSecurity为false

    1
    <useSecurity>false</useSecurity>
  • 删除authorizationStrategy、securityRealm节点

  • 重启jenkins for docker

    1
    2
    docker stop myjenkins
    docker start myjenkins

上面的操作可以清空权限系统,用管理员登录后重新配置即可。

jenkins四期介绍到此就结束了,然而jenkins在实际项目中的应用功能还远远不止如此,pipline,运维监控等高级玩法,jenkins+交通灯、报警器的搞怪玩法,以后有机会再整理分享出来。

评论和共享

目录

  1. 一、节点管理
    1. 1.1 添加节点
    2. 1.2 节点服务器配置
  2. 二、 测试项目
  3. 三、测试报告插件
    1. 3.1 Publish HTML reports插件使用
    2. 3.2 测试报告无法加载CSS的问题
      1. 3.2.1 临时方案
      2. 3.2.2 永久方案

前面几篇文章主要讲了jenkins搭建和项目构建相关内容,已经能满足自动编译,自动部署等功能,但还差一项很重要的工作:自动化测试!

作为敏捷开发必不可少的工作,我们需要每次构建后都要跑一遍自动化测试,做全量回归,如果有需要还有冒烟测试。那么下面就来介绍如何实现每次构建项目后自动执行一个自动化测试任务。

一、节点管理

一般来说,测试都基于windows操作系统,比如用selenium编写web项目的自动化测试,用teststack.white编写client项目自动化测试。而我们的jenkins是通过docker容器安装部署的,实际是linux环境,这里就会遇到操作系统不兼容的问题。

事实上,不仅仅是需要测试才会遇到这种情况,如果我们要通过jenkins for docker部署一个.net framework 项目,也是同样的问题。

但jenkins提供了节点管理的功能,能搭建起一个跨系统跨平台的集群,既能解决系统兼容问题,也能解决单点jenkins性能不足的问题。

1.1 添加节点

  • jenkins“系统管理”–“管理节点” 进入节点管理页面,点左边栏“新建节点”

    1

  • 填写节点名称,选择“Permanent Agent”

    2

  • 填写配置如下

    3

    • of executors:并发量,可同时执行job的数量,默认是1,根据需要填写
    • 远程工作目录:节点服务器的工作目录,目录不要有中文
    • 用法:默认为“尽可能使用该节点”,修改为“只允许运行绑定到这台机器的Job”
    • Environment variables:环境变量,如图所示,需要增加一个键值对,表示把节点服务器的PATH环境变量赋值到jenkins节点中,如果不配置环境变量,新建的这个节点将不能读取到节点服务器上的PATH环境,比如不能启动python命令
  • save后显示如下界面,点“Launch”按钮,下载“slave-agent.jnlp”文件

    4

1.2 节点服务器配置

登陆需要用于执行测试的windows服务器

  • 安装JDK
  • 创建工作目录,和1.1配置的远程工作目录一致,如“d:\jenkins”
  • 拷贝1.1章节最后一步下载的“slave-agent.jnlp”文件到该服务器,可以放到工作目录下,目录不要有中文
  • 运行“slave-agent.jnlp”即可

可将“slave-agent.jnlp”文件放在启动项里,使其开机启动,或者做成服务

成功运行后可在jenkins节点管理看到该节点,并且状态由红字提示“不在线”变成正常

二、 测试项目

添加测试项目和添加普通项目类似:

  • 添加自由软件风格项目

  • 指定第一章创建的节点执行该项目

    5

  • 添加测试项目的git源

    6

  • 配置构建触发器

    7

    • 勾选“Build after other projects are built”,选择“Trigger only if build is stable”,填写前置项目名称,表示当这些前置项目成功构建后,执行这个项目
    • 根据需要,勾选“Build when a change is pushed to GitLab.”,用于配置Gitlab webhook触发项目,可参考该系列第二篇文章 http://wurang.net/jenkins02_use/
  • 构建环境同样根据实际需要配置

    8

  • 添加构建脚本

    9

  • 最后构建后发送邮件通知

这里容易踩坑,找不到python命令,请参考1.2章节查看环境变量键值对设置。

另外生成的测试报告不方便查看,需要在节点服务器上搭建iis或者nginx,这对于jenkins集群来说并不是很好的做法。所以我们还需一个测试报告的插件。

三、测试报告插件

3.1 Publish HTML reports插件使用

  • 安装Publish HTML reports插件

  • 编辑jenkins测试项目

  • 构建后操作增加Publish HTML reports

    10

    • HTML directory to archive:测试报告目录,相对于项目工作目录
    • Index page[s]:测试报告页面
    • Report title:报告名称
    • Keep past HTML reports:保留旧的测试报告,默认不保留
    • Always link to last build:始终连接到最新的构建
    • Include files:包含文件,如果测试报告文件夹内还有css或js文件,需要根据需要填写
  • 配置完成后,重新构建测试项目,就可以在项目左边栏点击“HTML Report”查看测试报告了,如果保留了旧的测试报告,也可以在这里选择查看

    11

  • 除了上面的方式查看测试报告外,还可以使用URL:项目地址/HTML_Report/ 来访问,比如在邮件通知模板里用下面的代码在邮件显示测试报告地址

    1
    ${PROJECT_URL}HTML_Report

    修改该测试项目的邮件通知模板,拷贝“系统设置”里默认的邮件模板内容,加入自定义的代码

    12

3.2 测试报告无法加载CSS的问题

如果测试报告用到了CSS和JS,即使我们在3.1章节中配置了“Include files”,还是不能正常加载测试报告,会丢失CSS和JS文件,控制台报错跨域问题。这是因为Jenkins由java开发,默认对跨域做了安全限制,这是jenkins的CSP(Content Security Policy)默认配置的,配置项为:

1
sandbox; default-src 'none'; img-src 'self'; style-src 'self';

在这种配置下,HTML内联样式,js,ajax都不可以使用。

解决这个问题的方式有两种,归根到底都是修改CSP:

3.2.1 临时方案

  • 进入Jenkins脚本命令行

    13

  • 输入以下命令并运行

    1
    System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

PS:需要注意

  • 执行完命令后再次构建测试项目,对新生成的测试报告才有效
  • 重启jenkins后配置将失效

结合之前的项目配置经验,我们也可以创建一个专门用来自动执行这个脚本的项目,添加触发器,每次jenkins启动后,就执行项目。让它变成一个“永久”方案。

3.2.2 永久方案

由于上面那个“永久”方案操作起来有点麻烦,还不能治本,所以我们还是考虑其他方式。

还记得第一篇文章http://wurang.net/jenkins01_docker/ 关于Dockerfile里有一段代码吗?

1
2
3
4
5
...
RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 设置环境变量,设置时区和降低安全配置以允许查看测试报告时访问到css
ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\""
USER jenkins

设置了java的环境变量JAVA_OPTS,其中就包含了CSP的修改

1
-Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\"

所以我们在制作jenkins镜像的时候,从根本上就解决了问题。

至此,jenkins已经覆盖了编译、部署、测试整个软件开发流程。下一篇,将会介绍一下jenkins 的用户和项目权限管理。

评论和共享

目录

  1. 一、 插件
    1. 1.1 进入插件中心
    2. 1.2 更新插件
    3. 1.3 安装插件
    4. 1.4 卸载与降级插件
    5. 1.5 需要的插件
  2. 二、 使用示例
    1. 2.1 创建项目
    2. 2.2 源码管理
    3. 2.3 构建触发器
    4. 2.4 构建脚本
    5. 2.5 构建环境
      1. 2.5.1 Delete workspace before build starts 排除文件夹
      2. 2.5.2 Console Output日志加时间戳
    6. 2.6 构建后发布项目
      1. 2.6.1 设置SSH Server
      2. 2.6.2 发布到SSH Server
    7. 2.7 构建后邮件通知
      1. 2.7.1 配置Extended E-mail Notification
      2. 2.7.2 在项目中使用Extended E-mail Notification
  3. 三、 其他
    1. 3.1 SSH Server root权限执行命令或脚本
    2. 3.2 jenkins工作目录相关介绍
    3. 3.3 pm2相关知识介绍

一、 插件

正式介绍使用之前,还需要准备安装一些插件,插件中心使用操作如下:

1.1 进入插件中心

1

1.2 更新插件

2

1.3 安装插件

3

1.4 卸载与降级插件

4

1.5 需要的插件

  • GitLab Plugin
  • Gitlab Hook Plugin
  • Email Extension Plugin (默认安装)
  • Publish Over SSH
  • Timestamper (默认安装)
  • Workspace Cleanup Plugin (默认安装)

二、 使用示例

注:示例为dotnet core项目和nodejs项目,项目来源为Gitlab

2.1 创建项目

创建项目如下图所示,选择自由风格项目

5

2.2 源码管理

6

选择git源,Repo URL建议使用SSH地址,这时会提示没有权限,因为我们还没有将jenkins的public key添加到gitlab上。所以我们需要先拿到jenkins的public key:

  • 在宿主机上执行命令,其中myjenkins是容器名称

    1
    docker exec -it myjenkins /bin/bash
  • 进入jenkins容器后执行命令创建一对ssh的key

    1
    ssh-keygen -t rsa
  • 一路回车键,即可完成key的创建工作,这时我们进入默认目录查看

    1
    cd ~/.ssh
  • 会出现两个文件,一个是’id_rsa’还有一个是’id_rsa.pub’,我们需要查看’id_rsa.pub’文件,并记录下来

    1
    cat id_rsa.pub

    拷贝上面的文件内容,粘贴到记事本或其他文件管理软件并保存(后续操作仍会用到) 如:

    1
    ssh-rsa xxxxxxxxxxxjenkins@xxxxxx
  • 登陆gitlab账号(不要使用个人账号,建议使用部门公共账号、管理员账号),进入设置页面的’SSH KEYS’选项卡,填入拷贝好的public key,然后点’Add key’保存

    7

  • 再回到jenkins,点击’Add’添加认证方式

    8

  • 类型选为’SSH Username with private key’,Private Key选为’From the jenkins master ~/.ssh’,Username和Description可选填,用于备注,然后点’Add’完成添加

    9

  • 使用上面新建的认证方式后,原来红色的报错提醒就消失了,表示认证通过。最后修改一下需要拉取的分支,默认是’master’分支,我这里根据需要改成了’test’分支

    10

上面的配置完成后,jenkins就能从gitlab拉取项目了,可以在项目主菜单左边栏选择’立即构建’,看看是否成功:

11

2.3 构建触发器

通过2.2章节的配置,我们能够点击’立即构建’来主动开始一次构建,实际项目中,我们可能希望提交代码后,自动开始构建,这时需要配置构建触发器。下面演示gitlab触发器配置(需要安装Gitlab Hook Plugin插件):

12

  • 勾选’Build when a change is pushed to GitLab’,记录后面的URL,如本例中的’http://192.168.2.200:8081/project/Dent_WebSite_Test

  • 点击’高级’展开更多配置项

  • 在’Allowed branches’配置项选择’Filter branches by name’,在’Include’栏填入监听的分支名称,如’test’。表示触发器只监听来自test分支的事件。

  • 点击’Generate’按钮,生成’Secret token’并记录下来

  • 登陆gitlab(权限至少是master、owner或admin),进入jenkins需要配置的对应的项目,’Settings’选项卡,’Intergrations’配置页面。

    13

  • URL填写刚才记录的URL,如’http://192.168.2.200:8081/project/Dent_WebSite_Test

  • Secret Token填写刚才记录的Secret token

  • 勾选’push events’和’Merge Request event’,表示监听push和merge事件,然后点’Add webhook’,即可看到新建的webhooks

    14

  • 点击’Test’,选择’Push events’,测试看是否返回’HTTP 200’

    15

上面的配置完成后,就可以用git提交代码或者合并代码到test,看看jenkins是否自动开始构建。

PS: 触发器根据实际情况配置,一般来说实际项目中测试环境需要用触发器自动触发构建,而生产环境建议人工构建,避免误操作提交到master分支后冲掉生产环境!

2.4 构建脚本

前面我们已经完成了拉取代码的配置,接下来就要考虑如何’处理’这些源码:

  • 在’构建’栏增加构建步骤,选择’Execute shell’,创建shell脚本

    16

  • 如下图所示,是一个nodejs项目,它依次执行几个命令

    17

    • 需要注意的是每个命令用&&符号隔开,表示当前一个命令执行成功返回exit code = 0 后,下面的指令才执行,否则退出,用于避免前面的指令执行不成功,后面的指令继续执行
    • 点击’高级’展开更多配置,可以看到’Exit code to set build unstable’,默认exit code非0都会触发整个构建为unstable,也可以手动设置,如只监听exit code 为1,才会让构建变成unstable
  • 如下图所示,是一个dotnet core项目的编译shell脚本

    18

    • 这里将编译后的文件放到了workspace的jenkins_publish目录下面,这个workspace就是jenkins拉取git项目所在的目录
    • 最后一句将测试环境的配置文件改名,用于使其生效

2.5 构建环境

2.5.1 Delete workspace before build starts 排除文件夹

一般情况下,我们可能需要每次构建前,都要清空整个工作目录,也就是jenkins拉取git项目的目录,为了防止不必要的、多余的文件产生干扰,这时就需要勾选’构建环境’里面的’Delete workspace before build starts’,前提是安装了’Workspace Cleanup Plugin’插件:

19

也有另外几种情况,比如在nodejs项目中,每次清空工作目录后,执行’npm install’或’cnpm install’都需要很长时间,所以我们想保留’node_modules’文件夹,假如我们这个项目有两个二级目录(分别叫’app’和’frontend’)下面有’node_modules’文件夹,则配置如下所示:

20

  • 点开’高级’配置,添加4个Exclude输入栏
  • ‘ **/node_modules/** ‘表示node_modules文件夹和文件不删除
  • 还需添加两个二级目录’app’和’frontend’,否则只有子文件夹,没有父文件夹,实际上就是不存在任何文件夹
  • 还需添加’ **/.git/** ‘,因为下一次构建时,保留了’app’和’frontend’的’node_modules’文件夹以及文件内容,git不允许pull项目到非空目录,所以’ **/.git/** ‘目录也不能被清除
  • 不要勾选‘Apply pattern also on directories’,可能存在bug,勾选后反而不起作用

2.5.2 Console Output日志加时间戳

如下图所示,勾选’Add timestamps to the Console Output’,前提是安装’Timestamper’插件。这样构建时,在Console Output就能看到时间戳:

21

22

2.6 构建后发布项目

最后,我们要把’编译’或’处理’后的文件发布到指定的服务器上,常用方式为SSH,也可以通过FTP,下面都以SSH作为说明和示例(检查’Publish Over SSH’插件是否安装)。

2.6.1 设置SSH Server

进入’系统管理’里面的’系统设置’,找到’Publish over SSH’:

23

  • Passphrase:不用填写(如果你在2.2节,给jenkins创建ssh key时,一路回车没有添加这一属性,就无需配置)
  • Path to Key:填写’.ssh/id_rsa’,就是2.2节生成的私钥的文件路径
  • 点’增加’按钮,新增一个SSH Server,并点击’高级’展开更多配置
  • Name:SSH Server名称,用于展示和备注
  • Hostname:SSH Server的ip地址
  • Username:SSH Server用于发布服务的用户名,这里使用了www用户
  • Remote Directory: SSH Server用于发布服务的根目录,这里使用了www用户的目录/home/www/
  • Port:SSH Server的SSH服务端口,默认22

jenkins配置完成后,还需要对SSH Server做一些配置:

  • 远程连接到SSH Server上

  • 切换到SSH Server用于发布服务的用户,如www

  • 复制2.2节jenkins公钥的内容(id_rsa.pub),粘贴到该用户的’~/.ssh/authorized_keys’文件中,如果不存在则需要创建

  • 确保’~/.ssh’文件夹权限是700

    1
    chmod 700 ~/.ssh
  • 确保’authorized_keys’文件夹权限是600

    1
    chmod 600 ~/.ssh/authorized_keys
  • 确保’~/.ssh’文件夹及其内容的所有者是用于发布服务的用户

回到jenkins,如下图所示,点击’Test Configuration’,看是否返回Success!

24

2.6.2 发布到SSH Server

进入jenkins的项目,继续完成项目的发布配置,添加’ Send files or execute commands over SSH ‘构建步骤。

25

参考下图配置(示例为nodejs项目):

26

  • Add Transfer Set:添加一个传输
  • Name:选择需要发布的SSH Server
  • 在第一个Transfer处,写一些脚本,清理和重新创建部署目录,由于发布使用www用户,删除某些文件夹可能没有权限,所以需要用’sudo’来执行,’sudo’需要root用户的密码,我们这里需要免密码操作,操作详情可以看3.1章节
  • 在第二个Transfer处,Source files填写需要发布哪些文件
    • 该目录相对于工作目录,也就是jenkins拉取项目的目录,详情可以看3.2章节
    • 所有文件和文件夹填写’ **\** ‘
    • 如果有空文件夹,点开’高级’配置,如图中第5步所示,勾选’Make empty dirs’,否则空文件夹不会被发布
    • 如果要发布指定文件夹下的所有文件以及文件夹,填写’ foldername\ ‘,注意斜杠一定要有
    • 如果要发布多个目录,用英文逗号隔开
  • Remove prefix:如图所示,如果要发布app文件夹下的所有子内容,而不要把app这个文件夹也发布过去,需要在这里配置’app’
  • Remote directory:SSH Server的发布目录,相对于2.6.1章节,SSH Server配置的Remote Directory
  • Exec command:根据实际需求,执行一些命令,图中执行脚本安装nodejs包、重启pm2服务、重启nginx服务

dotnet core的参考配置如下,相对比较简单,发布后重启supervisor和nginx即可:

27

2.7 构建后邮件通知

上面的配置完成后已经能够正常使用,为了每次构建的信息能及时通知到团队,还需要配置邮件通知。

jenkins自带一个简单版的邮件通知功能,但不能满足一些复杂需求和定制需求,所以这里使用’Extended E-mail Notification’插件

2.7.1 配置Extended E-mail Notification

  • 进入’系统管理’里面的’系统设置’,找到’Jenkins Location’,填写’系统管理员邮件地址’:

    28

  • 找到’Extended E-mail Notification’,按下图所示配置:

    29

    • SMTP server:SMTP服务器地址
    • Default user E-mail suffix:默认邮件后缀
    • 点击’高级’展开更多配置
    • 勾选’Use SMTP Authentication’
    • 填写’User Name’,’Password’和’SMTP Port’
    • Default Recipients:默认收件人
  • Default Content就是邮件模板,这里给一个参考:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
    </head>
    <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
    offset="0">
    <table width="95%" cellpadding="0" cellspacing="0"
    style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
    <tr>
    <td><br />
    <b><font color="#0B610B">构建信息</font></b>
    <hr size="2" width="100%" align="center" /></td>
    </tr>
    <tr>
    <td>
    <ul>
    <li>项目名称 : ${PROJECT_NAME}</li>
    <li>构建编号 : 第${BUILD_NUMBER}次构建</li>
    <li>GIT URL: ${GIT_URL}</li>
    <li>GIT BRANCH: ${GIT_BRANCH}</li>
    <li>GIT COMMIT: ${GIT_COMMIT}</li>
    <li>触发原因: ${CAUSE}</li>
    <li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
    <li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
    <li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
    <li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
    </ul>
    </td>
    </tr>
    <tr>
    <td><b><font color="#0B610B">变更集</font></b>
    <hr size="2" width="100%" align="center" /></td>
    </tr>
    <tr>
    <td>${JELLY_SCRIPT,template="html"}<br/>
    <hr size="2" width="100%" align="center" /></td>
    </tr>
    </table>
    </body>
    </html>

2.7.2 在项目中使用Extended E-mail Notification

回到jenkins项目配置,添加名为’Editable Email Notification’的构建后操作,配置如下:

30

  • Project Recipient List:项目收件人邮箱列表
  • Default Content:邮件内容模板,默认调用2.7.1章节配置的模板内容,也可以在这里自定义
  • Attach Build Log:是否附件构建日志,一般选择’Attach Build Log’
  • Trigger:点开’高级’,配置触发器,条件有很多,根据需要配置,这里’Recipient List’就是项目收件人邮箱列表

至此,一个’完整’的jenins项目就部署完成了(由于还没有集成测试,所以完整打引号,jenkins集成测试请参见该系列第三篇文章)。

三、 其他

3.1 SSH Server root权限执行命令或脚本

通过SSH Server发布和部署项目往往会遇到权限不够,需要用’sudo’升权限的情况,在自动化流程里,不想使用密码或者暴露密码,那么可以参考下面的方式:

  • 进入SSH Server

  • 切换到root用户

  • 编辑sudoers文件

    1
    vi /etc/sudoers
  • 在文档最后,给www用户添加无需密码的指令或脚本

    1
    www ALL=(root) NOPASSWD:/usr/bin/systemctl,/bin/supervisorctl,/usr/bin/rm

    上面的命令表示www用户以sudo执行systemctl、supervisor和rm命令时不需要密码。

    因为给www用户rm权限比较危险,所以这里也可以填某个位置的脚本文件,封装起来相对更安全,根据项目需要自行选择。

  • 在sudoers文件还需注释掉下面的配置,用于允许其他来源的控制台传输sudo命令

    1
    #Defaults requiretty

3.2 jenkins工作目录相关介绍

jenkins工作目录就是jenkins拉取git项目的目录,可以在jenkins项目左边栏点击’工作空间’查看:

32

在宿主机上,可以进入docker jenkins容器映射的卷查看,如

1
cd /var/lib/docker/volumes/jenkins/_data/workspace/项目名

或者在宿主机上直接进入docker jenkins容器内,在jenkins_home文件夹中查看

1
2
3
docker exec -it myjenkins /bin/bash #宿主机上执行
#进入容器
cd /var/jenkins_home/workspace/项目名

3.3 pm2相关知识介绍

  • 不建议给每个nodejs项目配置局部pm2,建议给SSH Server配置全局的pm2

    1
    sudo npm install pm2 -g
  • pm2作用于每一个用户,如果是用www用户部署的node项目,在root用户下不能用’pm2 status’查看到pm2托管的项目,不要用不同的用户启动pm2,以免项目冲突

  • pm2开机启动方法如下:

    配置完pm2托管的项目后执行命令

    1
    2
    pm2 save
    pm2 startup

    再用root用户执行提示反馈的命令即可

评论和共享

目录

  1. 一、 关于持续集成
    1. 1.1 什么是持续集成
    2. 1.2 为什么要持续集成
  2. 二、 搭建Jenkins
    1. 2.1 什么是jenkins
    2. 2.2 Jenkins for Docker
      1. 2.2.1 准备工作
      2. 2.2.2 拉取Jenkins for Docker
      3. 2.2.3 通过Dockerfile定制jenkins
      4. 2.2.4 使用Jenkins for Docker
      5. 2.2.4 如何复用与迁移
    3. 2.3 如何升级Jenkins

一、 关于持续集成

1.1 什么是持续集成

持续集成, 简称CI(continuous integration).是一种软件开发实践,即团队开发成员经常集成他们的工作,每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

传统瀑布模型(水平划分项目阶段):

1

敏捷开发与CI模型(垂直划分项目阶段):

2

1.2 为什么要持续集成

  • 保证质量:CI是敏捷开发重要的一步,其目的在于让产品快速迭代的同时,尽可能保持高质量
  • 减少风险:CI讲求开发、部署、测试100%通过,通过多次集成,便于检查错误
  • 较少重复过程:自动化的构建、部署与测试节省重复工作,让团队能集中精力去做更重要的事
  • 增强项目可见性:每一次集成不论成功或失败,都能获得数据和信息供团队分析与决策
  • 增强团队协作:团队成员能清楚知道每一次提交代码后产生的影响,成员之间需要更密切的沟通来保证集成成功

二、 搭建Jenkins

2.1 什么是jenkins

Jenkins是一款用Java编写的开源的持续集成工具,是目前使用范围最广的CI工具。他长这样:

3

2.2 Jenkins for Docker

上文也说到Jenkins使用Java编写,所以支持跨平台。详细的介绍和安装说明可以查看官网

我们这里用到另一种方式,Jenkins for Docker. 这种方式在使用上更为轻量,不用在服务器上安装JDK,甚至不需要去研究如何安装Jenkins. 另外通过编写Dockerfile,能定制出符合我们需求的Jenkins配置,同时能够方便的复用和迁移。

2.2.1 准备工作

  • 需要一台装有Docker的宿主机(必要)

  • 需要一些Docker的基础知识与常用命令(必要)

  • docker宿主机设置代理(非必要)

    • 创建代理配置文件

      1
      2
      mkdir -p /etc/systemd/system/docker.service.d
      vi /etc/systemd/system/docker.service.d/http-proxy.conf
    • 内容填写参考如下

      1
      2
      [Service]
      Environment="HTTP_PROXY=http://192.168.2.100:1080/" "HTTPS_PROXY=https://192.168.2.100:1080/" "NO_PROXY=localhost,127.0.0.1"

      表示使用192.168.2.100:1080作为代理,且localhost不走代理

    • 更新配置

      1
      systemctl daemon-reload
    • 重启Docker

      1
      systemctl restart docker

2.2.2 拉取Jenkins for Docker

  • 宿主机执行命令拉取Jenkins的Docker Image

    1
    docker pull jenkins

    这个镜像体积不小,约800M,由于受国内网络环境影响,下载可能很慢,如果需要给docker设置代理请参考2.2.1 “docker宿主机设置代理”

  • 查看Jenkins镜像

    1
    docker images

    4

    到这里已经可以使用jenkins了,但我们还需做一些定制,比如给Jenkins镜像安装Nodejs、dotnet Core SDK等

2.2.3 通过Dockerfile定制jenkins

  • 根据需要编写Dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    FROM jenkins
    MAINTAINER wurang
    USER root
    # 将 shell 替换为 bash
    RUN rm /bin/sh && ln -s /bin/bash /bin/sh
    # 设置中科大软件镜像源
    RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
    RUN sed -i 's|security.debian.org|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
    # upgrade
    RUN apt-get update && apt-get upgrade -y && apt-get install -y apt-utils sudo
    # 安装必要软件包
    RUN apt-get install -y build-essential curl libunwind8 gettext apt-transport-https nasm autoconf automake libtool libpng-dev pkg-config
    # 安装dotnet core 2.0
    RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \
    && mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \
    && sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list' \
    && apt-get update && apt-get install -y dotnet-sdk-2.0.0
    # 安装nodejs
    RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - && apt-get install -y nodejs
    # 安装cnpm
    RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
    # 清理缓存
    RUN apt-get clean && apt-get autoclean
    # 解决时区问题
    RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    # 设置环境变量,设置时区和降低安全配置以允许查看测试报告时访问到css
    ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\""
    USER jenkins
  • 生成定制镜像

    1
    docker build -t auto-jenkins .
  • 查看镜像

    1
    docker images

    5

    auto-jenkins即为通过Dockerfile生成的定制镜像

2.2.4 使用Jenkins for Docker

  • 运行Jenkins容器

    1
    docker run --name myjenkins -d -p 8081:8080 -p 50000:50000 -v jenkins:/var/jenkins_home auto-jenkins
    • –name myjenkins 表示为运行的docker容器命名myjenkins
    • -d 表示用后台执行命令
    • -p 8081:8080 将Jenkins容器的8080端口映射至宿主机的8081端口,必须映射容器的8080端口到宿主机,考虑到宿主机8080端口可能被占用,所以这里映射到了8081端口,记住这个端口将用于对Jenkins的访问
    • -p 50000:50000 将Jenkins容器的50000端口映射至宿主机的50000端口,必须映射容器的50000端口到宿主机
    • -v jenkins:/var/jenkins_home 将Jenkins容器的home目录作为卷挂载到宿主机 /var/lib/docker/volumes/jenkins 目录,jenkins容器的所有配置、工作信息都会存放在这里。也可以挂载到宿主机的其他目录,不过需要注意权限问题
    • auto-jenkins 表示从我们创建的名为auto-jenkins的镜像启动容器
  • 访问Jenkins

    启动容器后,可以看到如下所示的信息,记住红框内的密钥信息

    6

    启动完成后就可以通过访问宿主机IP+容器8080的映射端口来访问Jenkins了,如通过上面的配置,我们可以访问 http://XXXX:8081

    7

    这里需要填入刚才记住的密钥,如果忘记了,可以在宿主机的挂载卷内找到

    1
    cat /var/lib/docker/volumes/jenkins/_data/secrets/initialAdminPassword

    然后安装推荐插件

    8

    9

    设置管理员账号

    10

  • 开机自动运行容器

    最后不要忘了给宿主机设置开机启动Jenkins容器,可添加下面的开机脚本命令

    1
    docker start myjenkins

2.2.4 如何复用与迁移

如果需要迁移整套jenkins或者在其他服务器复用,可以按照下面的步骤:

  • 新服务器安装Docker
  • 新服务器拉取Jenkins Image
  • 拷贝之前编写好的Dockerfile到新服务器,运行build脚本创建auto-jenkins镜像
  • 运行容器
  • 访问Jenkins
  • 拷贝旧服务器的挂载卷 /var/lib/docker/volumes/jenkins 到新服务器对应位置(迁移则需要这一步,复用不需要)

2.3 如何升级Jenkins

初次访问Jenkins for Docker,一般会遇到升级提示

11

升级步骤如下:

  • 右键”download”获取更新包地址

  • 进入jenkins容器

    1
    docker exec -it -u root myjenkins /bin/bash
  • 执行命令

    1
    2
    3
    4
    cd /usr/share/jenkins/ # 进入jenkins目录
    mkdir bak # 创建备份文件夹
    mv jenkins.war bak/jenkins.war.bak # 备份war包
    wget http://updates.jenkins-ci.org/download/war/2.73.2/jenkins.war # 下载更新包,地址为第一步获取的更新地址
  • 重启jenkins

    访问jenkins_url/restart 如 “http://XXXX:8081/restart” 重启jenkins

评论和共享

最近项目组上持续集成,于是利用jenkins的docker镜像做二次开发,原本写好的Dockerfile在生成docker image后不小心被误删,一番折腾后终于找了回来。

如下图所示:

  • jenkins为原始镜像
  • auto-jenkins为使用Dockerfile生成的镜像

1

使用history命令查询auto-jenkins镜像所执行的所有命令:

1
docker history auto-jenkins --no-trunc

2

可以从查询结果中识别出Dockerfile以外的命令,然后自下而上,找回Dockerfile的执行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM jenkins
USER root
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN sed -i 's|security.debian.org|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
RUN apt-get update && apt-get upgrade -y && apt-get install -y apt-utils sudo
RUN apt-get install -y build-essential curl libunwind8 gettext apt-transport-https
RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \
&& mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \
&& sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list' \
&& apt-get update && apt-get install -y dotnet-sdk-2.0.0
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - && apt-get install -y nodejs
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN apt-get clean && apt-get autoclean
RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV JAVA_OPTS=-Duser.timezone=Asia/Shanghai
USER jenkins

综上,从Docker Image找回Dockerfile,需满足如下条件:

  • 需存在Dockerfile生成的Docker Image
  • 利用Docker Image只能找到任何已提交到Image的指令,如Dockerfile生成的Image,或Container提交的Image。如果在Container中执行的指令没有Commit到Image,则不能用Image的history命令查询。

评论和共享

作者的图片

Wu Rang

Everything begin with HelloWorld!


System Architect


Guangzhou