Wordpress长图片功能实现

目标

在Wordpress的每篇文章添加TurnToJPG功能,点击该按钮以后,由博文直接生成长文字图片。

搭建测试环境

为了及时测试我们的博客,快速搭建一个基于docker的开发环境:

$ sudo docker pull wordpress
$ sudo docker pull mariadb
$ sudo docker pull corbinu/docker-phpmyadmin

创建一个docker-compose的yml文件,启动之:

$ vim docker-compose.yml 
wordpress:
  image: wordpress
  links:
    - wordpress_db:mysql
  ports:
    - 8080:80
wordpress_db:
  image: mariadb
  environment:
    MYSQL_ROOT_PASSWORD: examplepass
phpmyadmin:
  image: corbinu/docker-phpmyadmin
  links:
    - wordpress_db:mysql
  ports:
    - 8181:80
  environment:
    MYSQL_USERNAME: root
    MYSQL_ROOT_PASSWORD: examplepass

现在启动服务:

$ sudo docker-compose up -d
Creating mywordpress_wordpress_db_1
Creating mywordpress_wordpress_1
Creating mywordpress_phpmyadmin_1

打开http://127.0.0.1:8080即可访问到我们的测试站点。在wordpress后台 查找并安装tdPersona主题.

分析

所用主题模板

随机拷贝某篇博文的html, 而后:

$ cat test.html | grep -i themes
<link rel='stylesheet' id='tdpersona-icons-css'  href='http://www.hahahahohoho.com.cn/wp-content/themes/tdpersona/css/font-awesome.min.css?ver=4.6.2' type='text/css' media='all' />
<link rel='stylesheet' id='tdpersona-framework-css'  href='http://www.hahahahohoho.com.cn/wp-content/themes/tdpersona/css/bootstrap.min.css?ver=4.6.2' type='text/css' media='all' />
<link rel='stylesheet' id='style-css'  href='http://www.hahahahohoho.com.cn/wp-content/themes/tdpersona/style.css?ver=4.6.2' type='text/css' media='all' />

可见原博主选用的是tdpersona主题.

找到需要图形化的部分

在chromium里按F12, 耐心找寻你需要的那一部分。

/images/2017_01_12_19_54_51_636x338.jpg

我的:

<article id="post-10746" class="post-10746 post type-post status-publish format-standard hentry category-1">
// .........
</article>

进一步看,发现我们需要在<head></head>动文章:

/images/2017_01_12_12_21_05_626x149.jpg

	<header class="entry-header">
		<h2 class="entry-title"><?php the_title(); ?></h2>

		<?php if ( 'post' == get_post_type() ) : ?>
			<?php tdpersona_post_date(); ?>
		<?php endif; ?>

		<?php if ( has_post_thumbnail() ): ?>
		<div class="post-thumb">
			<?php the_post_thumbnail(); ?>
		</div><!-- .post-thumb -->
		<?php endif; ?>

	</header><!-- .entry-header -->

代码修改

/var/www/html/wp-content/themes/tdpersona/template-parts/content-single.php:

首尾分别添加如下行数(+代表添加的行数):

+ <div class="capturepost">
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	</footer><!-- .entry-meta -->
</article><!-- #post -->
+ </div>

/var/www/html/wp-content/themes/tdpersona/inc/template-tags.php:

	$output .= '<div class="entry-date">';
	$output .= '<a href="'.esc_url( get_permalink() ).'">'.$date_format_html.'</a>';
	$output .= '</div><!-- .entry-date -->';

+	$output .= '<div class="save-to-jpg", align="right">';
+	$output .= '<a href="javascript:genPostShot()">TurnToJPG --> <i class="fa fa-camera-retro fa-2x"></i></a><a id="test"></a>';
+	$output .= '</div><!-- .save-to-jpg -->'; 
+	$output .= '<hr>';

这将在标题栏下面添加一个图标和链接,点击该图标将触发javascript函数,将当前页面 转变为jpg图片.

/var/www/html/wp-includes/formatting.php:

		<script type="text/javascript">
			window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
			!function(a,b,c){function d(a){var b,c,d,e,f=String.fromCharCode;if(!k||!k.fillText)return!1;switch(k.clearRect(0,0,j.width,j.height),k.textBaseline="top",k.font="600 32px Arial",a){case"flag":return k.fillText(f(55356,56826,55356,56819),0,0),!(j.toDataURL().length<3e3)&&(k.clearRect(0,0,j.width,j.height),k.fillText(f(55356,57331,65039,8205,55356,57096),0,0),b=j.toDataURL(),k.clearRect(0,0,j.width,j.height),k.fillText(f(55356,57331,55356,57096),0,0),c=j.toDataURL(),b!==c);case"emoji4":return k.fillText(f(55357,56425,55356,57341,8205,55357,56507),0,0),d=j.toDataURL(),k.clearRect(0,0,j.width,j.height),k.fillText(f(55357,56425,55356,57341,55357,56507),0,0),e=j.toDataURL(),d!==e}return!1}function e(a){var c=b.createElement("script");c.src=a,c.defer=c.type="text/javascript",b.getElementsByTagName("head")[0].appendChild(c)}var f,g,h,i,j=b.createElement("canvas"),k=j.getContext&&j.getContext("2d");for(i=Array("flag","emoji4"),c.supports={everything:!0,everythingExceptFlag:!0},h=0;h<i.length;h++)c.supports[i[h]]=d(i[h]),c.supports.everything=c.supports.everything&&c.supports[i[h]],"flag"!==i[h]&&(c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&c.supports[i[h]]);c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&!c.supports.flag,c.DOMReady=!1,c.readyCallback=function(){c.DOMReady=!0},c.supports.everything||(g=function(){c.readyCallback()},b.addEventListener?(b.addEventListener("DOMContentLoaded",g,!1),a.addEventListener("load",g,!1)):(a.attachEvent("onload",g),b.attachEvent("onreadystatechange",function(){"complete"===b.readyState&&c.readyCallback()})),f=c.source||{},f.concatemoji?e(f.concatemoji):f.wpemoji&&f.twemoji&&(e(f.twemoji),e(f.wpemoji)))}(window,document,window._wpemojiSettings);
		</script>
+		/* Actually add it into here seems not a good idea, just a hacking */
+		<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+		<script type="text/javascript" src="/js/html2canvas.js"></script>
+		<script type='text/javascript'>//<![CDATA[
+		function genPostShot() { 
+		        var rightNow = new Date();
+		        var imageName = rightNow.toISOString().slice(0,16).replace(/(-)|(:)|(T)/g,"");
+		        imageName += '.jpg'
+		        html2canvas(document.getElementsByClassName('capturepost'), {
+		            background :'#FFFFFF',
+		            onrendered: function(canvas) {
+				// Click them for download
+		        	$('#test').attr('href', canvas.toDataURL("image/jpeg"));
+		        	$('#test').attr('download',imageName);
+		        	$('#test')[0].click();
+		            }
+		        });
+		}; 
+		//]]>
+		</script>
+		/* Hacking Ended */

其实这个hacking做得不好,整洁的代码应该是新建一个函数用于添加这些代码。

最后,在/var/www/html下创建一个文件夹,并下载html2canvas.js文件:

$ sudo mkdir -p /var/www/html/js
$ cd js
$ sudo wget https://cdn.bootcss.com/html2canvas/0.5.0-alpha1/html2canvas.js

或者直接修改上面定义中的/js/html2canvas.js 为:

//cdn.bootcss.com/html2canvas/0.5.0-alpha1/html2canvas.js

验证

现在重新刷新博客页面,即可享受一键另存为图片的快捷功能。

/images/2017_01_12_19_19_28_665x330.jpg

后台开发读书笔记

从图书馆借回来不少书,其中有一本腾讯工程师写的《后台开发核心技术与应用实践》,这本书的内容很浅显易懂, 基本上涵盖了Linux下C++开发在一般公司能用到的范畴。作者也说了,她写书的初衷在于用最短的篇幅讲解实际后台 用到的核心知识点以便读者能快速进入到实际开发中。扫了扫,前两张用来复习准备面试中有关C++的内容不错。

想提升的就算了,这本书的代码和调试手段都比较初级,实际工作中,需要更多的借助Google和开源社区来完成。

这里记录的主要是本人对该书里提到的一些概念的理解.

第一章

函数模板/函数重载

1.2函数章节里,关于函数重载和函数模板的理解可以用下面的代码来解释,左边是用函数重载的情形,可以看到一个 同名函数可以有多个参数版本,而右边的函数模板则引用了模板的概念,大大节约了代码行。

#include<iostream>      					#include<iostream>
using namespace std;    					using namespace std;
int min(int a, int b, int c){   			      |	template<typename T>
							      >	T min(T a,T b,T c){
    if(a>b)a=b; 						    if(a>b)a=b;
    if(a>c)a=c; 						    if(a>c)a=c;
    return a;   						    return a;
}       							}
long long min(long long a,long long b, long long c){          <
    if(a>b)a=b; 					      <
    if(a>c)a=c; 					      <
    return a;   					      <
}       						      <
double min(double a, double b){ //�������������ϵIJ�����ֻ��      <
    if(a-b>(1e-5))a=b;  				      <
    return a;   					      <
}       						      <
int main(){     						int main(){
    int a=1,b=2,c=3;    				      |	   int a=1,b=2,c=3;
    cout<<min(a,b,c)<<endl;     			      |	   cout<<min(a,b,c)<<endl;
    long long a1=100,b1=200,c1=300;     		      |	   long long a1=1000000000,b1=2000000000,c1=3000000000;
    cout<<min(a1,b1,c1)<<endl;  			      |	   cout<<min(a1,b1,c1)<<endl;
    double a2=1.1,b2=2.2;       			      |	   return 0;
    cout<<min(a2,b2)<<endl;     			      <
    return 0;   					      <
}       							}

字符数组

字符数组中,关于strlen()和sizeof()可以作为面试中的题目来问面试者。

函数与指针

函数指针的情形, 注意这里对函数的引用是直接将函数的地址赋给f.

#include<iostream>
using namespace std;
int Mmin(int x,int y){
     if(x<y)return x;
     return y;
}
int Mmax(int x,int y){
    if(x>y)return x;
    return y;
}
int main(){
    int (*f)(int x,int y);
    int a=10,b=20;
    f=Mmin;   //��Mmin���������ڵ�ַ����f
    cout << (*f)(a,b)<<endl;
    f=Mmax;  //��Mmax���������ڵ�ַ����f
    cout << (*f)(a,b)<<endl;
    return 0;
}

结构体/共用体/枚举

共用体的定义可以回忆一下:用关键字union来定义,是一种特殊的类。在一个共用体里可以定义多种 不同的数据类型,这些数据类型共享一段内存,在不同的时间里保存不同的数据类型和长度的变量。但是, 同一时间内只能存储一种类型的数据。其存在的目的是为了节省空间。

判断大小端的程序有点意思:

#include<iostream>
using namespace std;
union TEST{
    short a;
    char b[sizeof(short)];
};
int main(){
    TEST test;
    test.a=0x0102;// �������ù�����������ֻ�����ù����������еij�Ա��
    if(test.b[0]==0x01&&test.b[1]==0x02){
        cout<<"big endian."<<endl;
    }
    else if(test.b[0]==0x02&&test.b[1]==0x01){
        cout<<"small endian."<<endl;
    }
    else{
        cout<<"unknown"<<endl;
    }
    return 0;
}

枚举要注意类似于下面的题目:

enum fruits{apple=3,orange,banana=7,bear};
结果为: 3, 4, 7, 8

占用字节数的计算

鉴于这个题材经常在面试中被问到,单独拎出来写一段:

Union:

#include<iostream>
using namespace std; 
union A{
   int a[5];
   char b;
   double c;
};
int main(){
   cout<<sizeof(A)<<endl;
   return 0;
}

最长的double(8Byte)对齐,因而占用的大小应该是int(4Byte)*5=20, 而8字节对齐应该是3x8=24。因而运行结果为24

Struct:

#include<iostream>
using namespace std; 
struct B{
  char a;
  double b;
  int c;
}test_struct_b;
int main(){
   cout<<sizeof(test_struct_b)<<endl;
   return 0;
}

计算方法是:

char a , 1
补充7
double b, 8
int c, 4
补充4

所以结果为1+7+8+4+4=24.

混合结构体:

#include<iostream>
using namespace std;
typedef union{
    long i;
    int k[5];
    char c;
} UDATE; 
struct data{
    int cat;
    UDATE cow;
    double dog;
}too; 
UDATE temp; 
int main(){
    cout<<sizeof(struct data)+sizeof(temp)<<endl;
    return 0;
}

原书有错:

sizeof(temp)大小为24, 因为temp是UDATA类型结构。
sizeof(struct data)的计算则是结构体大小变化,每个变量需要占据各自独立的空间,依次为:

int cat: 4
4字节填充(参照double 8字节对齐)
UPDATE cow: 24
double dog: 8
一共为: 4+4+24+8=40

综上所述,结果为40+24=64.

do…while(0)的使用

这个问题可以在面试的时候问面试者。

例程:

# define Foo(x) do{\
    statement one;\
    statement two;\
}while(0)

在宏替换时这样的写法不会破坏以下的情形:

if(condition)
    statement one;
    statement two;
else
    //.....

如果去掉do…while(0)则会导致else语句孤立而出现编译错误,加了以后,则使得宏展开后,仍然保留了 原始的语义,从而保证程序的正确性。

extern “C”

加这个是为了让编译器将其当成C语言处理。

#ifdef __cpluscplus
    extern "C" {
#endif 

第二章

类与结构体

定义的异同:

class CStudent{         				      |	struct SStudent{
    int num;    					      |	public:
    char name[20];      				      <
    int age;           //��Щ�����ݳ�Ա��Ҳ��Ϊ��Ա����             <
    void display(){   //���dz�Ա����      			    void display(){   //���dz�Ա����
        cout<<"num:"<<num<<endl;				        cout<<"num:"<<num<<endl;
        cout<<"name:"<<name<<endl;      			        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;				        cout<<"age:"<<age<<endl;
    }   						      |	    }                  //����û�зֺ�
};      						      |	private:
CStudent cstu1,cstu2;//������2������    		      |	    int num;
							      >	    char name[20];
							      >	    int age;
							      >	};

类的封装性

源代码如下:

➜  0205 cat student.h
class CStudent{
public:
    void display();
private:
    int num;
    char name[20];
    int age;
};
➜  0205 cat main.cpp 
#include<iostream>
#include "student.h" //ע��������˫����
int main(){
    CStudent stu1;//����stu1����
    stu1.display();//ָ��stu1�����ij�Ա����
    return 0;
}
➜  0205 cat student.cpp 
#include<iostream>
#include "student.h" //������Ҫinclude����ͷ�ļ��������޷��ҵ�Student��
using namespace std; 
void CStudent::display(){  //����Ҫע����Student����
    cout<<"num:"<<num<<endl;
    cout<<"name:"<<name<<endl;
    cout<<"age:"<<age<<endl;
}

运行结果:

num:4196688
name:
age:0

这样的结果是因为没有对数据成员进行初始化而导致的。

构造函数

更改上面的代码:

➜  0205_1 cat student.h
class CStudent{
public:
    CStudent() {
      num = 0;
      age = 0;
    }
    void display();
private:
    int num;
    char name[20];
    int age;
};
➜  0205_1 ./test 
num:0
name:
age:0

静态数据成员

例子:

➜  chapter02 cat ./0212.cpp 
#include<iostream>
using namespace std;
class Base{
public:
    static int var;
};
int Base::var=10;
class Derived:public Base{
};
int main(){
    Base base1;
    base1.var++;//ͨ������������
    cout<<base1.var<<endl;//����11
    Base base2;
    base2.var++;
    cout<<base2.var<<endl;//����12
    Derived derived1;
    derived1.var++; 
    cout<<derived1.var<<endl;//����13
    Base::var++;//ͨ����������
    cout<<derived1.var<<endl;//����14
    return 0;
}
➜  chapter02 ./0212
11
12
13
14

对象存储空间

这个和第一章的存储空间可以结合起来看。

空类的存储空间为1:

➜  chapter02 cat 0214.cpp 
#include<iostream>
using namespace std;
class CBox{
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;//����1
    return 0;
}
➜  chapter02 ./0214
1

有成员变量的类的存储空间:

➜  chapter02 cat 0215.cpp 
#include<iostream>
using namespace std;
class CBox{
    int length,width,height;
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}
➜  chapter02 ./0215
12

有静态成员变量时,静态成员变量不占据对象的内存空间:

➜  chapter02 cat 0216.cpp 
#include<iostream>
using namespace std;
class CBox{
    int length,width,height;
    static int count;
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}
➜  chapter02 ./0216
12

成员函数不占据空间:

➜  chapter02 cat 0217.cpp 
#include<iostream>
using namespace std;
class CBox{
    int foo();
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}
➜  chapter02 ./0217
1

构造函数与析构函数也不占据空间:

class CBox{
public:
    CBox(){};
    ~CBox(){};
};
大小为1

虚析构函数,占用大小为8:

class CBox{
public:
    CBox(){};
    virtual ~CBox(){};
};
大小为8

类模板

操作整数的类与操作浮点数的类:

class Operation_int{    				      |	class Operation_double{
public: 							public:
    Operation_int(int a,int b):x(a),y(b){}      	      |	    Operation_double(double a, double b):x(a),y(b){}
    int add(){  					      |	    double add(){
        return x+y;     					        return x+y;
    }   							    }
    int subtract(){     				      |	    double subtract(){
        return x-y;     					        return x-y;
    }   							    }
private:							private:
    int x,y;    					      |	    double x,y;
};      							};

用类模板来抽象:

#include <iostream>
using namespace std;
template<class T>//����һ��ģ�壬����������ΪT
class Operation {
public:
    Operation (T a, T b):x(a),y(b){}
    T add(){
        return x+y;
    }
    T subtract(){
        return x-y;
    }
private:
    T x,y;
};
int main(){
    Operation <int> op_int(1,2);
    cout<<op_int.add()<<" "<<op_int.subtract()<<endl;//����3��-1
    Operation <double> op_double(1.2,2.3);
    cout<<op_double.add()<<" "<<op_double.subtract()<<endl;//����3.5��-1.1
    return 0;
}
➜  chapter02 ./0224
3 -1
3.5 -1.1

虚函数/纯虚函数

虚函数可以使得基类指针访问派生类中的同名函数:
而纯虚函数则是因为:用基类本身生成对象不合情理,例如用动物作为基类来抽象具体的动物,而动物这个基类 本身是不能被实例化的。

析构函数不是虚函数时,容易引发内存泄漏:

➜  chapter02 ./0231
Base::Base()
Derive::Derive()
Base::~Base()
➜  chapter02 cat 0231.cpp 
#include<iostream>
using namespace std;
class Base{
public:
    Base(){ std::cout<<"Base::Base()"<<std::endl; }
    ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Derive:public Base{
public:
    Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
    ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
int main(){
    Base* pBase = new Derive(); 
    /*����base classed������Ŀ����Ϊ������"ͨ��base class�ӿڴ���derived class����"*/
    delete pBase;
    return 0;
}

这是由C++的定义指出的:如果一个派生类对象经由一个基类指针被删除,而该基类带有一个非虚析构函数,则会导致 派生类中的成分没被销毁。

所以,需要把析构函数定义为虚函数。

➜  chapter02 ./0232
Base::Base()
Derive::Derive()
Derive::~Derive()
Base::~Base()
➜  chapter02 cat 0232.cpp 
#include<iostream>
using namespace std;
class Base{
public:
    Base(){ std::cout<<"Base::Base()"<<std::endl; }
    virtual ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};

class Derive:public Base{
public:
    Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
    ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};

int main(){
    Base* pBase = new Derive();
    delete pBase;
    return 0;
}

单例模式

理解:一台计算机上可以连好几台打印机,但是打印程序只能有一个,这里就可以通过单例模式来避免两个打印作业 被同时输出到打印机中。

➜  chapter02 ./0233
s1=s2
➜  chapter02 cat 0233.cpp 
#include<iostream>
using namespace std;
class CSingleton{
private:
	CSingleton(){   //构造函数是私有的
	}
	static CSingleton *m_pInstance;
public:
	static CSingleton * GetInstance(){
        if(m_pInstance == NULL)  //判断是否是第一次调用
	        m_pInstance = new CSingleton();
	    return m_pInstance;
	}
};
CSingleton * CSingleton::m_pInstance=NULL;//初始化静态成员变量
int main(){
    CSingleton *s1= CSingleton::GetInstance();
    CSingleton *s2= CSingleton::GetInstance();
    if(s1==s2){
        cout<<"s1=s2"<<endl; 
    }
    return 0;
}

单例类的特点:

1. 有指向唯一实例的静态指针m_pInstance,且为私有。   
2. 有一个公有函数用于获取唯一的实例。
3. 构造函数是私有的,不能在别处创建该类的实例.

Hugo And TravisCI Issue

Problem

In this morning when I get up and try to write something in my blog, I found the blog won’t upate. In travisCI website I got something very strange like following picture shows:

/images/2017_01_07_10_29_39_562x249.jpg

Error info:

Failed to normalize URL string. Returning in = "/"

Reason

As discussed in this post:

https://discuss.gohugo.io/t/started-getting-failed-to-normalize-url-string-returning-in/5034

This is because hugo now holds its own dependencies using govendor, you could view from its repository:

$ cd $GOPATH
$ cd src/github.com/spf13/hugo 
$ ls -l vendor 
total 16
-rw-r--r-- 1 dash root 14793 Jan  3 11:23 vendor.json

Solution

Using govendor for syncing the dependencies, the modified .travis.yml is listed as following:

install:
    - go get -u -v github.com/kardianos/govendor
    - go get -u -v github.com/spf13/hugo
    - cd $GOPATH/src/github.com/spf13/hugo && govendor sync && go install
  

script:
    - cd $HOME/gopath/src/github.com/purplepalmdash/purplepalmdash.github.io && hugo

Save changes and commit it into github, the travis building will start again, this time you won’t failed.

DockerCloudReadingDigests

第一章

Docker镜像准备:

$ sudo docker pull redis
$ sudo docker pull django
$ sudo docker pull haproxy
$ sudo docker pull ubuntu

应用栈节点架构:

启动redis-master容器节点, 两个redis-slave容器节点启动时连接到redis-master上面, 两个app容器节点启动时连接到redis-master上面, haproxy容器结点启动时连接到两个app结点上面。

容器的启动顺序为:redis-master -> redis-slave -> APP -> HAProxy.

Redis Master:

$ sudo docker run -it --name redis-master redis /bin/bash
root@4e4e597ffcb6:/data# 

Redis Slave1/Slave2:

$ sudo docker run -it --name redis-slave1 --link redis-master:master redis /bin/bash
$ sudo docker run -it --name redis-slave2 --link redis-master:master redis /bin/bash

App1, App2:

$ mkdir -p ~/Projects/Django/App1
$ mkdir -p ~/Projects/Django/App2
$ sudo docker run -it --name APP1 --link redis-master:db -v ~/Projects/Django/App1:/user/src/app django /bin/bash
$ sudo docker run -it --name APP2 --link redis-master:db -v ~/Projects/Django/App2:/user/src/app django /bin/bash

HAProxy:

$ mkdir -p ~/Projects/HAProxy
$ sudo docker run -it --name HAProxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v ~/Projects/HAProxy:/tmp haproxy /bin/bash

检查source目录挂载:

$ sudo docker ps | grep master
4e4e597ffcb6        redis                            "docker-entrypoint.sh"
43 minutes ago      Up 43 minutes       6379/tcp                 redis-master
$ sudo docker inspect 4e4e | grep Source
"Source":
"/var1/DockerRepo/docker/volumes/1dc31736abd411569cfbc51c6624125867883b44733f40113e2f918770843438/_data",

下载redis.conf文件:

$ wget http://download.redis.io/redis-stable/redis.conf
$ cp redis.conf /var1/DockerRepo/docker/volumes/1dc31736abd411569cfbc51c6624125867883b44733f40113e2f918770843438/_data

修改redis.conf内容:

bind 0.0.0.0

进入到容器后的操作:

# mkdir /var/lib/redis
# cd /data
# cp redis.conf /usr/local/bin
# cd /usr/local/bin
# redis-server redis.conf

Slave节点的配置:

# docker inspect f88a69ca2b1e | grep Source
                "Source": "/var1/DockerRepo/docker/volumes/6b211a6e469f4864a723dd7531f49674eeb9af9509ddcb75de8f18f4a677b85f/_data",
# cd /var1/DockerRepo/docker/volumes/6b211a6e469f4864a723dd7531f49674eeb9af9509ddcb75de8f18f4a677b85f/_data
# cp /home/dash/redis.conf  ./
# vim redis.conf 
slaveof master 6379
# docker inspect 14bbec8a900b | grep Source
                "Source": "/var1/DockerRepo/docker/volumes/a51e5a23a9b2e36350325aac6c533a48beb34d566b08773d48a2a3273c786d42/_data",
# cp redis.conf /var1/DockerRepo/docker/volumes/a51e5a23a9b2e36350325aac6c533a48beb34d566b08773d48a2a3273c786d42/_data

启动流程和Master一样。

Django配置, 注意要在我们映射的目录下:

root@7243f0d5a231:/user/src/app# mkdir dockerweb
root@7243f0d5a231:/user/src/app# cd dockerweb/
root@7243f0d5a231:/user/src/app/dockerweb# cd redisweb/
root@7243f0d5a231:/user/src/app/dockerweb/redisweb# python manage.py startapp helloworld

修改django文件:

$ pwd
/user/src/app
$ vim dockerweb/redisweb/helloworld/views.py 
from django.shortcuts import render
from django.http import HttpResponse

#Create your views here

import redis

def hello(request):
    str = redis.__file__
    str += "<br>"
    r = redis.Redis(host='db', port=6379, db=0)
    info = r.info()
    str += ("Set Hi <br>")
    r.set('Hi', 'HelloWorld-APP1')
    str += ("Get Hi: %s <br>" % r.get('Hi'))
    str += ("Redis Info: <br>")
    str += ("Key: Info Value")
    for key in info:
        str += ("%s: %s <br>" % (key, info[key]))
    return HttpResponse(str)

添加helloworld程序到INSTALLED_APPS选项下:

$ vim dockerweb/redisweb/redisweb/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'helloworld',
]

修改urls.py:

$ vim dockerweb/redisweb/redisweb/urls.py 
from django.conf.urls import url
from django.contrib import admin
from helloworld.views import hello

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^helloworld$', hello),
]

App1和App2的差别在于views.py里改掉这一行:

    - r.set('Hi', 'HelloWorld-APP1')
    + r.set('Hi', 'HelloWorld-APP2')

之后运行:

# python manage.py makemigrations
# python manage.py migrate
# python manage.py createsuperuser

分别运行:

#### APP1
# python manage.py runserver 0.0.0.0:8001
#### APP2
# python manage.py runserver 0.0.0.0:8002

HAProxy配置:

# cd ~/Projects/HAProxy
# vim haproxy.cfg
global 
  log 127.0.0.1 local0
  maxconn 4096
  chroot /usr/local/sbin
  daemon
  nbproc 4
  pidfile /usr/local/sbin/haproxy.pid

defaults
  log 127.0.0.1	local3
  mode http 
  option dontlognull 
  option redispatch
  retries 2
  maxconn 2000
  balance roundrobin
  timeout connect 5000ms
  timeout client 50000ms
  timeout server 50000ms

listen redis_proxy
  bind 0.0.0.0:6301
  stats enable
  stats uri /haproxy-stats
      server APP1 APP1:8001 check inter 2000 rise 2 fall 5 
      server APP2 APP2:8002 check inter 2000 rise 2 fall 5 

在haproxy容器里:

# cd /tmp
# cp haproxy.cfg /usr/local/sbin/
# cd /usr/local/sbin
# haproxy -f haproxy.cfg

现在验证:

http://127.0.0.1:6301/helloworld

/images/2017_01_06_18_45_40_627x330.jpg

http://127.0.0.1:6301/haproxy-stats:

/images/2017_01_06_18_46_45_634x397.jpg

运行K8S例程

GuestBook

注意修改imagePullPolicy为IfNotPresent, 创建服务的步骤分别为:

$ kubectl create -f redis-master-deployment.yaml
$ kubectl create -f redis-master-service.yaml
$ kubectl create -f frontend-deployment.yaml
$ kubectl create -f frontend-service.yaml

现在得到其运行状态:

$ kubectl get pod
NAME                            READY     STATUS    RESTARTS   AGE
frontend-88237173-02dvl         1/1       Running   0          2h
frontend-88237173-r7g3v         1/1       Running   0          2h
frontend-88237173-vjbv5         1/1       Running   0          2h
redis-master-4154998525-f186t   1/1       Running   0          2h
redis-slave-132015689-3qh7b     1/1       Running   0          2h
redis-slave-132015689-hpw88     1/1       Running   0          2h

可以用proxy-forward直接访问某个pod中暴露出来的frontend服务:

$ kubectl port-forward frontend-88237173-02dvl 9081:80

上述命令的意思是,将pod frontend-88237173-02dvl80端口的流量转发到 本地的9081端口,则可以通过访问http://127.0.0.1:9081来访问frontend.

或者,我们可以在service文件中指定服务类型为NodePort, 定义文件修改如下:

spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 31080

服务创建以后,访问http://CoreOS1IP:31080则可访问到guestbook前端页面,三个CoreOS 节点的31080端口均可提供前端页面访问。