MySQLレプリケーション+keepalived
linux, サーバー関連 2010年6月19日,
地味にみなさんがどういう設定をしてるかが気になるところですが
正直何の知識もない僕が仕事で
「DB2台でレプリケーションよろしく」といわれて試行錯誤で構築した軌跡です。
他の人たちがどのように構築しているかがすごく気になるところですねぇ・・・
目的
すでに構築されているWEBとDBの構成のもの。DBを1台増やすから「1台が落ちても2台目がホットスタンバイしてて即座に切り替わる構成」
としてほしいというのが依頼。
構成的にはこんな感じにDBが1台増える感じ。
こんな風に普段はWEBはDB1を参照するけど、DB1に障害が起きた場合は!
こんな形でDB2を参照するようにしちゃう。
こういった風に代替サーバーが役割を引き継ぐ事をフェイルオーバーって言うらしいよ!
これを可能にするにはDB1とDB2の間で以下の事がないと成立できない
例えばDB1に障害が起きた場合は
・DB1に起きた障害を検知できる事
・検知後、DB2に切り替わる事
・DB1とDB2のデータベースの内容が常に同じである事
の3つが成立しないといけないわけですね。
これを実現させるためにkeepalivedやMySQLレプリケーションといった技術を組み合わせて使います。
※今回の依頼は負荷分散に関してはまったく言及されていなかったのでこんな構成です。
負荷分散も視野にいれるとWEBとDBの間にロードバランサーとかをいれてスレーブ間だけを
フェイルオーバーしていくような構成になると思いますがやったことないのでほっときます。
keepaliveで障害感知&バーチャルIP
んでタイトルにもあるkeepalivedを使います。普段はロードバランサ側に入れてバックエンドのヘルスチェックとVRRPで自身の冗長化とかに使うみたい。
今回使える!と思ったのは「VRRPで自身の冗長化」という部分ですね。
VRRPとか言われても専門用語うぜー。意味不なんだけど
2台間でバーチャルIPを持つ事ができるみたいなんですよね。
つまりこんな感で使うようにしてみた
ここではDB1とDB2にkeepalivedをインストールします。
DB1とDB2はkeepalivedの機能でお互いを監視(冗長化)させます。
10.10.10.100がバーチャルIPで10.10.10.100は現在DB1が持っているIPです。
つまり10.10.10.100に接続がくるとDB1(10.10.10.101)へ向きます。
ここで上記のヘルスチェックの際、DB1に障害が発生した場合に、
10.10.10.100の向き先がDB2(10.10.10.102)へ向かうようになれば今回の案件に適います。
今回の件では設定のvirtual_serverのディレクティブはいらなかったかも・・・?
DB1にkeepalivedのインストールと設定
OSはDebian lennyです。まずはDB1側。
今回はこのDB1をマスターとして使用します。
DB1にログインしてインストールします。
インストール
apt-get install keepalived
設定
vi /etc/keepalived/keepalived.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 34 35 36 37 38 39 40 41 42 43 44 45 | ! Configuration File for keepalived global_defs { notification_email { sonar@sonarsrv.com } notification_email_from sonar@sonarsrv.com smtp_server localhost smtp_connect_timeout 30 } vrrp_instance vip_mysqld { state MASTER interface eth0 grap_master_delay 5 virtual_router_id 1 priority 150 nopreempt advert_int 1 authentication { auth_type PASS auth_pass secret } virtual_ipaddress { 10.10.10.100/24 dev eth0 } } virtual_server 10.10.10.100 3306 { delay_loop 3 lvs_method DR protocol TCP real_server 10.10.10.101 3306 { TCP_CHECK { connect_port 3306 connect_timeout 30 } } real_server 10.10.10.102 3306 { TCP_CHECK { connect_port 3306 connect_timeout 30 } } } |
起動
/etc/init.d/keepalived start
DB2にkeepalivedのインストールと設定
次にDB2側。DB2はスレーブとして使用します。
DB2にログインしてインストールします。
インストール
apt-get install keepalived
設定
vi /etc/keepalived/keepalived.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 34 35 36 37 38 39 40 41 42 43 44 45 | ! Configuration File for keepalived global_defs { notification_email { sonar@sonarsrv.com } notification_email_from sonar@sonarsrv.com smtp_server localhost smtp_connect_timeout 30 } vrrp_instance vip_mysqld { state BACKUP interface eth0 grap_master_delay 5 virtual_router_id 1 priority 100 nopreempt advert_int 1 authentication { auth_type PASS auth_pass secret } virtual_ipaddress { 10.10.20.100/24 dev eth0 } } virtual_server 10.10.10.100 3306 { delay_loop 3 lvs_method DR protocol TCP real_server 10.10.10.101 3306 { TCP_CHECK { connect_port 3306 connect_timeout 30 } } real_server 10.10.10.100 3306 { TCP_CHECK { connect_port 3306 connect_timeout 30 } } } |
起動
/etc/init.d/keepalived start
keepalivedの確認
これでもうkeepalivedはちゃんと動いているはずです。WEB側から10.10.10.100へpingを飛ばしてみると繋がるはずです。
ここでDB1(マスター)にて
ip addr show eth0
と入力すると、VIPが設定されているかが見れます
inet 10.10.10.101/24 brd 10.10.20.255 scope global eth0
inet 10.10.10.100/24 scope global secondary eth0
ここでマスター側をシャットダウンなりkeepalivedを止めるなりしてみると
10.10.10.100のIPがバックアップ側が持ちます。
WEB側から常にpingを飛ばしながら実験してみると確認できます。
マスターをrebootした時の流れは
①バックアップ側がマスターへ昇格。vipの10.10.10.100を持つ
②マスター側が復活するとマスターがマスターへ昇格。バックアップはバックアップへ戻り
vipの10,10.10.100はマスターへ戻る
という動きとなります。
このマスターとスレーブの動きはよく検証しておく必要があります。
MySQLのレプリケーションとからませる場合は、どちらかのDBがマスターとして常に最新でなくてはならず
vipがふらふら動いてどっちにも更新が行われた場合はお互いのDBが「最新」ではなくなり
管理者としては「カス!ゴミ!クズ!」のレッテルを貼られることとなります。
当然レプリケーションも停止しますし。
フェイルオーバーした後のvipの動きを理解してどのような方針で復旧していくのか。
管理ポリシーをしっかりと想定しておきましょー。
ちなみに僕はDB1マスターが落ちた後、復活した場合でもkeepalivedは自動起動させず
万一起動してしまってもマスターではなくスレーブとしています。
一度マスターに障害がおきたらMySQL、keepalivedと共にDB2がマスターとなるようにし、
自動的にvipが元に戻るような事はないようにしています。
当初、この方針でいくと報告した際に心の中では
「ちょっとした負荷でkeepalivedで”落ちた判定”になりvipが移動しまくったらどうしよ」
とか思ってましたが今のところ問題はないです。
MySQLレプリケーション
ここまでで、WEB側からは10.10.10.100としてDB接続すればOKな構成になっています。あとはDB1とDB2が常に同じ構成になればOKです。
ここでDB1とDB2を同期する方法はいろいろ考えました。
lrsyncとかネットワークごしのRAID1みたいな技術とか(名前忘れた)
でも試した結果、負荷が圧倒的に少ないのがレプリケーションでした。
概要としてはDB1をマスター、DB2をスレーブとしてDB1にきたクエリとかをバイナリログとして
そのままDB2に流し込み~ みたいな感じだと思うのですが
なぜか圧倒的に早い。そして圧倒的にDB2の負荷が軽い。
なんだろね~すごいね~
というわけでMySQLレプリケーションの構築。
マスターの設定
マスター側から。
僕の環境ではmy.cnfがない状態だったんで
適当なサンプルもんをつこうた。
/usr/local/mysqlにインストールしてたので
cp /usr/local/mysql/support-files/my-medium.cnf /etc/mysql/my.cnf
vi /etc/mysql/my.cnf
レプリケーションの要となるのは
server-id と log-binの項目になってきます。
server-idはレプリケーションをおこなるサーバー群で一意なものとなる必要があります。
重複してはいけない。
server-id = 1
log-bin = mysql-bin
としときました。
次にアカウントの作成
mysqlにログインして
GRANT REPLICATION SLAVE ON *.* TO repl@10.10.10.102 IDENTIFIED BY ‘password’;
とし、replというレプリケーション用のユーザーを作成しました。
10.10.10.102(スレーブ)からの接続のみを許可するようなユーザーとなります。
passwordの部分はちゃんと適当なパスワード作ってね。
この設定を反映させるためにmysqlを再起動します。
次にDB1とDB2を同期させる設定です。
まだお互いが空の状態ならいいんですがすでに動いてる環境からレプリケーションをする場合、
まずはDB1とDB2を同じ状態とする必要があります。
その為にDBの、バイナリログの値とオフセット値を控えて完全バックアップを取ります。
スレーブ側にはこの完全バックアップデータとバイナリログの値とオフセット値を与える事で
自動的にそれ以降のバイナリログを読み取って同期してくれる感じです。
DB1の完全バックアップを行うにはDBの更新処理を一時的にできなくする必要があります(ロック)
なぜならバックアップ中に更新処理が行われても
バイナリログの値とオフセット値は刻々と変化し続けるからです。
ここはどうしても停止処理が必要なので先方さんと話をあわせます。(まぁ再起動の必要もありますしね)
事前にバックアップにどれくらいの時間がかかるかを測定しておくのがいいです。
さて実際にDBをロックしてバックアップを取るには。
MySQLにログインした後
FLUSH TABLES WITH READ LOCK;
これで更新処理は完全に停止します。
この間に
SHOW MASTER STATUS;
と入力することでバイナリログ名とオフセット値がでてきます。
これは確実に!メモしてください。ロックまでしている意味がなくなりますのでw
1 2 3 4 5 6 | +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000004 | 189 | | | +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec) |
今回のような例なら mysql-bin.000004と189 をメモしておく。
次に完全バックアップ。
MySQLをログアウトして
/usr/local/mysql/bin/mysqldump -pパスワード-u root -x -A > mysql_dumpall.db
って感じでmysqlのフルバックアップを取得。
mysql_dumpall.dbにバックアップデータが出来たことを確認したら
再びmysqlにログインし、ロックを解除する。
UNLOCK TABLES;
とりあえず停止時間は終わり。
この後スレーブ側の設定へと移るわけですが
あんまりのんびりしてると完全バックアップ以降の更新がたまっていって同期に時間かかるので注意w
ちなみに、SHOW MASTER STATUS;をした時に
Empty set (0.00 sec)
と出た場合は、MySQLが空っぽで生まれたての姿であるという事です。
この場合はFileは””(空文字)、Positionは4となります。
スレーブの設定
マスター側と同じような設定をしていきます。
cp /usr/local/mysql/support-files/my-medium.cnf /etc/mysql/my.cnf
vi /etc/mysql/my.cnf
レプリケーションの要となるserver-id と log-binの項目を編集します。
server-idはレプリケーションをおこなるサーバー群で一意なものとなる必要があります。
重複してはいけない。
さっきは1を設定したのでスレーブは適当に2をw
server-id = 2
log-bin = mysql-bin
次にアカウントの作成
mysqlにログインして
GRANT REPLICATION SLAVE ON *.* TO repl@10.10.10.101 IDENTIFIED BY ‘password’;
とし、replというレプリケーション用のユーザーを作成しました。
10.10.10.101(マスター)からの接続のみを許可するようなユーザーとなります。
これは必ず必要ってわけじゃーないんですがkeepalivedでこちらがマスターとなった場合に
障害復旧する場合に必要になってくるので今のうちにつくっとこーって感じです。
passwordの部分はマスターのreplユーザーと同じでいいと思います。
ここまででmysqlを再起動。
で、ついにレプリケーションの開始です。
まずはマスター側からとってきた完全バックアップデータを
mysqlに流し込みます
mysql -u root -pパスワード < mysql_dumpall.db
mysqlにログインしデータベースの復元を確認します。
データベースの確認ができたらスレーブを開始!
1 2 3 4 5 6 | CHANGE MASTER TO MASTER_HOST='10.10.20.101', MASTER_USER='repl', MASTER_PASSWORD='パスワード', MASTER_LOG_FILE='mysql-bin.****', MASTER_LOG_POS=****; |
****の部分にはメモしたバイナリファイル名とオフセット値を入力します。
スレーブを開始します。
START SLAVE;
以上でレプリケーションは完了です。
スレーブが開始されているかを確認
SHOW SLAVE STATUS\G
レプリケーションが開始された事を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 | *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 10.10.10.101 Master_User: test Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000004 Read_Master_Log_Pos: 189 Relay_Log_File: testcam-relay-bin.000022 Relay_Log_Pos: 251 Relay_Master_Log_File: mysql-bin.000004 Slave_IO_Running: Yes Slave_SQL_Running: Yes |
ここで新たに/usr/local/mysql/data内にmaster.info、relay-log.infoが作成される
(余談。master.infoはスレーブとして同期するマスターの情報がかかれていてmy.cnfよりも優先に読まれます。
ですのでmysqlを再起動しても何してもスレーブとして自動で動いてくれます。
障害時にマスターとスレーブを入れ替える必要がある時などは必ず削除して下さいな。
これで大変な事になったんだよねww旧マスターのDB全削除したらレプリケーション生きてて全DB消えたりとかw)
状態が両方yesになるまでしばらく待つ必要あり
Slave_IO_StateがWaiting for master to send event
Slave_IO_RunningがYes
Slave_SQL_RunningがYes
こうなっているのが確認できれば正常に動いています。
マスター側でDBを作ってみたりして即座にスレーブ側も反映されてればOK
レプリケーションがうまくいかない場合!
さて、僕が構築する上でよく陥ってたエラーです。①ポートが開いてない
mysqlは3306ポートを使うのですがマスターとかスレーブ側のmysqlポート、
3306をちゃんとiptablesであけてますか?
②設定変更後mysqlを再起動していない
以外とやっちゃいます。my.cnfを作ったりちょっとでも変更したなら
反映させるにはmysqlの再起動が必要ですよ
mysqlレプリケーションはエラーでときどき止まる
長いことやってるとどうしてもエラーで止まる事がしばしば・・・まず、エラーが起きるというタイミングは
スレーブ側にしかないレコードやらDBやらがあった場合。
例えばマスター側でtest というDBをcreateしたとしよう。
すると当然スレーブ側でもtestというDBを作られる事になるわけだけど
ここでスレーブ側にtestというDBがすでに存在すればこれだけでレプリケーションがストップします。
また、そのほかにもランダム系のクエリはエラーの発生が起こりやすくなります。
例えば1~4をランダムで取得してDBに入れるようなSQL文を実行すると
マスター側では1が格納されたのにスレーブ側では2が格納されてた、とか。
(ここらへんは結構改善されていってるっぽいですが)
まぁ多くは上2個が原因だったんですがとにかく結構とまります。
大手の構築でも、「実はレプリケーションは行われていなかった」なんて事よくあるみたいですしね。
と、いうわけで、レプリケーションが正常に行われているかの監視は必須です。
有名な監視ソフトでも死活監視はできてもレプリケーションの監視はできないので自前で作ります。
要はSHOW SLAVE STATUS\Gで
Slave_IO_RunningがYes
Slave_SQL_RunningがYes
になっていればよいので
1 2 3 4 5 6 7 8 9 | #!/bin/sh PASSWORD="パスワード" eval "`/usr/local/mysql/bin/mysql -u root -p${PASSWORD} -e 'show slave status \G' | sed -ne 's/: \(.*\)/="\1"/p' `" if [ "$?${Slave_IO_Running}${Slave_SQL_Running}" != "0YesYes" ]; then /usr/local/mysql/bin/mysql -u root -p"${PASSWORD}" -e 'show slave status \G' |/usr/bin/mail sonar@sonarsrv.com -s "REPLICATION DOWN "`hostname` exit 1 fi |
て感じで
Slave_IO_RunningとSlave_SQL_Runningの値がyesでなければ
show slave statusで取得できるステータス一覧をメールで送信するスクリプトを作成。
適当にreplchk.shとでもつけて実行権限を与えてcronで5分ごととかにまわしておけば
レプリケーションのチェックができます。(ついでにmysqlの死活監視もw)
基本的にはレプリケーションは止まってもサービスの停止にはならないので気長に復旧すればいいです。
多くは
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
というコマンド一発で解決します。
(これは、エラーの出たクエリを1つ飛ばす(無視する)という意味だと思われ)
仕上げ
ここまでで、keepalivedとmysqlレプリケーションは完成したわけですが肝心の
マスター側のMySQLの監視ができていません。
例えばWEB側から変なSQL文きてループに陥ったりしてMySQLに接続できなくなっても
それは立派な障害です。(ポートはあいてるから監視ではOKとかでちゃうんだよね)
ってわけで適当にmysqlにつなぎにいって
もしmysqlに繋がらなければ自信のkeepalivedを落とす
というスクリプトを作成してcronでまわしときました。
今回はphpでww
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 | <?php /*********************************** * MYSQLの死活チェック * 接続できるかどうかをチェックする ************************************/ // 接続先ホスト(必須) $HOST = '127.0.0.1'; // 接続ユーザ(必須) $USER = 'mysql'; // パスワード(必須) $PASS = 'パスワード'; // 直接DBまでつなげたかったら書く $DB_NAME = ''; // 失敗したときに実行したいコマンド $FAIL_BIN = '/etc/init.d/keepalived stop |/usr/bin/mail sonar@sonarsrv.com -s "mysql down! "`hostname`'; // 成功したときに実行したいコマンド $SUCCESS_BIN = ''; $result = db_connect_check($HOST, $USER, $PASS, $DB_NAME); if (!$result) { // 失敗 print "error\n"; if ($FAIL_BIN) { system($FAIL_BIN); } } else { // OK if ($SUCCESS_BIN) { system($SUCCESS_BIN); } } exit; function db_connect_check($host, $user, $pass, $db_name) { $con = mysql_connect($host, $user, $pass, true); if (!$con) { // エラー表示 print mysql_error(); return false; } if ($db_name) { if (!mysql_select_db($db_name, $con)) { // エラー表示 print mysql_error(); return false; } } return true; } ?> |
終わり
結構複雑になっちゃったんだよね・・・他の人がどうしてるかかなり気になるのでメールなりなんなりで教えてくれると嬉しいです。
ちなみに最近ではAmazon EC2で14台構成の構築を行う案件があるんですが
DBのレプリケーションで悩んでいます。
今回のようにkeepalivedでバーチャルIP持てないし
そもそもローカルIPなんて概念があるのか・・・とか。
インスタンス全部がグローバルだしグローバル越のレプリケーションってできるの??違和感!!
Amazon EC2でMySQLレプリケーション行ってる事例があったら教えて欲しい!詳細を!
6月 6th, 2016 at 12:04 PM
[…] mysqlが落ちて、かつマシン(vrrp)が動いている場合、フェイルオーバが効かないので、mysqlが落ちた時に自身のkeepalivedを落とすスクリプト(PHP)を作成。 こちらからのまるパクリです。 […]
10月 21st, 2018 at 5:03 PM
The following time I learn a weblog, I hope that it doesnt disappoint me as a lot as this one. I imply, I know it was my choice to learn, but I really thought youd have something fascinating to say. All I hear is a bunch of whining about one thing that you can repair in case you werent too busy searching for attention.
11月 13th, 2020 at 2:51 AM
You recognize a whole lot its virtually tough to argue along with you (not that I personally would want…HaHa).
1月 10th, 2023 at 10:58 PM
, .
, .
5月 15th, 2023 at 5:40 AM
Hi, its good piece of writing on the topic of media print, we all understand media is a impressive source
of facts.
5月 19th, 2023 at 2:13 PM
Just wanna comment that you have a very nice site, I
like the pattern it really stands out.
5月 27th, 2023 at 1:42 AM
I am regular reader, how are you everybody? This article posted at this
web page is actually good.
6月 4th, 2023 at 1:48 AM
Thank you for sharing excellent informations. Your website is so cool.
I am impressed by the details that you’ve on this site. It reveals how nicely you understand this
subject. Bookmarked this web page, will come back for extra
articles. You, my pal, ROCK! I found simply the info I already
searched all over the place and just could not come across.
What an ideal web site.
6月 4th, 2023 at 10:35 AM
This is a great tip especially to those new to the blogosphere.
Short but very accurate info… Many thanks for sharing this one.
A must read article!
6月 6th, 2023 at 9:26 PM
You made some clear points there. I looked on the internet for the issue and found most individuals will approve with your blog.
6月 7th, 2023 at 11:20 AM
It’s nearly impossible to find educated people in this particular topic, but you sound like you know what
you’re talking about! Thanks
11月 21st, 2023 at 3:23 PM
what is the genesis block https://tronlink-app.org/
1月 1st, 2024 at 5:27 AM
It’s exhausting to say GOOD BYE to NINJA Mike and Jeff Elliott!
It’s noteworthy that TikTok’s user-generated content material has a powerful emphasis on leisure, dance, and humor, reflecting
the platform’s enchantment to younger generations who get pleasure from
light-hearted and fun content material. You most likely see them all the time and also you in all probability
think it’s your computer giving you these notices. I passed the CPA Exam when I used
to be 30 and had “only” three kids at residence at the
time and instantly went to the corporate world… What is CPA Marketing?
However, the preferred characteristic is Facebook Ads to get
paid site visitors to develop your CPA advertising business.
CPA offers have gotten more and more well-liked with advertisers,
so affiliates get to pick their favorites. I’m a married licensed CPA with 10 Pre-TCJA
Dependency Exemptions (tax nerds will get that). In the event you have been offer insert 2.2X here I believe
we’d have a deal.” No buyer will pitch 2X if they know what X is, though. I feel the more you perceive the different challenges and really agree that we’re all dealing with the same problem within the sense that even the biggest corporations on the earth recognise the necessity for them to contribute.
1月 1st, 2024 at 5:57 AM
No matter specialty, accountants and auditors who’ve earned professional recognition by means of certification or licensure ought to have the perfect job prospects.
In the wake of the Enron and Tyco accounting scandals, the general public views skilled certification as much
more needed as a result of it ensures that the accountant’s ethics
are sound and his/her knowledge and credentials are up-to-date.
The need for accounting clerks will increase as consumers opt for storefront tax preparers resembling Liberty
and H & R Block, quite than hiring a CPA firm.
How do you change into a CPA? Each of the 54 jurisdictions which affords the CPA Exam has its own qualifications for sitting for the examination. A number of the extra common forms
of CPA marketing opportunities available are: Free-trial affords,
e-mail affords and zip code provides, all of which probably pay extremely
effectively once you get going with it. Even when you’re acquainted with
the concept of CPA commissions typically, that doesn’t mean you understand the nuts and bolts of CPA on ClickBank.
1月 28th, 2024 at 3:28 PM
id=”firstHeading” class=”firstHeading mw-first-heading”>Search гesults
Hеlp
English
Tools
Tools
mоve to sidebar hide
Actions
Ԍeneral
2月 4th, 2024 at 11:12 PM
The core of your writing whilst sounding agreeable originally, did not really settle very well with me after some time.
Somewhere throughout the paragraphs you managed to make me a believer but just for a while.
I nevertheless have got a problem with your jumps in logic and you would do nicely to help fill
in those gaps. When you can accomplish that, I will undoubtedly be fascinated.
3月 26th, 2024 at 11:14 AM
In line with my observation, after a property foreclosure home is bought at an auction, it is common for
that borrower in order to still have some sort ofthat remaining unpaid debt on the
personal loan. There are many financial institutions who seek to have all costs and liens paid by the next buyer.
Even so, depending on certain programs, regulations,
and state guidelines there may be several loans that are not easily solved through the exchange of loans.
Therefore, the obligation still rests on the debtor that has got his or her property in foreclosure.
Thanks for sharing your ideas on this site.
4月 1st, 2024 at 10:48 AM
ดอกไม้งานศพ ⲟr funeral flowers аrе ɑn essential рart
օf Thai culture аnd tradition.
4月 27th, 2024 at 4:00 PM
Ewa precisely what you can call me though I am really like
being called like the fact that. Hiring is where his primary income comes from.
What me and my family love is kayaking having said that i can’t succeed my profession really.
District of Columbia has for ages been his home and
his family loves it.
4月 27th, 2024 at 5:16 PM
hr staff n stuff https://www.hrstaffnstuff.fr
7月 13th, 2024 at 4:08 AM
F*ckin’ awesome issues here. I am very satisfied to peer your article.
Thank you so much and i am having a look ahead
to contact you. Will you kindly drop me a e-mail?
8月 4th, 2024 at 9:48 AM
Thank you for every other informative website.
The place else may I am getting that kind of information written in such an ideal manner?
I have a project that I’m simply now running on, and I’ve been at the glance out for
such info.
8月 4th, 2024 at 10:42 AM
If you are going for finest contents like myself,
simply visit this web page every day as it offers quality contents,
thanks