Gambar buruh pelabuhan 5,94 meter dengan Telegram MTProxy

Seperti yang sudah didengar semua orang, pada akhir Mei Telegram meluncurkan server MTProto Proxy (alias MTProxy) resmi, yang ditulis dalam bahasa berikut . Pada tahun 2018, tidak ada banyak tempat tanpa Docker, karena disertai dengan cara "resmi" yang sama dalam format zero-config. Semuanya akan baik-baik saja, tetapi tiga "tapi" merusak kesan rilis: gambar berbobot> 130 Mb (ada Debian yang gemuk, bukan Alpine yang biasa), karena "zero-config" tidak selalu mudah dikonfigurasikan (hanya oleh pengaturan lingkungan) dan orang-orang lupa kampanye, lay out Dockerfile.



TL; DR Kami akan membuat gambar docker resmi berbasis alpine 1-in-1 praktis berukuran 5.94MB dan meletakkannya di sini (dan Dockerfile di sini ); sepanjang jalan, kami akan mencari tahu bagaimana kadang-kadang Anda dapat berteman dengan perangkat lunak Alpine menggunakan jepit dan file, dan kami akan bermain sedikit dalam ukuran, khusus untuk bersenang-senang.

Konten Gambar


Sekali lagi, karena apa yang diributkan? Mari kita lihat apa yang diwakili gambar resmi dengan perintah sejarah :

$ docker history --no-trunc --format "{{.Size}}\t{{.CreatedBy}}" telegrammessenger/proxy 

Lapisan dibaca dari bawah ke atas, masing-masing:



Yang paling tebal adalah Debian Jessie, dari mana gambar asli diwarisi, kita harus menyingkirkannya terlebih dahulu (alpine: 3.6, sebagai perbandingan, beratnya 3,97MB); diikuti oleh dimensi adalah ikal dan sertifikat segar. Untuk memahami apa arti dua file dan direktori lainnya, kita akan melihat ke dalam menggunakan perintah run , mengganti CMD dengan bash (ini akan memungkinkan Anda berjalan di sekitar gambar yang diluncurkan, mengenal satu sama lain lebih dekat, menjalankan fragmen tertentu, menyalin sesuatu yang bermanfaat):

 $ docker run -it --rm telegrammessenger/proxy /bin/bash 

Sekarang kita dapat dengan mudah mengembalikan gambar kejadian - sesuatu seperti Dockerfile resmi yang hilang terlihat seperti:

 FROM debian:jessie-20180312 RUN set -eux \ && apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && rm -rf /var/lib/apt/lists/* COPY ./mtproto-proxy /usr/local/bin RUN mkdir /data COPY ./secret/ /etc/telegram/ COPY ./run.sh /run.sh CMD ["/bin/sh", "-c", "/bin/bash /run.sh"] 

Di mana mtproto-proxy adalah server yang dikompilasi, folder rahasia hanya berisi file hello-explorers-how-are-you-do dengan kunci enkripsi AES (lihat perintah server, di sana, merupakan rekomendasi resmi untuk mendapatkan kunci melalui API, tetapi dengan kata lain mungkin untuk menghindari situasi ketika API juga diblokir), dan run.sh melakukan semua persiapan untuk memulai proxy.

Isi run.sh asli
 #!/bin/bash if [ ! -z "$DEBUG" ]; then set -x; fi mkdir /data 2>/dev/null >/dev/null RANDOM=$(printf "%d" "0x$(head -c4 /dev/urandom | od -t x1 -An | tr -d ' ')") if [ -z "$WORKERS" ]; then WORKERS=2 fi echo "####" echo "#### Telegram Proxy" echo "####" echo SECRET_CMD="" if [ ! -z "$SECRET" ]; then echo "[+] Using the explicitly passed secret: '$SECRET'." elif [ -f /data/secret ]; then SECRET="$(cat /data/secret)" echo "[+] Using the secret in /data/secret: '$SECRET'." else if [[ ! -z "$SECRET_COUNT" ]]; then if [[ ! ( "$SECRET_COUNT" -ge 1 && "$SECRET_COUNT" -le 16 ) ]]; then echo "[F] Can generate between 1 and 16 secrets." exit 5 fi else SECRET_COUNT="1" fi echo "[+] No secret passed. Will generate $SECRET_COUNT random ones." SECRET="$(dd if=/dev/urandom bs=16 count=1 2>&1 | od -tx1 | head -n1 | tail -c +9 | tr -d ' ')" for pass in $(seq 2 $SECRET_COUNT); do SECRET="$SECRET,$(dd if=/dev/urandom bs=16 count=1 2>&1 | od -tx1 | head -n1 | tail -c +9 | tr -d ' ')" done fi if echo "$SECRET" | grep -qE '^[0-9a-fA-F]{32}(,[0-9a-fA-F]{32}){,15}$'; then SECRET="$(echo "$SECRET" | tr '[:upper:]' '[:lower:]')" SECRET_CMD="-S $(echo "$SECRET" | sed 's/,/ -S /g')" echo -- "$SECRET_CMD" > /data/secret_cmd echo "$SECRET" > /data/secret else echo '[F] Bad secret format: should be 32 hex chars (for 16 bytes) for every secret; secrets should be comma-separated.' exit 1 fi if [ ! -z "$TAG" ]; then echo "[+] Using the explicitly passed tag: '$TAG'." fi TAG_CMD="" if [[ ! -z "$TAG" ]]; then if echo "$TAG" | grep -qE '^[0-9a-fA-F]{32}$'; then TAG="$(echo "$TAG" | tr '[:upper:]' '[:lower:]')" TAG_CMD="-P $TAG" else echo '[!] Bad tag format: should be 32 hex chars (for 16 bytes).' echo '[!] Continuing.' fi fi curl -s https://core.telegram.org/getProxyConfig -o /etc/telegram/backend.conf || { echo '[F] Cannot download proxy configuration from Telegram servers.' exit 2 } CONFIG=/etc/telegram/backend.conf IP="$(curl -s -4 "https://digitalresistance.dog/myIp")" INTERNAL_IP="$(ip -4 route get 8.8.8.8 | grep '^8\.8\.8\.8\s' | grep -Po 'src\s+\d+\.\d+\.\d+\.\d+' | awk '{print $2}')" if [[ -z "$IP" ]]; then echo "[F] Cannot determine external IP address." exit 3 fi if [[ -z "$INTERNAL_IP" ]]; then echo "[F] Cannot determine internal IP address." exit 4 fi echo "[*] Final configuration:" I=1 echo "$SECRET" | tr ',' '\n' | while read S; do echo "[*] Secret $I: $S" echo "[*] tg:// link for secret $I auto configuration: tg://proxy?server=${IP}&port=443&secret=${S}" echo "[*] t.me link for secret $I: https://t.me/proxy?server=${IP}&port=443&secret=${S}" I=$(($I+1)) done [ ! -z "$TAG" ] && echo "[*] Tag: $TAG" || echo "[*] Tag: no tag" echo "[*] External IP: $IP" echo "[*] Make sure to fix the links in case you run the proxy on a different port." echo echo '[+] Starting proxy...' sleep 1 exec /usr/local/bin/mtproto-proxy -p 2398 -H 443 -M "$WORKERS" -C 60000 --aes-pwd /etc/telegram/hello-explorers-how-are-you-doing -u root $CONFIG --allow-skip-dh --nat-info "$INTERNAL_IP:$IP" $SECRET_CMD $TAG_CMD 

Majelis


Di bawah CentOS 7 MTProxy pada Habré yang sudah dikumpulkan , kami akan mencoba untuk mengumpulkan gambar di bawah Alpine dan untuk menyimpan megabyte, iklan, 130 di gambar buruh pelabuhan yang dihasilkan.

Fitur khas Alpine Linux adalah penggunaan musl, bukan glibc. Keduanya adalah pustaka C standar. Musl kecil (tidak memiliki seperlima dari "standar" di dalamnya), tetapi volume dan kinerja (setidaknya dijanjikan) memutuskan ketika datang ke Docker. Dan menempatkan glibc di Alpine tidak benar secara rasial, paman Jakub Jirutka tidak akan mengerti , misalnya.

Kami juga akan membangun buruh pelabuhan untuk mengisolasi dependensi dan mendapatkan kebebasan untuk eksperimen, jadi buatlah Dockerfile baru:

 FROM alpine:3.6 RUN apk add --no-cache git make gcc musl-dev linux-headers openssl-dev RUN git clone --single-branch --depth 1 https://github.com/TelegramMessenger/MTProxy.git /mtproxy/sources RUN cd /mtproxy/sources \ && make -j$(getconf _NPROCESSORS_ONLN) 

Dari dependensi, git akan berguna (dan tidak hanya untuk kloning repositori resmi, file make akan melampirkan sha commit ke versi), make, gcc dan file header (set minimum diperoleh secara empiris). Kami hanya mengkloning cabang master dengan kedalaman 1 komit (kami jelas tidak membutuhkan sejarah). Baiklah, mari kita coba memanfaatkan semua sumber daya host saat kompilasi dengan -j switch. Saya sengaja memecahnya menjadi lebih banyak lapisan untuk mendapatkan caching yang nyaman selama pembangunan kembali (biasanya ada banyak dari mereka).

Kami akan menjalankan perintah build (berada di direktori dengan Dockerfile):

 $ docker build -t mtproxy:test . 

Dan inilah masalah pertama:

 In file included from ./net/net-connections.h:34:0, from mtproto/mtproto-config.c:44: ./jobs/jobs.h:234:23: error: field 'rand_data' has incomplete type struct drand48_data rand_data; ^~~~~~~~~ 

Sebenarnya, semua yang berikutnya akan terhubung dengannya. Pertama, bagi mereka yang tidak terbiasa dengan diri mereka sendiri, kompiler sebenarnya bersumpah pada kurangnya deklarasi struktur drand48_data. Kedua, pengembang musl mencetak skor pada fungsi acak thread-safe (dengan postfix _r) dan pada semua yang terhubung dengannya (termasuk struktur). Dan pengembang Telegram, pada gilirannya, tidak repot-repot dengan kompilasi untuk sistem di mana random_r dan rekan-rekannya tidak diimplementasikan (di banyak pustaka OS Anda dapat melihat bendera HAVE_RANDOM_R atau kehadiran + tidak adanya yang sewenang-wenang atau tidak adanya kelompok fungsi ini biasanya diperhitungkan dalam konfigurasi otomatis).

Nah, sekarang kita pasti akan menginstal glibc? Tidak! Kami akan menyalin apa yang kami butuhkan dari glibc ke minimum dan membuat tambalan untuk sumber MTProxy.

Selain masalah dengan random_r, kami mengambil masalah dengan fungsi backtrace (execinfo.h), yang digunakan untuk menampilkan stack backtrace dalam kasus pengecualian: Anda dapat mencoba menggantinya dengan implementasi dari libunwind, tetapi tidak sepadan, karena panggilan dibingkai dengan memeriksa untuk __GLIBC__.

Konten tambalan Random_compat.patch
 Index: jobs/jobs.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- jobs/jobs.h (revision cdd348294d86e74442bb29bd6767e48321259bec) +++ jobs/jobs.h (date 1527996954000) @@ -28,6 +28,8 @@ #include "net/net-msg.h" #include "net/net-timers.h" +#include "common/randr_compat.h" + #define __joblocked #define __jobref Index: common/server-functions.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- common/server-functions.c (revision cdd348294d86e74442bb29bd6767e48321259bec) +++ common/server-functions.c (date 1527998325000) @@ -35,7 +35,9 @@ #include <arpa/inet.h> #include <assert.h> #include <errno.h> +#ifdef __GLIBC__ #include <execinfo.h> +#endif #include <fcntl.h> #include <getopt.h> #include <grp.h> @@ -168,6 +170,7 @@ } void print_backtrace (void) { +#ifdef __GLIBC__ void *buffer[64]; int nptrs = backtrace (buffer, 64); kwrite (2, "\n------- Stack Backtrace -------\n", 33); @@ -178,6 +181,7 @@ kwrite (2, s, strlen (s)); kwrite (2, "\n", 1); } +#endif } pthread_t debug_main_pthread_id; Index: common/randr_compat.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- common/randr_compat.h (date 1527998264000) +++ common/randr_compat.h (date 1527998264000) @@ -0,0 +1,72 @@ +/* + The GNU C Library is free software. See the file COPYING.LIB for copying + conditions, and LICENSES for notices about a few contributions that require + these additional notices to be distributed. License copyright years may be + listed using range notation, eg, 2000-2011, indicating that every year in + the range, inclusive, is a copyrightable year that would otherwise be listed + individually. +*/ + +#pragma once + +#include <endian.h> +#include <pthread.h> + +struct drand48_data { + unsigned short int __x[3]; /* Current state. */ + unsigned short int __old_x[3]; /* Old state. */ + unsigned short int __c; /* Additive const. in congruential formula. */ + unsigned short int __init; /* Flag for initializing. */ + unsigned long long int __a; /* Factor in congruential formula. */ +}; + +union ieee754_double +{ + double d; + + /* This is the IEEE 754 double-precision format. */ + struct + { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned int negative:1; + unsigned int exponent:11; + /* Together these comprise the mantissa. */ + unsigned int mantissa0:20; + unsigned int mantissa1:32; +#endif /* Big endian. */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + /* Together these comprise the mantissa. */ + unsigned int mantissa1:32; + unsigned int mantissa0:20; + unsigned int exponent:11; + unsigned int negative:1; +#endif /* Little endian. */ + } ieee; + + /* This format makes it easier to see if a NaN is a signalling NaN. */ + struct + { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned int negative:1; + unsigned int exponent:11; + unsigned int quiet_nan:1; + /* Together these comprise the mantissa. */ + unsigned int mantissa0:19; + unsigned int mantissa1:32; +#else + /* Together these comprise the mantissa. */ + unsigned int mantissa1:32; + unsigned int mantissa0:19; + unsigned int quiet_nan:1; + unsigned int exponent:11; + unsigned int negative:1; +#endif + } ieee_nan; +}; + +#define IEEE754_DOUBLE_BIAS 0x3ff /* Added to exponent. */ + +int drand48_r (struct drand48_data *buffer, double *result); +int lrand48_r (struct drand48_data *buffer, long int *result); +int mrand48_r (struct drand48_data *buffer, long int *result); +int srand48_r (long int seedval, struct drand48_data *buffer); \ No newline at end of file Index: Makefile IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- Makefile (revision cdd348294d86e74442bb29bd6767e48321259bec) +++ Makefile (date 1527998107000) @@ -40,6 +40,7 @@ DEPENDENCE_NORM := $(subst ${OBJ}/,${DEP}/,$(patsubst %.o,%.d,${OBJECTS})) LIB_OBJS_NORMAL := \ + ${OBJ}/common/randr_compat.o \ ${OBJ}/common/crc32c.o \ ${OBJ}/common/pid.o \ ${OBJ}/common/sha1.o \ Index: common/randr_compat.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- common/randr_compat.c (date 1527998213000) +++ common/randr_compat.c (date 1527998213000) @@ -0,0 +1,120 @@ +/* + The GNU C Library is free software. See the file COPYING.LIB for copying + conditions, and LICENSES for notices about a few contributions that require + these additional notices to be distributed. License copyright years may be + listed using range notation, eg, 2000-2011, indicating that every year in + the range, inclusive, is a copyrightable year that would otherwise be listed + individually. +*/ + +#include <stddef.h> +#include "common/randr_compat.h" + +int __drand48_iterate (unsigned short int xsubi[3], struct drand48_data *buffer) { + uint64_t X; + uint64_t result; + + /* Initialize buffer, if not yet done. */ + if (!buffer->__init == 0) + { + buffer->__a = 0x5deece66dull; + buffer->__c = 0xb; + buffer->__init = 1; + } + + /* Do the real work. We choose a data type which contains at least + 48 bits. Because we compute the modulus it does not care how + many bits really are computed. */ + + X = (uint64_t) xsubi[2] << 32 | (uint32_t) xsubi[1] << 16 | xsubi[0]; + + result = X * buffer->__a + buffer->__c; + + xsubi[0] = result & 0xffff; + xsubi[1] = (result >> 16) & 0xffff; + xsubi[2] = (result >> 32) & 0xffff; + + return 0; +} + +int __erand48_r (unsigned short int xsubi[3], struct drand48_data *buffer, double *result) { + union ieee754_double temp; + + /* Compute next state. */ + if (__drand48_iterate (xsubi, buffer) < 0) + return -1; + + /* Construct a positive double with the 48 random bits distributed over + its fractional part so the resulting FP number is [0.0,1.0). */ + + temp.ieee.negative = 0; + temp.ieee.exponent = IEEE754_DOUBLE_BIAS; + temp.ieee.mantissa0 = (xsubi[2] << 4) | (xsubi[1] >> 12); + temp.ieee.mantissa1 = ((xsubi[1] & 0xfff) << 20) | (xsubi[0] << 4); + + /* Please note the lower 4 bits of mantissa1 are always 0. */ + *result = temp.d - 1.0; + + return 0; +} + +int __nrand48_r (unsigned short int xsubi[3], struct drand48_data *buffer, long int *result) { + /* Compute next state. */ + if (__drand48_iterate (xsubi, buffer) < 0) + return -1; + + /* Store the result. */ + if (sizeof (unsigned short int) == 2) + *result = xsubi[2] << 15 | xsubi[1] >> 1; + else + *result = xsubi[2] >> 1; + + return 0; +} + +int __jrand48_r (unsigned short int xsubi[3], struct drand48_data *buffer, long int *result) { + /* Compute next state. */ + if (__drand48_iterate (xsubi, buffer) < 0) + return -1; + + /* Store the result. */ + *result = (int32_t) ((xsubi[2] << 16) | xsubi[1]); + + return 0; +} + +int drand48_r (struct drand48_data *buffer, double *result) { + return __erand48_r (buffer->__x, buffer, result); +} + +int lrand48_r (struct drand48_data *buffer, long int *result) { + /* Be generous for the arguments, detect some errors. */ + if (buffer == NULL) + return -1; + + return __nrand48_r (buffer->__x, buffer, result); +} + +int mrand48_r (struct drand48_data *buffer, long int *result) { + /* Be generous for the arguments, detect some errors. */ + if (buffer == NULL) + return -1; + + return __jrand48_r (buffer->__x, buffer, result); +} + +int srand48_r (long int seedval, struct drand48_data *buffer) { + /* The standards say we only have 32 bits. */ + if (sizeof (long int) > 4) + seedval &= 0xffffffffl; + + buffer->__x[2] = seedval >> 16; + buffer->__x[1] = seedval & 0xffffl; + buffer->__x[0] = 0x330e; + + buffer->__a = 0x5deece66dull; + buffer->__c = 0xb; + buffer->__init = 1; + + return 0; +} \ No newline at end of file 

Masukkan ke dalam folder ./patches dan modifikasi Dockerfile kita sedikit untuk menerapkan patch dengan cepat:

 FROM alpine:3.6 COPY ./patches /mtproxy/patches RUN apk add --no-cache --virtual .build-deps \ git make gcc musl-dev linux-headers openssl-dev \ && git clone --single-branch --depth 1 https://github.com/TelegramMessenger/MTProxy.git /mtproxy/sources \ && cd /mtproxy/sources \ && patch -p0 -i /mtproxy/patches/randr_compat.patch \ && make -j$(getconf _NPROCESSORS_ONLN) \ && cp /mtproxy/sources/objs/bin/mtproto-proxy /mtproxy/ \ && rm -rf /mtproxy/{sources,patches} \ && apk add --no-cache --virtual .rundeps libcrypto1.0 \ && apk del .build-deps 

Sekarang biner mtproto-proxy yang dirakit setidaknya diluncurkan, dan kita bisa melanjutkan.

Izin


Saatnya untuk mengubah run.sh asli menjadi docker-entrypoint.sh. Menurut pendapat saya, ini logis ketika "pengikatan wajib" masuk ke ENTRYPOINT (selalu dapat kelebihan beban dari luar), dan argumen untuk meluncurkan aplikasi buruh pelabuhan sesuai dengan maksimum dalam CMD (+ variabel lingkungan sebagai pengganti).

Kita bisa menginstal bash dan grep penuh di gambar alpine kita (saya akan jelaskan nanti) untuk menghindari sakit kepala dan menggunakan kode asli seperti apa adanya, tetapi itu akan mengembang gambar miniatur kita untuk memalukan, sehingga kita akan tumbuh nyata, ibunya, bonsai.

Mari kita mulai dengan shebang, ganti #!/bin/bash dengan #!/bin/sh . Default untuk alpine ash mampu mencerna hampir semua sintaks bash "sebagaimana adanya", tetapi kami masih menghadapi satu masalah - untuk alasan yang tidak diketahui, ia menolak menerima tanda kurung dalam salah satu kondisi, oleh karena itu kami akan memperluasnya dengan membalikkan logika perbandingan:



Sekarang kami sedang menunggu showdown dengan grep, yang dalam pengiriman busybox sedikit berbeda dari yang biasa (dan, omong-omong, jauh lebih lambat, ingatlah dalam proyek Anda). Pertama, dia tidak mengerti ungkapan {,15} , dia harus secara eksplisit menentukan {0,15} . Kedua, itu tidak mendukung flag -P (gaya perl), tetapi diam-diam mencerna ekspresi ketika extended (-E) diaktifkan.

Dalam dependensi kami, hanya ada ikal (tidak ada gunanya menggantinya dengan wget dari busybox) dan libcrypto (cukup, langsung openssl tidak diperlukan sama sekali dalam perakitan ini).

Bangunan multi-tahap yang indah muncul di Docker beberapa tahun yang lalu, sangat ideal, misalnya, untuk aplikasi Go atau untuk situasi di mana perakitannya rumit dan lebih mudah untuk mentransfer artefak dari gambar ke gambar daripada melakukan pembersihan akhir. Kami akan menggunakannya untuk menanam bonsai kami, ini akan menghemat sedikit.

 FROM alpine:3.6 #         ( ) RUN apk add --no-cache --virtual .build-deps \ # ... ,    && make -j$(getconf _NPROCESSORS_ONLN) FROM alpine:3.6 #   ,  ,   WORKDIR /mtproxy COPY --from=0 /mtproxy/sources/objs/bin/mtproto-proxy . #          #          

Bonsai harus bonsai - singkirkan instalasi libcrypto. Ketika membangun, kami membutuhkan file header dari paket openssl-dev, yang dalam dependensi akan menarik libcrypto dan executable kami akan berorientasi pada penggunaan libcrypto.so.1.0.0. Tapi ini adalah satu-satunya ketergantungan, selain itu, sudah diinstal di Alpine (dalam versi 3.6 itu libcrypto.so.41, 3.7 - libcrypto.so.42, ini ada di / lib /). Mereka memarahi saya sekarang, ini bukan cara yang paling dapat diandalkan, tapi itu sepadan dan kami masih menambahkan symlink ke versi yang ada (jika Anda memiliki cara yang lebih baik, saya akan dengan senang hati menerima PR).

Sentuhan akhir dan hasil:

Hub docker
Github

Saya akan berterima kasih atas saran dan kontribusi.

Source: https://habr.com/ru/post/id413881/


All Articles